Restore RecyclerView scroll position
You might have had the problem where a RecyclerView loses the scroll position when your Activity / Fragment is re-created. This usually happens because the Adapter data is loaded asynchronously and data hasn’t loaded by the time RecyclerView needs to layout so it fails to restore the scroll position.
Starting with 1.2.0-alpha02 , RecyclerView offers a new API to let the Adapter block layout restoration until it is ready. Read on to learn how to use this new API and how it works.
Restoring the scroll position
There are several ways to ensure a correct scroll position that you might have adopted. The best one is making sure that you always set the data on the Adapter before the first layout pass by caching the data you want to display in memory, in a ViewModel or in a repository. If this approach wasn’t possible, other solutions were either more complicated, like avoiding setting the Adapter on the RecyclerView , which can bring issues with items like headers, or misusing LayoutManager.onRestoreInstanceState API.
The recyclerview:1.2.0-alpha02 solution is a new Adapter method which allows you to set a state restoration policy (via the StateRestorationPolicy enum). This has 3 options:
- ALLOW — the default state, that restores the RecyclerView state immediately, in the next layout pass
- PREVENT_WHEN_EMPTY — restores the RecyclerView state only when the adapter is not empty ( adapter.getItemCount() > 0 ). If your data is loaded async, the RecyclerView waits until data is loaded and only then the state is restored. If you have default items, like headers or load progress indicators as part of your Adapter , then you should use the PREVENT option, unless the default items are added using ConcatAdapter (find out more here). ConcatAdapter waits for all of its adapters to be ready and only then it restores the state.
- PREVENT — all state restoration is deferred until you set ALLOW or PREVENT_WHEN_EMPTY .
Set the state restoration policy on the adapter like this:
That’s it! A short and sweet post to get you up to date with RecyclerView ’s lazy state restoration feature. Start using it 🏁👩💻👨💻!
Источник
How to scroll RecyclerView to a certain position?
This questions seems so simple, in fact Android SDK has provided several API to scroll item at certain position.
It should work as expected right? Afterall, scrolling an item to certain position is a fairly common usage. Yet, why does this question is still popular?
Me too, is a victim of this bug. I have spent years before I realised I was asking the wrong question, before I’ll answer this question, let me show you what I previously had done before.
This is a bit hacky, but always works (in my scenario). Try to reduce the delay, usually 500 milliseconds is enough.
The right question to ask about scrollToPosition is not How, but When.
When to scroll RecyclerView to a certain position?
I find that the problem why scrollToPosition , and others API, does not work is because the dataset is not yet ready. We should call scrollToPosition when the recyclerView has received dataset. To do that, we can use AdapterDataObserver .
AdapterDataObserver has five public methods.
As the method name implies, onItemRangeInserted is called when an item is added to the RecyclerView, and onItemRemoved is called when an item is removed from the RecyclerView.
There is an exception for onChanged . onChanged is only called when you call notify on an Adapter, it is not going to called everytime there is a change on the RecyclerView.
I usually call scrollToPosition when the dataset is succesfully inserted to a RecyclerView, for example:
This way, we don’t have to wait certain millisecond to scroll a ReyclerView. When there is a new dataset, we can trigger scrollToPosition or smoothScrollToPosition and be sure RecyclerView is scrolled.
Источник
polbins / EndlessListActivity.java
Endless Scroll Listener for Recycler Views
Endless Scrolling for Paginated APIs as shown in : Google Design — Progress Activity Behavior which has the following components:
- Endless Scroll Listener for Recycler Views
- concrete class implements loadMore(pageNumber) to signal the API to load more data
- List Adapter which shows:
- Empty View when given an empty Item List (TextView)
- Progress View when we are currently loading (ProgressBar)
- addAll(. ) to append data to the end of the list
- setIsAppending(boolean) for signaling the start/end of data loading
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public class EndlessListActivity extends Activity < |
private EndlessRecyclerOnScrollListener mEndlessScrollListener; |
private EndlessListAdapter mAdapter; |
@Override |
protected void onCreate ( Bundle savedInstanceState ) < |
super . onCreate(savedInstanceState); |
RecyclerView listView = ( RecyclerView ) findViewById( R . id . list); |
LinearLayoutManager linearLayoutManager = new LinearLayoutManager ( this ); |
mEndlessScrollListener = new EndlessRecyclerOnScrollListener (mLinearLayoutManager) < |
@Override |
public void onLoadMore ( int current_page ) < |
// try to get adapter from the recycler view, and cast it appropriately |
EndlessListAdapter adapter = ( EndlessListAdapter ) mListView . getAdapter(); |
if (adapter != null ) < |
// signal adapter that loading attempt has started |
adapter . setIsAppending( true ); |
adapter . notifyItemInserted(adapter . getItemCount()); |
// Call your API Here |
loadMore(current_page); |
> |
> |
>; |
listView . setLayoutManager(linearLayoutManager); |
listview . addOnScrollListener(mEndlessScrollListener); |
// Do your RecyclerView stuff here |
mAdapter = new EndlessListAdapter ( . ); |
> |
// When your Data from the API is loaded, |
// set the Total Entries on your Endless Scroll Listener |
public void onDataLoaded ( int serverTotalEntries ) < |
mEndlessScrollListener . setTotalEntries(serverTotalEntries); |
> |
// After finishing loading of new data, |
// add it to your adapter |
public void onLoadMore ( List D > dataList ) < |
mAdapter . addAll(dataList); |
mAdapter . setIsAppending( false ); |
> |
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public abstract class EndlessListAdapter extends RecyclerView . ViewHolder > |
extends RecyclerView . Adapter VH > < |
private boolean mIsAppending = false ; |
protected static final int VIEW_TYPE_PROGRESS = 333 ; |
public EndlessListAdapter ( List D > dataList ) < |
super (dataList); |
> |
public boolean isAppending () < |
return mIsAppending; |
> |
public void setIsAppending ( boolean isAppending ) < |
mIsAppending = isAppending; |
> |
@Override |
public int getItemCount () < |
return isAppending() ? |
super . getItemCount() + 1 : super . getItemCount(); |
> |
@Override |
public int getItemViewType ( int position ) < |
return (isAppending() && position >= super . getItemCount()) ? |
VIEW_TYPE_PROGRESS : super . getItemViewType(position); |
> |
@Override |
public final VH onCreateViewHolder ( ViewGroup parent , int viewType ) < |
RecyclerView . ViewHolder vh; |
if (viewType == VIEW_TYPE_PROGRESS ) < |
View v = LayoutInflater . from(parent . getContext()) |
.inflate( R . layout . layout_progress_bar, parent, false ); |
vh = new ProgressViewHolder (v); |
> else < |
vh = super . onCreateViewHolder(parent, viewType); |
> |
return ( VH ) vh; |
> |
@Override |
public final void onBindViewHolder ( VH holder , int position ) < |
if (holder instanceof ProgressViewHolder ) < |
// do nothing |
> else < |
super . onBindViewHolder(holder, position); |
> |
> |
public static class ProgressViewHolder extends RecyclerView . ViewHolder < |
public ProgressViewHolder ( View v ) < |
super (v); |
> |
> |
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView . OnScrollListener < |
private boolean mLoading = false ; // True if we are still waiting for the last set of data to load |
private int previousItemCount = 0 ; // The total number of items in the dataset after the last load |
private int mTotalEntries; // The total number of entries in the server |
private int current_page = 1 ; // Always start at Page 1 |
private LinearLayoutManager mLinearLayoutManager; |
public EndlessRecyclerOnScrollListener ( LinearLayoutManager linearLayoutManager ) < |
mLinearLayoutManager = linearLayoutManager; |
> |
// Concrete classes should implement the Loading of more data entries |
public abstract void onLoadMore ( int current_page ); |
public void setTotalEntries ( int totalEntries ) < |
mTotalEntries = totalEntries; |
> |
// when you’re RecyclerView supports refreshing, also refresh the count |
public void refresh () < |
current_page = 1 ; |
previousItemCount = 0 ; |
> |
@Override |
public void onScrolled ( RecyclerView recyclerView , int dx , int dy ) < |
super . onScrolled(recyclerView, dx, dy); |
int visibleItemCount = recyclerView . getChildCount(); |
int totalItemCount = mLinearLayoutManager . getItemCount(); |
int firstVisibleItem = mLinearLayoutManager . findFirstVisibleItemPosition(); |
if (mLoading) < |
int diffCurrentFromPrevious = totalItemCount — previousItemCount; |
// check if current total is greater than previous (diff should be greater than 1, for considering placeholder) |
// and if current total is equal to the total in server |
if ((diffCurrentFromPrevious > 1 ) || |
totalItemCount >= mTotalEntries) < |
mLoading = false ; |
previousItemCount = totalItemCount; |
> |
> else < |
if (totalItemCount >= mTotalEntries) < |
// do nothing, we’ve reached the end of the list |
> else < |
// check if the we’ve reached the end of the list, |
// and if the total items is less than the total items in the server |
if ((firstVisibleItem + visibleItemCount) >= totalItemCount && |
totalItemCount mTotalEntries) < |
onLoadMore( ++ current_page); |
mLoading = true ; |
previousItemCount = totalItemCount; |
> |
> |
> |
> |
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Источник
Обнаружение начала и конца прокрутки в recyclerview
Мне нужно определить начало / конец и направление прокрутки в recyclerview. У прослушивателя прокрутки есть два метода: onScrolled() и onScrollStateChanged() . Первый метод вызывается после запуска прокрутки (действительно, вызывается onScrolled (), а не onScrolling ()). Второй метод дает информацию о состоянии, но у меня нет информации о направлении. Как я могу достичь своей цели?
7 ответов
Шаг 1 Вы можете создать класс, расширяющий RecyclerView.OnScrollListener, и переопределить эти методы
Шаг 2- Поскольку setOnScrollListener устарел, лучше использовать addOnScrollListener
Вот полное решение для выполнения действия после остановки прокрутки (для RecyclerView)
Который исправил мое исключение OutOfBounds, связанное с прокруткой recyclerView
Думаю, другие ответы не касаются вашей болевой точки.
О вопросе
Как вы сказали, RecycleView вызовет onScrollStateChanged () перед методом onScrolled (). Однако метод onScrollStateChanged () может сообщить вам только три состояния RecycleView:
- SCROLL_STATE_IDLE
- SCROLL_STATE_DRAGGING
- SCROLL_STATE_SETTLING
Это означает, что предположим, что RecycleView сейчас находится наверху, если пользователь прокручивает страницу вверх, он может запускать только метод onScrollStateChanged() , но не метод onScrolled() . И если пользователь прокручивает страницу вниз, сначала запускается метод onScrollStateChanged() , а затем метод onScrolled() .
Затем возникает проблема: SCROLL_STATE_DRAGGING может только сказать вам, что пользователь перетаскивает представление, но не может сказать направление его перетаскивания.
Вы должны объединить с методом dy from onScrolled() для определения направления перетаскивания, но onScrolled() не запускается с onScrollStateChanged() .
MySolution:
Запишите состояние прокрутки при срабатывании onScrollStateChanged() , затем задержите время, например 10 мс, проверьте, есть ли движение по оси Y, и сделайте вывод о направлении перетаскивания.
Источник