Using below code to check that whether RecyclerView reached to bottom or not..
Means Checking that last item of the Recyclerview is visible or not..
For that I have googled and added Scroll listener in Recyclerview.
Using below code:
MyRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!MyRecyclerView.canScrollVertically(1)) {
Toast.makeText(mContext, "Last", Toast.LENGTH_LONG).show();
}
}
})
Here, I am trying to check that Recyclerview reached to bottom or not.
Toast is not displaying when I scroll recyclerview to the bottom.
But, Toast displayed suddenly when items in the recyclerview binds first time. It should toast only when user scroll up to the bottom of the Recyclerview.
What might be the issue ?
Please guide. Thanks.
I think, using RecyclerView.LayoutManager api will better suit your needs.
You can check LinearLayoutManager::findLastCompletelyVisibleItemPosition or findLastVisibleItemPosition method in your RecyclerView.OnScrollListener
rvPlayers?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
if (isLastVisable()) {
//code here
}
}
}
})
private fun isLastVisable(): Boolean {
val layoutManager = rvPlayers.layoutManager as LinearLayoutManager
val pos = layoutManager.findLastCompletelyVisibleItemPosition()
val numItems = adapter.itemCount
return (pos >= numItems - 1)
}
Related
My RecyclerView scroll code
But this code gets called twice on the first start, I think findFirstCompletelyVisibleItemPosition is the problem
I used that code because I had to pull the scroll up. Is there any other way?
If you know how, please answer.
binding.rcvMessageRoom.run {
adapter = messageRoomRecyclerViewAdapter
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
scrollToBottom()
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) return
if ((layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0) {
val handler = Handler()
handler.postDelayed({
viewModel.getChatMessage(chatRoomId, ++page, size, lastId)
}, 100)
}
}
})
}
I parse NewsApi and use clean architecture also ListAdapter.
I need to do "pagination" so that In my fragment be less code.
By use abstract class or extension file, but I don't know, how to do it.
Also I don't understand how to do addAll method in ListAdapter, so that past items don't disappear.
How I understand paging 3 not compatible with clean architecture.
I have tried do the follow:https://coderoad.ru/51433106/kotlin-RecyclerView-%D0%BD%D1%83%D0%BC%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F-%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86
but it failed.
Also the follow:
fun pagination(viewModel: EverythingViewModel): RecyclerView.OnScrollListener {
return object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount) {
viewModel.page++
viewModel.getEverything()
}
}
}
}
}
Also failed
If you need more details write me in the comments
I am trying to add an infinite scroll to my Android Application but the onScrolled method doesnt work correctly I think so.
It will be called only once if I call the addOnScrollListener. But I think it should be called every time the RecyclerView has been scrolled.
linearLayoutManager = LinearLayoutManager(this)
recyclerViewNeuheiten.layoutManager = linearLayoutManager//LinearLayoutManager(this)
recyclerViewHistory.layoutManager = LinearLayoutManager(this)
recyclerViewBestSeller.layoutManager = LinearLayoutManager(this)
recyclerViewFavorites.layoutManager = LinearLayoutManager(this)
recyclerViewNeuheiten.adapter = neuheitenAdapter
recyclerViewHistory.adapter = historyAdapter
recyclerViewBestSeller.adapter = bestsellerAdapter
recyclerViewFavorites.adapter = favoriteAdapter
//setRecyclerViewScrollListener()
setRecyclerViewScrollListener()
private fun setRecyclerViewScrollListener() {
Log.v("scroll", "set listener")
recyclerViewNeuheiten.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
Log.v("scroll", "onScrollStateChanged newState $newState")
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
Log.v("scroll", "onScrolled !!!!!")
super.onScrolled(recyclerView, dx, dy)
val currentItem = recyclerView.layoutManager!!.childCount
val totalItemCount = recyclerView.layoutManager!!.itemCount
Log.v("scroll", "currentItem $currentItem")
Log.v("scroll", "totalItemCount $totalItemCount")
Log.v("scroll", "lastVisibleItemPosition $lastVisibleItemPosition")
Log.v("scroll", "scroll out items ${linearLayoutManager.findFirstVisibleItemPosition()}")
}
})
}
I found the problem. I am using my RecyclerView inside a NestedScrollView. The onScrolled method triggers each scroll without a ScrollView.
I am using the new ExtendedFloatingActionButton from the Material Components for Android library 1.1.0-alpha06. It is rendered just fine but the 'extend' and 'shrink' methods are not doing anything.
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="#+id/extended_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="#id/bottom_sheet"
android:text="Drag map to change location"
app:icon="#drawable/my_location"
app:backgroundTint="#color/white"
app:iconTint="#color/quantum_googblueA200"
android:textColor="#color/quantum_googblueA200"
app:iconSize="18dp"
style="#style/Widget.MaterialComponents.ExtendedFloatingActionButton"
android:padding="4dp"
android:textSize="12sp"
android:textAllCaps="false"
android:layout_margin="8dp"
app:layout_anchorGravity="right|top"/>
Here's the rendered layout:
Here is a Kotlin version which matches the behavior of the built-in Contacts app. The FAB is extended whenever the RecyclerView is at the top, and the FAB shrinks whenever the user scrolls away from the top.
class FabExtendingOnScrollListener(
private val floatingActionButton: ExtendedFloatingActionButton
) : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE
&& !floatingActionButton.isExtended
&& recyclerView.computeVerticalScrollOffset() == 0
) {
floatingActionButton.extend()
}
super.onScrollStateChanged(recyclerView, newState)
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy != 0 && floatingActionButton.isExtended) {
floatingActionButton.shrink()
}
super.onScrolled(recyclerView, dx, dy)
}
}
Usage:
recyclerView.addOnScrollListener(FabExtendingOnScrollListener(fab))
I have done this in my code.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
extendedFloatingActionButton.extend();
}
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
if (dy > 0 || dy < 0 && extendedFloatingActionButton.isExtended()) {
extendedFloatingActionButton.shrink();
}
}
});
For Kotlin users:
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0 && extendedFab.isExtended) {
extendedFab.shrink()
} else if (dy < 0 && !extendedFab.isExtended) {
extendedFab.extend()
}
}
})
I have a Fragment with a ViewModel (so Mvvm) and in the xml I have 2 RecyclerView with both having their own Adapter using the same itemList because I want to see the same data in 2 different layout.
Before switching to Mvvm, I could reference the adapter and the recyclerView and could reach "getChildAdapterPosition"
But right now this is quite impossible it seem. I made some custom BindingAdapter to bind my Adapter, a CustomPageSnaperHelper and a scrollListener. but now I can't seem to be able to sync the 2 to show the same item even if i scroll the bottom one or the top one.
Gonna post some code snippet, but not sure which one would help more:
#JvmStatic #BindingAdapter("adapter")
fun <T : RecyclerView.ViewHolder> setRvAdapter(rv: RecyclerView, adapter: RecyclerView.Adapter<T>?) {
if(adapter != null) rv.adapter = adapter
}
#JvmStatic #BindingAdapter("pageSnapper")
fun setPageSnapper(rv: RecyclerView, cs: CustomSnapHelper?){
cs?.attachToRecyclerView(rv)
}
#JvmStatic #BindingAdapter("rvScrollListener")
fun setRvScrollListener(rv: RecyclerView, sl: RecyclerView.OnScrollListener?){
if (sl == null) return
rv.addOnScrollListener(sl)
}
Xml:
<androidx.recyclerview.widget.RecyclerView
adapter="#{vm.OCardAdapter}"
pageSnapper="#{vm.OCardPageSnaper}"
rvScrollListener="#{vm.OCardScrollListener}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/top_tabs"
tools:listItem="#layout/card_layout" />
<androidx.recyclerview.widget.RecyclerView
adapter="#{vm.OCarouselAdapter}"
pageSnapper="#{vm.OCarouselPageSnaper}"
rvScrollListener="#{vm.OCarouselScrollListener}"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:background="#color/colorWhite"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:listItem="#layout/carousel_layout"/>
To be fair I could paste in more of my code, but I don't think it could help more ish.
val scrollListenerCard = object: RecyclerView.OnScrollListener(){
var mScrolled = false
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
}
}
You could create
#InverseBindingAdapter(attribute = "currentPosition")
public static int getCurrentPosition(final RecyclerView rv) {
return ((LinearLayoutManager) rv.getLayoutManager()).findFirstVisibleItemPosition();
}
#BindingAdapter(value = "currentPositionAttributeChanged")
public static void setListener(final RecyclerView rv, final InverseBindingListener l) {
final int prevPos = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy == 0) {
return;
}
int newPosition = linearLayoutManager.findFirstVisibleItemPosition();
if (previousPosition != newPosition) {
l.onChange();
}
}
});
}
#BindingAdapter("currentPosition")
public void setCurrentPosition(final RecyclerView rv, final int pos) {
// no need to do anything here - setter required by databinding
}
These would give you access to the positions, or you can set these listeners by accessing bindings inside your fragment/activity and update your viewModel from there.
I'm not sure that I understand your problem well but if I had to create two rw-s that are always scrolled to the same position with databindig, than I would give adapter A to scroll listener B as a constructor parameter (and inversely too) and this way I would call the adapter's scrollTo() method when the listener fires.
class MyScrollListener(private val recyclerView: RecyclerView): RecyclerView.OnScrollListener(){
var mScrolled = false
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
this.recyclerView.smoothScrollToPosition(newState)
}
}
val scrollListenerCard = MyScrollListener(recyclerView)
Based on Mirza's answer above, I came up with this in kotlin, which works nicely:
#JvmStatic
#InverseBindingAdapter(attribute = "currentPosition", event = "currentPositionAttributeChanged")
fun getCurrentPosition(rv: RecyclerView): Int {
return (rv.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
}
#JvmStatic
#BindingAdapter(value = ["currentPositionAttributeChanged"])
fun setListener(rv: RecyclerView, l: InverseBindingListener) {
val layoutManager = (rv.layoutManager as LinearLayoutManager?)!!
var prevPos = layoutManager.findFirstVisibleItemPosition()
rv.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy == 0) {
return
}
val newPos = layoutManager.findFirstVisibleItemPosition()
if (prevPos != newPos) {
prevPos = newPos
l.onChange()
}
}
})
}
#JvmStatic
#BindingAdapter("currentPosition")
fun setCurrentPosition(rv: RecyclerView, pos: Int) {
(rv.layoutManager as LinearLayoutManager).scrollToPosition(pos)
}
I use it like this:
ViewModel has:
val currentPos = MutableLiveData<Int>()
And the layout has:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
currentPosition="#={viewModel.currentPos}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">