Reputation: 423
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
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
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:
Set stackFromEnd
to true.
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.
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
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
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
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
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