42

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.

Remees M Syde
  • 2,564
  • 1
  • 19
  • 42
lj wang
  • 423
  • 1
  • 4
  • 5
  • Did you resolve the problem at last? I find setReverseLayout to true will cause the item displayed from top to bottom, it's not suitable for chat. – CodeAlien Sep 04 '15 at 10:02
  • you can try my answer: http://stackoverflow.com/a/43647835/6482350 – DysaniazzZ Apr 28 '17 at 02:23

7 Answers7

50

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.

Kirill Kulakov
  • 10,035
  • 9
  • 50
  • 67
37

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.

yigit
  • 37,683
  • 13
  • 72
  • 58
  • thanks,you solve my problem. i use reverseLayout param – lj wang Nov 21 '14 at 08:52
  • 17
    The layout contents got reversed, but the list didn't scroll to the end. Even stackFromBottom didn't help. – Codevalley Feb 02 '15 at 05:02
  • Just an addition: Sometimes setting `stackFromEnd` in XML is **not** working while calling the method on code does. – guness Dec 16 '15 at 10:03
  • 1
    If you set both stackFromEnd=true and setReverseLayout=true it doesn't work. Does anyone know any workaround? – Luccas Correa Jan 22 '16 at 17:21
  • @LuccasCorrea How did you get it to work? I used llm.setStackFromEnd(true); and mRecyclerView.scrollToPosition(mItemsList.size() - 1); on adding an item at the bottom of the list. Let me know if you found any other way. – Nitin Sethi Mar 24 '16 at 20:03
  • 1
    mLinearLayoutManager.setStackFromEnd(true); really helped for keyboard opening fix recyclerView.. Thanks man.. :) – Meet Vora Jun 16 '16 at 12:17
  • In my case, I set both `reverseLayout` and `stackFromEnd` to true, and sort my data reversely, which works just like a Chat list. – Evi Song Mar 04 '18 at 07:01
  • I set `reverseLayout` and `stackFromEnd` and sorted the data reversely - everything got reversed but it doesn't scroll to the end. – User Feb 28 '20 at 12:29
6
recyclerView.scrollToPosition(getAdapter().getItemCount()-1);
Bacteria
  • 8,406
  • 10
  • 50
  • 67
jun xia
  • 137
  • 1
  • 6
  • 1
    This doesn't work well when users scroll up a bit and wish to stay same for the whatever last visible item. – John Pang Jan 08 '17 at 19:38
5

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!

Community
  • 1
  • 1
  • 1
    this works best out of all the answers on this page, however, when you implement the code be sure to check that `myAdapter.getItemCount() > 0` Otherwise when the app starts up without items in the list, it'll crash because it tries to scroll to position -1 – Someone Somewhere Apr 16 '19 at 15:58
1

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);
Shivam Sharma
  • 290
  • 2
  • 14
0

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
        }
    }
}
Kachi
  • 3,609
  • 2
  • 23
  • 18
0

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)
    }
})
Diego Marcher
  • 378
  • 3
  • 7