lj wang
lj wang

Reputation: 423

how to keep RecyclerView always scroll bottom

I Use Recyclerview Replace with list view I want to keep Recyclerview always scroll bottom.

ListView can use this method setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL)

RecyclerView I use method smoothScrollToPosition(myAdapter.getItemCount() - 1)

but when Soft keyboard Pop ,its replace RecyclerView content.

Upvotes: 42

Views: 47535

Answers (7)

Diego Marcher
Diego Marcher

Reputation: 388

Although this questions is a bit old, seems like no answer is correct. I think what you need here is to listen to the Adapter changes and then scroll to the bottom on every detected change.

This is a working example of it

yourAdapter?.registerAdapterDataObserver(object : AdapterDataObserver() {
    override fun onChanged() {
        yourRecyclerView?.smoothScrollToPosition(yourAdapter?.itemCount ?: 0)
    }
})

Upvotes: 0

Kachi
Kachi

Reputation: 3709

I ran into this problem myself and I ended up creating my own LayoutManager to solve it. It's a pretty straightforward solution that can be broken down into three steps:

  1. Set stackFromEnd to true.

  2. Determine whether forceTranscriptScroll should be set to true whenever onItemsChanged is called. Per the documentation, onItemsChanged gets called whenever the contents of the adapter changes. If transcriptMode is set to Disabled, forceTranscriptScroll will always be false, if it's set to AlwaysScroll, it will always be true, and if it's set to Normal, it will only be true if the last item in the adapter is completely visible.

  3. In onLayoutCompleted, scroll to the last item in the list if forceTranscriptScroll is set to true and the last item in the list isn't already completely visible.

Below is the code that accomplishes these three steps:

import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class TranscriptEnabledLinearLayoutManager(context: Context, transcriptMode: TranscriptMode = TranscriptMode.Normal) :
    LinearLayoutManager(context) {

    enum class TranscriptMode {
        Disabled, Normal, AlwaysScroll
    }

    private var transcriptMode: TranscriptMode = TranscriptMode.Disabled
        set(value) {
            field = value
            // Step 1
            stackFromEnd = field != TranscriptMode.Disabled
        }

    private var forceTranscriptScroll = false

    init {
        this.transcriptMode = transcriptMode
    }

    // Step 2
    override fun onItemsChanged(recyclerView: RecyclerView) {
        super.onItemsChanged(recyclerView)
        forceTranscriptScroll = when (transcriptMode) {
            TranscriptMode.Disabled -> false
            TranscriptMode.Normal -> {
                findLastCompletelyVisibleItemPosition() == itemCount - 1
            }
            TranscriptMode.AlwaysScroll -> true
        }
    }

    // Step 3
    override fun onLayoutCompleted(state: RecyclerView.State?) {
        super.onLayoutCompleted(state)
        val recyclerViewState = state ?: return

        if (!recyclerViewState.isPreLayout && forceTranscriptScroll) {
            // gets the position of the last item in the list. returns if list is empty
            val lastAdapterItemPosition = recyclerViewState.itemCount.takeIf { it > 0 }
                ?.minus(1) ?: return
            val lastCompletelyVisibleItem = findLastCompletelyVisibleItemPosition()
            if (lastCompletelyVisibleItem != lastAdapterItemPosition ||
                recyclerViewState.targetScrollPosition != lastAdapterItemPosition) {
                scrollToPositionWithOffset(lastAdapterItemPosition, 0)
            }
            forceTranscriptScroll = false
        }
    }
}

Upvotes: 0

Shivam Sharma
Shivam Sharma

Reputation: 420

I have also faced same problem. But following code help me. I hope this is useful.

In this staus is arraylist.

 recyclerView.scrollToPosition(staus.size()-1);

next one is:- In This you can use adapter class

   recyclerView.scrollToPosition(showAdapter.getItemCount()-1);

Upvotes: 1

jun xia
jun xia

Reputation: 137

recyclerView.scrollToPosition(getAdapter().getItemCount()-1);

Upvotes: 6

Kirill Kulakov
Kirill Kulakov

Reputation: 10245

If you want to keep the scroll position anchored to the bottom of the RecyclerView, it's useful in chat apps. just call setStackFromEnd(true) to on the LinearLayoutManager to make the keyboard keep the list items anchored on the bottom (the keyboard) and not the top.

Upvotes: 50

Krishna Kalubandi
Krishna Kalubandi

Reputation: 542

I have faced the same problem and I solved it using the approach mentioned here. It is used to detect whether soft keyboard is open or not and if it is open, just call the smoothScrollToPosition() method.

A much simpler solution is to give your activity's root view a known ID, say '@+id/activityRoot', hook a GlobalLayoutListener into the ViewTreeObserver, and from there calculate the size diff between your activity's view root and the window size:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
    int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
    if (heightDiff > 100) { 
        recyclerView.smoothScrollToPosition(myAdapter.getItemCount() - 1);
    }
 }
});

Easy!

Upvotes: 5

yigit
yigit

Reputation: 38243

This is because RV thinks its reference point is TOP and when keyboard comes up, RV's size is updated by the parent and RV keeps its reference point stable. (thus keeps the top position at the same location)

You can set LayoutManager#ReverseLayout to true in which case RV will layout items from the end of the adapter.

e.g. adapter position 0 is at the bottom, 1 is above it etc...

This will of course require you to reverse the order of your adapter.

I'm not sure but setting stack from end may also give you the same result w/o reordering your adapter.

Upvotes: 37

Related Questions