RecyclerView
is responsible for displaying the list (I hope, we have already forgotten everything about ListView
:)) and all the necessary classes for setting up the RecyclerView
.offset
and limit
PublishSubject
. Secondly, Subscriber
will perfectly suit the role of a “listener”.RecyclerView
. public class AutoLoadingRecyclerView<T> extends RecyclerView
limit
, responsible for the size of the portion of the loaded data at a time. private int limit; public int getLimit() { if (limit <= 0) { throw new AutoLoadingRecyclerViewExceptions("limit must be initialised! And limit must be more than zero!"); } return limit; } /** * required method */ public void setLimit(int limit) { this.limit = limit; }
AutoLoadingRecyclerView
should "radiate" the sequence number of the last element visible on the screen.offset
and limit
, combined into the following model: public class OffsetAndLimit { private int offset; private int limit; public OffsetAndLimit(int offset, int limit) { this.offset = offset; this.limit = limit; } public int getOffset() { return offset; } public int getLimit() { return limit; } }
private PublishSubject<OffsetAndLimit> scrollLoadingChannel = PublishSubject.create(); // private void startScrollingChannel() { addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { int position = getLastVisibleItemPosition(); int limit = getLimit(); int updatePosition = getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { int offset = getAdapter().getItemCount() - 1; OffsetAndLimit offsetAndLimit = new OffsetAndLimit(offset, limit); scrollLoadingChannel.onNext(offsetAndLimit); } } }); } // // LayoutManager private int getLastVisibleItemPosition() { Class recyclerViewLMClass = getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new AutoLoadingRecyclerViewExceptions("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); }
int updatePosition = getAdapter().getItemCount() - 1 - (limit / 2);
limit
or slightly change this condition so that the data loading takes place a little earlier. int updatePosition = getAdapter().getItemCount() - 1 - (limit / 2);
// private void subscribeToLoadingChannel() { Subscriber<OffsetAndLimit> toLoadingChannelSubscriber = new Subscriber<OffsetAndLimit>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { Log.e(TAG, "subscribeToLoadingChannel error", e); } @Override public void onNext(OffsetAndLimit offsetAndLimit) { // unsubscribe(); // loadNewItems(offsetAndLimit); } }; // scrollLoadingChannel - . subscribeToLoadingChannelSubscription = scrollLoadingChannel .subscribe(toLoadingChannelSubscriber); } // private void loadNewItems(OffsetAndLimit offsetAndLimit) { Subscriber<List<T>> loadNewItemsSubscriber = new Subscriber<List<T>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { Log.e(TAG, "loadNewItems error", e); subscribeToLoadingChannel(); } @Override public void onNext(List<T> ts) { // // , addNewItems. , getAdapter().addNewItems(ts); // getAdapter().notifyItemInserted(getAdapter().getItemCount() - ts.size()); // , (), . // if (ts.size() > 0) { // subscribeToLoadingChannel(); } } }; // getLoadingObservable().getLoadingObservable(offsetAndLimit) - AutoLoadingRecyclerView Observable. loadNewItemsSubscription = getLoadingObservable().getLoadingObservable(offsetAndLimit) // UI .subscribeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) // UI ( ) // ( ), // View UI .observeOn(AndroidSchedulers.mainThread()) .subscribe(loadNewItemsSubscriber); }
AutoLoadingRecyclerView
. /** * Offset and limit for {@link AutoLoadingRecyclerView AutoLoadedRecyclerView channel} * * @author e.matsyuk */ public class OffsetAndLimit { private int offset; private int limit; public OffsetAndLimit(int offset, int limit) { this.offset = offset; this.limit = limit; } public int getOffset() { return offset; } public int getLimit() { return limit; } @Override public String toString() { return "OffsetAndLimit{" + "offset=" + offset + ", limit=" + limit + '}'; } }
/** * @author e.matsyuk */ public class AutoLoadingRecyclerViewExceptions extends RuntimeException { public AutoLoadingRecyclerViewExceptions() { super("Exception in AutoLoadingRecyclerView"); } public AutoLoadingRecyclerViewExceptions(String detailMessage) { super(detailMessage); } }
/** * @author e.matsyuk */ public interface ILoading<T> { Observable<List<T>> getLoadingObservable(OffsetAndLimit offsetAndLimit); }
/** * Adapter for {@link AutoLoadingRecyclerView AutoLoadingRecyclerView} * * @author e.matsyuk */ public abstract class AutoLoadingRecyclerViewAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private List<T> listElements = new ArrayList<>(); public void addNewItems(List<T> items) { listElements.addAll(items); } public List<T> getItems() { return listElements; } public T getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } }
/** * @author e.matsyuk */ public class LoadingRecyclerViewAdapter extends AutoLoadingRecyclerViewAdapter<Item> { private static final int MAIN_VIEW = 0; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } @Override public long getItemId(int position) { return getItem(position).getId(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
/** * @author e.matsyuk */ public class AutoLoadingRecyclerView<T> extends RecyclerView { private static final String TAG = "AutoLoadingRecyclerView"; private static final int START_OFFSET = 0; private PublishSubject<OffsetAndLimit> scrollLoadingChannel = PublishSubject.create(); private Subscription loadNewItemsSubscription; private Subscription subscribeToLoadingChannelSubscription; private int limit; private ILoading<T> iLoading; private AutoLoadingRecyclerViewAdapter<T> autoLoadingRecyclerViewAdapter; public AutoLoadingRecyclerView(Context context) { super(context); init(); } public AutoLoadingRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public AutoLoadingRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } /** * required method * call after init all parameters in AutoLoadedRecyclerView */ public void startLoading() { OffsetAndLimit offsetAndLimit = new OffsetAndLimit(START_OFFSET, getLimit()); loadNewItems(offsetAndLimit); } private void init() { startScrollingChannel(); } private void startScrollingChannel() { addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { int position = getLastVisibleItemPosition(); int limit = getLimit(); int updatePosition = getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { int offset = getAdapter().getItemCount() - 1; OffsetAndLimit offsetAndLimit = new OffsetAndLimit(offset, limit); scrollLoadingChannel.onNext(offsetAndLimit); } } }); } private int getLastVisibleItemPosition() { Class recyclerViewLMClass = getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new AutoLoadingRecyclerViewExceptions("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } public int getLimit() { if (limit <= 0) { throw new AutoLoadingRecyclerViewExceptions("limit must be initialised! And limit must be more than zero!"); } return limit; } /** * required method */ public void setLimit(int limit) { this.limit = limit; } @Deprecated @Override public void setAdapter(Adapter adapter) { if (adapter instanceof AutoLoadingRecyclerViewAdapter) { super.setAdapter(adapter); } else { throw new AutoLoadingRecyclerViewExceptions("Adapter must be implement IAutoLoadedAdapter"); } } /** * required method */ public void setAdapter(AutoLoadingRecyclerViewAdapter<T> autoLoadingRecyclerViewAdapter) { if (autoLoadingRecyclerViewAdapter == null) { throw new AutoLoadingRecyclerViewExceptions("Null adapter. Please initialise adapter!"); } this.autoLoadingRecyclerViewAdapter = autoLoadingRecyclerViewAdapter; super.setAdapter(autoLoadingRecyclerViewAdapter); } public AutoLoadingRecyclerViewAdapter<T> getAdapter() { if (autoLoadingRecyclerViewAdapter == null) { throw new AutoLoadingRecyclerViewExceptions("Null adapter. Please initialise adapter!"); } return autoLoadingRecyclerViewAdapter; } public void setLoadingObservable(ILoading<T> iLoading) { this.iLoading = iLoading; } public ILoading<T> getLoadingObservable() { if (iLoading == null) { throw new AutoLoadingRecyclerViewExceptions("Null LoadingObservable. Please initialise LoadingObservable!"); } return iLoading; } private void subscribeToLoadingChannel() { Subscriber<OffsetAndLimit> toLoadingChannelSubscriber = new Subscriber<OffsetAndLimit>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { Log.e(TAG, "subscribeToLoadingChannel error", e); } @Override public void onNext(OffsetAndLimit offsetAndLimit) { unsubscribe(); loadNewItems(offsetAndLimit); } }; subscribeToLoadingChannelSubscription = scrollLoadingChannel .subscribeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(toLoadingChannelSubscriber); } private void loadNewItems(OffsetAndLimit offsetAndLimit) { Subscriber<List<T>> loadNewItemsSubscriber = new Subscriber<List<T>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { Log.e(TAG, "loadNewItems error", e); subscribeToLoadingChannel(); } @Override public void onNext(List<T> ts) { getAdapter().addNewItems(ts); getAdapter().notifyItemInserted(getAdapter().getItemCount() - ts.size()); if (ts.size() > 0) { subscribeToLoadingChannel(); } } }; loadNewItemsSubscription = getLoadingObservable().getLoadingObservable(offsetAndLimit) .subscribeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(loadNewItemsSubscriber); } /** * required method * call in OnDestroy(or in OnDestroyView) method of Activity or Fragment */ public void onDestroy() { scrollLoadingChannel.onCompleted(); if (subscribeToLoadingChannelSubscription != null && !subscribeToLoadingChannelSubscription.isUnsubscribed()) { subscribeToLoadingChannelSubscription.unsubscribe(); } if (loadNewItemsSubscription != null && !loadNewItemsSubscription.isUnsubscribed()) { loadNewItemsSubscription.unsubscribe(); } } }
AutoLoadingRecyclerView
it should also be noted that we should not forget about the life cycle and possible memory leaks from RxJava. Therefore, when we “kill” our list, we must remember to unsubscribe from all Subscribers
. /** * A placeholder fragment containing a simple view. */ public class MainActivityFragment extends Fragment { private final static int LIMIT = 50; private AutoLoadingRecyclerView<Item> recyclerView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); init(rootView); return rootView; } @Override public void onResume() { super.onResume(); // // recyclerView.startLoading(); } private void init(View view) { recyclerView = (AutoLoadingRecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); LoadingRecyclerViewAdapter recyclerViewAdapter = new LoadingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setLimit(LIMIT); recyclerView.setAdapter(recyclerViewAdapter); recyclerView.setLoadingObservable(offsetAndLimit -> EmulateResponseManager.getInstance().getEmulateResponse(offsetAndLimit.getOffset(), offsetAndLimit.getLimit())); } @Override public void onDestroyView() { recyclerView.onDestroy(); super.onDestroyView(); } }
AutoLoadingRecyclerView
and the standard RecyclerView
only in the addition of the setLimit, setLoadingObservable, onDestroy startLoading
. And in the box we have a self-loading list. In my opinion, it is very convenient, capacious and beautiful.AutoLoadingRecyclerView
is a more practical implementation of the idea, rather than a class, which is easily customized to any developer needs. Therefore, I will be very happy for your comments, suggestions, my AutoLoadingRecyclerView
vision and comments.Source: https://habr.com/ru/post/268991/
All Articles