Recyclerview_helper多功能封裝,讓你的應(yīng)用更加自如
前言
RecyclerView作為列表使用,在項目中的應(yīng)用場景實在是太普遍了。針對項目應(yīng)用,主要使用了RecyclerView的單或多類型Item,點擊/長按事件,ItemAnimator動畫效果以及上拉加載、下拉刷新。recyclerview_helper就是針對以上應(yīng)用場景進行的封裝與使用,避免在項目使用中重復(fù)的敲代碼以及依賴多個庫或者自定義實現(xiàn)等復(fù)雜方式。
介紹
如上所示,recyclerview_helper針對不同應(yīng)用場景封裝了不同的功能。
具體功能如下:
1.封裝了ViewHolder以及Adapter,避免每次都要重復(fù)寫創(chuàng)建ViewHolder以及重寫onCreateViewHolder,onBindViewHolder方法,支持單/多類型Item。
2.封裝了OnItemClickListener以及OnItemLongClickListener,不必每次寫接口,定義回調(diào)。僅支持對Item的點擊事件。
3.具有各種炫酷的動畫效果,功能裝飾者模式封裝Adapter添加初始化動畫效果 ,以及自定義ItemAnimator實現(xiàn)各種炫酷的Item增刪動畫效果。
4.支持添加頭尾布局,以及支持下拉刷新和上拉加載更多功能。同時支持自定義下拉刷新布局及動畫,以及上拉加載更多的布局,可實現(xiàn)各種炫酷效果,跟隨你的想象放飛。
使用
1.添加依賴
①.在項目的 build.gradle 文件中添加
- allprojects {
- repositories {
- ...
- maven { url 'https://jitpack.io' }
- }
- }
②.在 module 的 build.gradle 文件中添加依賴
- dependencies {
- compile 'com.github.LRH1993:recyclerview_helper:V1.0.3'
- }
2.單/多類型Item使用
單類型
- CommonAdapter<String> mAdapter = new CommonAdapter<String>(this, R.layout.item_common, mDatas) {
- @Override
- public void convert(BaseViewHolder holder, int position) {
- holder.setText(R.id.tv_content,mDatas.get(position));
- int number = new Random().nextInt(3);
- holder.setImageResource(R.id.iv_content,mImageRes[number]);
- }
- };
- mRecyclerView.setAdapter(mAdapter);
通過實現(xiàn)CommonAdapter,傳入context,布局以及數(shù)據(jù),然后實現(xiàn)convert方法,設(shè)置view的顯示數(shù)據(jù)就完成了。很簡潔方便。
多類型
- MultiItemTypeSupport<String> support = new MultiItemTypeSupport<String>() {
- @Override
- public int getLayoutId(int itemType) {
- if (itemType == TYPE_HEAD) {
- return R.layout.item_special;
- } else {
- return R.layout.item_common;
- }
- }
- @Override
- public int getItemViewType(int position, String s) {
- if (position%3==0&& position>0) {
- return TYPE_HEAD;
- } else {
- return TYPE_COMMON;
- }
- }
- };
- -----------------------------------------------------------------------------------------------------------------------------
- MultiItemCommonAdapter<String> mAdapter = new MultiItemCommonAdapter<String>(this, mDatas, support) {
- @Override
- public void convert(BaseViewHolder holder, int position) {
- if (position%3==0&& position>0) {
- holder.setImageResource(R.id.iv_head,R.drawable.multi_image);
- } else {
- holder.setText(R.id.tv_content,mDatas.get(position));
- int number = new Random().nextInt(3)+3;
- holder.setImageResource(R.id.iv_content,mImageRes[number]);
- }
- }
- };
- mRecyclerView.setAdapter(mAdapter);
和單類型的區(qū)別就是需要實現(xiàn)MultiItemTypeSupport,在MultiItemCommonAdapter對象中傳入實現(xiàn)的該對象,該對象完成兩個方法,功能是通過類型判別Item布局以及通過位置和數(shù)據(jù)判斷返回類型。通過這個對象,避免我們在Adapter中進行類別判斷的書寫。
該部分實現(xiàn)參考了鴻洋大神對RecyclerView的封裝。
3.事件監(jiān)聽使用
事件監(jiān)聽的使用就比較簡單了,和正常使用一樣。
- adapter.setOnItemClickListener(new CommonAdapter.OnItemClickListener() {
- @Override
- public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
- System.out.println("點擊");
- showMyDialog("響應(yīng)點擊事件");
- }
- });
- adapter.setOnItemLongClickListener(new CommonAdapter.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
- showMyDialog("響應(yīng)長按事件");
- return false;
- }
- });
4.動畫使用
gif錄制效果不太明顯,實際手機上看著效果還是不錯的。
- mRecyclerView.setItemAnimator(new LandingAnimator());
- ScaleInAnimationAdapter scaleAdapter = new ScaleInAnimationAdapter(adapter);
- scaleAdapter.setFirstOnly(false);
- scaleAdapter.setDuration(500);
- mRecyclerView.setAdapter(scaleAdapter);
動畫效果分兩種:
第一種:adapter初始化item的動畫效果
第二種:ItemAnimator定義的動畫效果
第一種動畫效果使用了裝飾者模式,需要再封裝一層,然后通過setAdapter添加進去。
第二種直接和平時使用一樣。
除此之外,ItemAnimator還有以下高級功能:
設(shè)置動畫時長
- mRecyclerView.getItemAnimator().setAddDuration(1000);
- mRecyclerView.getItemAnimator().setRemoveDuration(1000);
- mRecyclerView.getItemAnimator().setMoveDuration(1000);
- mRecyclerView.getItemAnimator().setChangeDuration(1000);
插值器
- SlideInLeftAnimator animator = new SlideInLeftAnimator();
- animator.setInterpolator(new OvershootInterpolator());
- mRecyclerView.setItemAnimator(animator);
也可以通過自定義AnimateViewHolder實現(xiàn)類,實現(xiàn)其他動畫效果。
Adapter也有一些高級功能:
動畫時長
- AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
- alphaAdapter.setDuration(1000);
- mRecyclerView.setAdapter(alphaAdapter);
插值器
- AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
- alphaAdapter.setInterpolator(new OvershootInterpolator());
- mRecyclerView.setAdapter(alphaAdapter);
是否僅顯示一次動畫效果
- AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
- scaleAdapter.setFirstOnly(false);
- recyclerView.setAdapter(alphaAdapter);
設(shè)置成true,則動畫效果只在第一次創(chuàng)建Item使用,設(shè)置成false,則每次上下滑動都帶動畫效果。
復(fù)雜情況下,可以設(shè)置成true。
復(fù)合動畫
- AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
- recyclerView.setAdapter(new ScaleInAnimationAdapter(alphaAdapter));
recyclerview_helper中已經(jīng)自定義了各種動畫效果,如果有好的實現(xiàn)動畫,可以通過自定義實現(xiàn)。
該部分實現(xiàn)參考了recyclerview-animators這個動畫庫。
5.添加頭/尾布局
自定義頭/尾布局,隨意添加。
- View headView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_head,null,false);
- View footView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_foot,null,false);
- mRecyclerView.addHeaderView(headView);
- mRecyclerView.addFooterView(footView);
幾行代碼搞定。
6.下拉刷新/上拉加載
布局設(shè)置
- <com.lvr.library.recyclerview.HRecyclerView
- app:loadMoreEnabled="true"
- app:loadMoreFooterLayout="@layout/layout_hrecyclerview_load_more_footer"
- app:refreshEnabled="true"
- app:refreshHeaderLayout="@layout/layout_hrecyclerview_refresh_header"
- android:id="@+id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
其中頭/尾布局需要自定義View實現(xiàn)。在例子中已經(jīng)分別實現(xiàn)了一種
如果想實現(xiàn)不同的加載圖片以及動畫效果,可以對比實現(xiàn)。
首先設(shè)置監(jiān)聽
- mRecyclerView.setOnRefreshListener(this);
- mRecyclerView.setOnLoadMoreListener(this);
實現(xiàn)回調(diào)方法
- @Override
- public void onLoadMore() {
- if(mLoadMoreFooterView.canLoadMore()){
- mLoadMoreFooterView.setStatus(LoadMoreFooterView.Status.LOADING);
- new Thread(new Runnable() {
- @Override
- public void run() {
- //假裝加載耗時數(shù)據(jù)
- SystemClock.sleep(1000);
- Message message = Message.obtain();
- message.what =count;
- count++;
- mHandler.sendMessage(message);
- }
- }).start();
- }
- }
- @Override
- public void onRefresh() {
- new Thread(new Runnable() {
- @Override
- public void run() {
- //假裝加載耗時數(shù)據(jù)
- SystemClock.sleep(1000);
- Message msg = Message.obtain();
- msg.what=0;
- mHandler.sendMessage(msg);
- }
- }).start();
- }
開啟刷新/關(guān)閉刷新
- mRecyclerView.setRefreshing(true);
根據(jù)不同情況設(shè)置不同加載狀態(tài):
- //正在加載
- mLoadMoreFooterView.setStatus(LoadMoreFooterView.Status.LOADING); //沒有更多數(shù)據(jù)mLoadMoreFooterView.setStatus(LoadMoreFooterView.Status.THE_END);//加載完畢mLoadMoreFooterView.setStatus(LoadMoreFooterView.Status.GONE);//出現(xiàn)錯誤mLoadMoreFooterView.setStatus(LoadMoreFooterView.Status.ERROR);
出現(xiàn)錯誤,還可以通過實現(xiàn)onRetry方法,實現(xiàn)再次加載。
以上兩部分效果參考了IRecyclerView實現(xiàn)。
原理
上面介紹了使用,那么下面就介紹下實現(xiàn)原理,如果需要自定義實現(xiàn),可以參考完成。
1.ViewHolder及Adapter封裝
針對ViewHolder的功能:實現(xiàn)View緩存,避免重復(fù)findViewById,進行封裝。針對Adapter在實際項目中重復(fù)書寫onCreateViewHolder,onBindViewHolder方法,進行封裝處理。
ViewHolder
- private SparseArray<View> viewArray;
- private View mItemView;
- /**
- * 構(gòu)造ViewHolder
- *
- * @param parent 父類容器
- * @param resId 布局資源文件id
- */
- public BaseViewHolder(Context context, ViewGroup parent, @LayoutRes int resId) {
- super(LayoutInflater.from(context).inflate(resId, parent, false));
- viewArray = new SparseArray<>();
- }
- /**
- * 獲取ItemView
- * @return ItemView
- */
- public View getItemView(){
- return this.itemView;
- }
- /**
- * 獲取布局中的View
- *
- * @param viewId view的Id
- * @param <T> View的類型
- * @return view
- */
- public <T extends View> T getView(@IdRes int viewId) {
- View view = viewArray.get(viewId);
- if (view == null) {
- view = itemView.findViewById(viewId);
- viewArray.put(viewId, view);
- } return (T) view;
- }
將View存放在集合類中,進行緩存,getView方法可以在Adapter中直接調(diào)用,避免每次創(chuàng)建不同類型的ViewHolder,一個BaseViewHolder搞定一切情況。
Adapter
基類CommonAdapter
- public abstract class CommonAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {
- protected Context mContext;
- protected int mLayoutId;
- protected List<T> mDatas;
- protected OnItemClickListener mOnItemClickListener;
- protected OnItemLongClickListener mOnItemLongClickListener;
- public CommonAdapter(Context context, int layoutId, List<T> datas)
- {
- mContext = context;
- mLayoutId = layoutId;
- mDatas = datas;
- }
- @Override
- public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- final BaseViewHolder viewHolder = new BaseViewHolder(mContext,parent,mLayoutId);
- return viewHolder;
- }
- @Override
- public void onBindViewHolder(final BaseViewHolder holder, final int position) {
- convert(holder, position);
- } public abstract void convert(BaseViewHolder holder, int position);
- @Override
- public int getItemCount() {
- return mDatas.size();
- }
- .........
封裝實現(xiàn)onCreateViewHolder方法,并把convert方法抽象出去,實現(xiàn)數(shù)據(jù)綁定工作。使得結(jié)構(gòu)簡單。
針對多類型情況,需要判斷類型,設(shè)置不同布局,所以需要個幫助類:
- public interface MultiItemTypeSupport<T>{
- int getLayoutId(int itemType);
- int getItemViewType(int position, T t);
- }
剩下來基礎(chǔ)CommonAdapter實現(xiàn)MultiItemCommonAdapter來應(yīng)對多類型Item。
- public abstract class MultiItemCommonAdapter<T> extends CommonAdapter<T>{
- protected MultiItemTypeSupport<T> mMultiItemTypeSupport;
- public MultiItemCommonAdapter(Context context, List<T> datas,
- MultiItemTypeSupport<T> multiItemTypeSupport)
- { super(context, -1, datas);
- mMultiItemTypeSupport = multiItemTypeSupport;
- }
- @Override
- public int getItemViewType(int position)
- {
- return mMultiItemTypeSupport.getItemViewType(position, mDatas.get(position));
- }
- @Override
- public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
- {
- int layoutId = mMultiItemTypeSupport.getLayoutId(viewType);
- BaseViewHolder holder = new BaseViewHolder(mContext, parent, layoutId);
- return holder;
- }
- }
整個實現(xiàn)很簡單,但是效果不錯。關(guān)于事件點擊監(jiān)聽,很簡單,就不介紹了。
2.動畫效果實現(xiàn)
先介紹個簡單的,Adapter的動畫效果:
- @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
- mAdapter.onBindViewHolder(holder, position);
- int adapterPosition = holder.getAdapterPosition();
- if (!isFirstOnly || adapterPosition > mLastPosition) {
- for (Animator anim : getAnimators(holder.itemView)) {
- anim.setDuration(mDuration).start();
- anim.setInterpolator(mInterpolator);
- }
- mLastPosition = adapterPosition;
- } else {
- ViewHelper.clear(holder.itemView);
- }
- }
其實就是實現(xiàn)一個包裝類AnimationAdapter,在其中onBindViewHolder方法中添加了動畫效果。
下面繼續(xù)介紹ItemAnimator的實現(xiàn)。關(guān)于這部分實現(xiàn),實際代碼比較長。就簡要介紹下實現(xiàn)主要流程,具體實現(xiàn)可以看下這篇文章:recyclerview-animators,讓你的RecyclerView與眾不同。
其實RecyclerView中已經(jīng)為我們默認提供了Item動畫效果,就是通過這個類:DefaultItemAnimator,而這個類又從哪來?
- public class DefaultItemAnimator extends SimpleItemAnimator
搞清楚源頭,那么我們也可以 比照著進行實現(xiàn)動畫效果,所以通過繼承SimpleItemAnimator實現(xiàn)自定義動畫類。
主要針對對animateRemove, animateAdd, animateMove, animateChange 這4個方法的實現(xiàn),增加數(shù)據(jù)增、刪、動、改的動畫效果。
3.頭布局/尾布局實現(xiàn)
其實頭布局,尾布局的添加和上面實現(xiàn)Adapter動畫效果的方式一致,都是通過裝飾者模式。
因為下面要實現(xiàn)上拉加載/下拉刷新,所以這兩部分布局也像添加.頭布局/尾布局一樣實現(xiàn)。
先定義一個WrapperAdapter。
- @Override
- public int getItemCount() {
- return mAdapter.getItemCount() + 4;
- }
- @Override
- public int getItemViewType(int position) {
- if (position == 0) {
- return REFRESH_HEADER;
- } else if (position == 1) {
- return HEADER;
- } else if (1 < position && position < mAdapter.getItemCount() + 2) { return mAdapter.getItemViewType(position - 2);
- } else if (position == mAdapter.getItemCount() + 2) { return FOOTER;
- } else if (position == mAdapter.getItemCount() + 3) { return LOAD_MORE_FOOTER;
- }
- throw new IllegalArgumentException("Wrong type! Position = " + position);
- }
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == REFRESH_HEADER) { return new RefreshHeaderContainerViewHolder(mRefreshHeaderContainer);
- } else if (viewType == HEADER) {
- return new HeaderContainerViewHolder(mHeaderContainer);
- } else if (viewType == FOOTER) {
- return new FooterContainerViewHolder(mFooterContainer);
- } else if (viewType == LOAD_MORE_FOOTER) {
- return new LoadMoreFooterContainerViewHolder(mLoadMoreFooterContainer);
- } else {
- return mAdapter.onCreateViewHolder(parent, viewType);
- }
- }
- @Override
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (1 < position && position < mAdapter.getItemCount() + 2) {
- mAdapter.onBindViewHolder(holder, position - 2);
- }
- }
主要是針對上拉加載/下拉刷新,頭布局/尾布局進行特殊處理。
其次,由于上面四部分都要獨占一行,在LinearLayoutManager下沒問題,但是在StaggeredGridLayoutManager以及GridLayoutManager要特殊處理。
GridLayoutManager
- @Overridepublic void onAttachedToRecyclerView(final RecyclerView recyclerView) {
- RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
- if (layoutManager instanceof GridLayoutManager) {
- final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
- final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
- gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
- @Override
- public int getSpanSize(int position) {
- WrapperAdapter wrapperAdapter = (WrapperAdapter) recyclerView.getAdapter();
- if (isFullSpanType(wrapperAdapter.getItemViewType(position))) {
- return gridLayoutManager.getSpanCount();
- } else if (spanSizeLookup != null) {
- return spanSizeLookup.getSpanSize(position - 2);
- }
- return 1;
- }
- });
- }
- }
StaggeredGridLayoutManager
- @Override
- public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
- super.onViewAttachedToWindow(holder);
- int position = holder.getAdapterPosition();
- int type = getItemViewType(position);
- if (isFullSpanType(type)) {
- ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
- if (layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
- StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) layoutParams;
- lp.setFullSpan(true);
- }
- }
- }
最后可以放心的添加了,通過自定義RecyclerView,重寫setAdapter()方法
- @Override
- public void setAdapter(Adapter adapter) {
- mOriginAdapter =adapter;
- ensureRefreshHeaderContainer();
- ensureHeaderViewContainer();
- ensureFooterViewContainer();
- ensureLoadMoreFooterContainer();
- super.setAdapter(new WrapperAdapter(adapter, mRefreshHeaderContainer, mHeaderViewContainer, mFooterViewContainer, mLoadMoreFooterContainer));
- }
這樣就加入了頭/尾布局以及上拉加載/下拉刷新布局。
總結(jié)
文章篇幅所限,“上拉加載/下拉刷新”等其他操作可點擊左下角“閱讀原文”查看。目前由于多類型+頭尾以及上拉刷新/下拉加載,判斷類型過多,在頭布局會出現(xiàn)卡頓,所以在使用多類型的情況下,不建議加入頭尾布局,可以考慮重寫setAdapter方法,去掉不需要的布局。
從效果,到使用,最后到原理,加深了對RecyclerView的理解,recyclerview_helper可以應(yīng)對一般的使用場景,不過如有特殊應(yīng)用場景,也可進行比對自定義實現(xiàn)。
Github地址:https://github.com/LRH1993/recyclerview_helper,給個star支持下。