RecyclerView点滴

作者:编程    发布时间:2020-02-11 05:31     浏览次数 :

[返回]

项目中,我们用得最多的元素就是列表了,在Android 中,实现列表用原生的RecyclerView就能满足需求,关于RecyclerView 的基础使用这里不做过多的介绍,网上有太多的博文介绍了。本篇文章将介绍自己封装的一个Adapter,帮你快速高效的添加一个列表(包括单 Item 列表和多item列表)。

一、开发Android的程序员都知道LIstView,2014年Google Android L 版发布,新的控件RecyclerView取代之前ListView,为什么Google要取代它,通过自己实践使用,发现它比ListView有以下几大有点:

图片 1cover.jpg

理念

1, 构造一个通用的Adapter模版,避免每添加一个列表就要写一个Adapter,避免写Adapter中的大量重复代码。2,通过组装的方式来构建Adapter,将每一种(ViewType不同的)Item抽象成一个单独组件,Adapter 就是一个壳,我们只需要向Adapter中添加Item就行,这样做的好处就是减少耦合,去掉一种item 或者添加一种item对于列表是没有任何影响的。3,高内聚,低耦合,扩展方便。

1.提供了一种插拔式的体验,高度的解耦,异常的灵活使用

上一篇 初识RecyclerView,是RecylerView的入门篇,主要讲解了什么是RecylerView,RecylerView的优势以及三种布局管理器的区别,我们对RecyclerVIew有了初步了解

思路

为每一种 viewType 定义一个Cell,Cell就是上面提到的独立组件,它负责创建ViewHolder,数据绑定和逻辑处理。它有2个重要的方法,onCreateViewHolder 负责创建ViewHolder,onBindViewHolder负责数据绑定,这两个方法的定义和生命周期同Adapter种的2个方法一样,事实上,Adapter 中的onCreateViewHolder和onBindViewHolder 最终调用的是Cell中的方法。

** 一种 ViewType 对应一个Cell**看一个示例:

图片 2cell_simple.png

如上图:以豆瓣APP的首页为例,文章包含图片和视频的两个Item 的布局是不同的,因此,可以添加两个Cell(ImageCell和VideoCell)来分别处理这两种Item。

** 有了Cell之后,要向列表添加添加Header和Footer 的需求就很简单了,我们直接添加一个HeaderCell和FooterCell 就可以了,也不用更改Adapter代码,是不是很方便。此外,还可以用Cell实现列表LoadMore状态、Loadding状态、Empty状态、Error状态 View的显示。**

2.显示的样式更丰富包括水平,竖直,Grid,瀑布显示方式

本篇是Recylerview的进阶篇,我将一步步带领大家为RecylerView添加HeaderView, FooterView, EmptyView, 以及完成对GloriousRecyclerView的封装,使我们的开发更加便捷

包结构

图片 3rv_pakage.png

介绍:1,base:base包下面为Lib的主要代码,一个Cell接口和三个抽象类,分别抽取了Adapter,ViewHolder,Cell的公共逻辑。2,cell:cell包下面有4个cell,分别显示列表的LoadMore,Loading,Empty,Error状态。3,fragment:有一个Fragment抽象类,定义了一个UI模版(不需要额外添加布局文件),要实现列表的界面只需要继承AbsBaseFragment,实现几个方法添加数据就OK。

3.可以通过ItemDecoration自定义Item间的间隔

我们使用ListView的时候知道,要为ListView添加HeaderView是非常方便的,我们只需要调用ListView的addHeaderView方法即可。

具体代码

4.可以通过ItemAnimator自定义Item增、删动画(也可设置默认动画)

于是,果断翻看RecyclerView的源码

1,Cell 接口定义
/** * Created by zhouwei on 17/1/19. */public interface Cell { /** * 回收资源 * */ public void releaseResource(); /** * 获取viewType * @return */ public int getItemType(); /** * 创建ViewHolder * @param parent * @param viewType * @return */ public RVBaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType); /** * 数据绑定 * @param holder * @param position */ public void onBindViewHolder(RVBaseViewHolder holder, int position);}

定义了4个方法,除了回收资源的方法releaseResource(),其它三个和Adapter中的一样。

5.代码内聚不需要手动创建ViewHolder

图片 4RecyclerView_SourceCode_1.png图片 5RecyclerView_SourceCode_2.png

2,RVBaseCell
/** * Created by zhouwei on 17/1/19. */public abstract class RVBaseCell<T> implements Cell { public RVBaseCell{ mData = t; } public T mData; @Override public void releaseResource() { // do nothing // 如果有需要回收的资源,子类自己实现 }}

抽象类,接受一个范型T(Cell接受的数据实体),实现了releaseResource方法,但什么事也没干,因为有很多简单的Cell没有资源回收,就不需要实现。如果子类Cell 有资源回收,重写这个方法就可以了。

二 、使用RecyclerView先了解他们的用处

遗憾的是,我们并没有找到添加HeaderView的方法,退而求其次

3, RVBaseViewHolder
/** * Created by zhouwei on 17/1/19. */public class RVBaseViewHolder extends RecyclerView.ViewHolder{ private SparseArray<View> views; private View mItemView; public RVBaseViewHolder(View itemView) { super; views = new SparseArray<>(); mItemView = itemView; } /** * 获取ItemView * @return */ public View getItemView() { return mItemView; } public View getView(int resId) { return retrieveView; } public TextView getTextView(int resId){ return retrieveView; } public ImageView getImageView(int resId){ return retrieveView; } public Button getButton(int resId){ return retrieveView; } @SuppressWarnings("unchecked") protected <V extends View> V retrieveView(int viewId){ View view = views.get; if(view == null){ view = mItemView.findViewById; views.put(viewId,view); } return  view; } public void setText(int resId,CharSequence text){ getTextView.setText; } public void setText(int resId,int strId){ getTextView.setText; }}

以前写Adapter的时候,每一种viewType 都对应了一个ViewHolder,其中有大量的findViewById绑定视图,有了RVBaseViewHolder,再也不需要定义ViewHolder了,通过id获取View就行,View用SparseArray 保存进行了复用,避免每一次都find。

1.RecyclerView.LayoutManager--------负责item显示方式

我们找到在RecyclerView的内部静态抽象类LayoutManager中有addView()方法

4,RVBaseAdapter
/** * Created by zhouwei on 17/1/19. */public abstract class RVBaseAdapter<C extends RVBaseCell> extends RecyclerView.Adapter<RVBaseViewHolder>{ public static final String TAG = "RVBaseAdapter"; protected List<C> mData; public RVBaseAdapter(){ mData = new ArrayList<>(); } public void setData(List<C> data) { addAll; notifyDataSetChanged(); } public List<C> getData() { return mData; } @Override public RVBaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { for(int i=0;i<getItemCount{ if(viewType == mData.get.getItemType{ return mData.get.onCreateViewHolder(parent,viewType); } } throw new RuntimeException("wrong viewType"); } @Override public void onBindViewHolder(RVBaseViewHolder holder, int position) { mData.get.onBindViewHolder(holder,position); } @Override public void onViewDetachedFromWindow(RVBaseViewHolder holder) { super.onViewDetachedFromWindow; Log.e(TAG,"onViewDetachedFromWindow invoke..."); //释放资源 int position = holder.getAdapterPosition(); //越界检查 if(position<0 || position>=mData.size{ return; } mData.get.releaseResource(); } @Override public int getItemCount() { return mData == null ? 0:mData.size(); } @Override public int getItemViewType(int position) { return mData.get.getItemType(); } /** * add one cell * @param cell */ public void add{ mData.add; int index = mData.indexOf; notifyItemChanged; } public void add(int index,C cell){ mData.add(index,cell); notifyItemChanged; } /** * remove a cell * @param cell */ public void remove{ int indexOfCell = mData.indexOf; remove(indexOfCell); } public void remove(int index){ mData.remove; notifyItemRemoved; } /** * * @param start * @param count */ public void remove(int start,int count){ if((start +count) > mData.size{ return; } int size = getItemCount(); for(int i =start;i<size;i++){ mData.remove; } notifyItemRangeRemoved(start,count); } /** * add a cell list * @param cells */ public void addAll(List<C> cells){ if(cells == null || cells.size{ return; } Log.e("zhouwei","addAll cell size:"+cells.size; mData.addAll; notifyItemRangeChanged(mData.size()-cells.size(),mData.size; } public void addAll(int index,List<C> cells){ if(cells == null || cells.size{ return; } mData.addAll(index,cells); notifyItemRangeChanged(index,index+cells.size; } public void clear(){ mData.clear(); notifyDataSetChanged(); } /** * 如果子类需要在onBindViewHolder 回调的时候做的操作可以在这个方法里做 * @param holder * @param position */ protected abstract void onViewHolderBound(RVBaseViewHolder holder, int position);}

RVBaseAdapter 继承 RecyclerView.Adapter,接受的是RVBaseCell类型,保存了一个Cell 列表。其中还有有添加、移除,清空、更新数据的方法。注意其中几个方法:1,getItemViewType: 调用的是对应position Cell 的getItemViewType 方法。

2,onCreateViewHolder:调用Cell 的onCreateViewHolder 创建ViewHolder。

3,onBindViewHolder: 调用对应Cell的onBindViewHolder 方法绑定数据

4,onViewDetachedFromWindow: 资源回收

2.RecyclerView.Adapter---------------处理数据集合并负责绑定视图

图片 6RecyclerView_SourceCode_3.png

5,RVSimpleAdapter
/** * Created by zhouwei on 17/1/23. */public class RVSimpleAdapter extends RVBaseAdapter{ public static final int ERROR_TYPE = Integer.MAX_VALUE -1; public static final int EMPTY_TYPE = Integer.MAX_VALUE -2; public static final int LOADING_TYPE = Integer.MAX_VALUE -3; public static final int LOAD_MORE_TYPE = Integer.MAX_VALUE -4; private EmptyCell mEmptyCell; private ErrorCell mErrorCell; private LoadingCell mLoadingCell; private LoadMoreCell mLoadMoreCell; //LoadMore 是否已显示 private boolean mIsShowLoadMore = false; public RVSimpleAdapter(){ mEmptyCell = new EmptyCell; mErrorCell = new ErrorCell; mLoadingCell = new LoadingCell; mLoadMoreCell = new LoadMoreCell; } @Override protected void onViewHolderBound(RVBaseViewHolder holder, int position) { } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); //处理GridView 布局 if(manager instanceof GridLayoutManager){ final GridLayoutManager gridLayoutManager = (GridLayoutManager) manager; gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { int viewType = getItemViewType; return (viewType == ERROR_TYPE|| viewType == EMPTY_TYPE || viewType == LOADING_TYPE ||viewType == LOAD_MORE_TYPE) ? gridLayoutManager.getSpanCount; } } @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { // 处理StaggeredGridLayoutManager 显示这个Span int position = holder.getAdapterPosition(); int viewType = getItemViewType; if(isStaggeredGridLayout{ if(viewType == ERROR_TYPE|| viewType == EMPTY_TYPE || viewType == LOADING_TYPE ||viewType == LOAD_MORE_TYPE){ StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams(); //设置显示整个span params.setFullSpan; } } } private boolean isStaggeredGridLayout(RecyclerView.ViewHolder holder) { ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); if (layoutParams != null && layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) { return true; } return false; } /** * 显示LoadingView * <p>请求数据时调用,数据请求完毕时调用{@link #hideLoading }</p> * @see #showLoadingKeepCount */ public void showLoading(){ clear(); add(mLoadingCell); } public void showLoading(View loadingView){ if(loadingView == null){ showLoading(); } clear(); mLoadingCell.setLoadingView(loadingView); add(mLoadingCell); } /** * 显示LoadingView * <p>列表显示LoadingView并保留keepCount个Item</p> * @param keepCount 保留的条目数量 */ public void showLoadingKeepCount(int keepCount){ if(keepCount < 0 || keepCount>mData.size{ return; } remove(keepCount,mData.size() - keepCount); if(mData.contains(mLoadingCell)){ mData.remove(mLoadingCell); } add(mLoadingCell); } /** * hide Loading view */ public void hideLoading(){ if(mData.contains(mLoadingCell)){ mData.remove(mLoadingCell); } } /** * 显示错误提示 * <p>当网络请求发生错误,需要在界面给出错误提示时,调用{@link #showError}</p> * @see #showErrorKeepCount */ public void showError(){ clear(); add(mErrorCell); } /** * 显示错误提示 * <p>当网络请求发生错误,需要在界面给出错误提示时,调用{@link #showErrorKeepCount},并保留keepCount 条Item</p> * @param keepCount 保留Item数量 */ public void showErrorKeepCount(int keepCount){ if(keepCount < 0 || keepCount>mData.size{ return; } remove(keepCount,mData.size() - keepCount); if(mData.contains(mErrorCell)){ mData.remove(mErrorCell); } add(mErrorCell); } /** * 隐藏错误提示 */ public void hideErorr(){ if(mData.contains(mErrorCell)){ remove(mErrorCell); } } /** * 显示LoadMoreView * <p>当列表滑动到底部时,调用{@link #showLoadMore()} 提示加载更多,加载完数据,调用{@link #hideLoadMore()} * 隐藏LoadMoreView,显示列表数据。</p> * */ public void showLoadMore(){ if(mData.contains(mLoadMoreCell)){ return; } add(mLoadMoreCell); mIsShowLoadMore = true; } /** * 隐藏LoadMoreView * <p>调用{@link #showLoadMore()}之后,加载数据完成,调用{@link #hideLoadMore()}隐藏LoadMoreView</p> */ public void hideLoadMore(){ if(mData.contains(mLoadMoreCell)){ remove(mLoadMoreCell); mIsShowLoadMore = false; } } /** * LoadMore View 是否已经显示 * @return */ public boolean isShowLoadMore() { return mIsShowLoadMore; } /** * * @param keepCount */ public void showEmptyKeepCount(int keepCount){ if(keepCount < 0 || keepCount>mData.size{ return; } remove(keepCount,mData.size() - keepCount); if(mData.contains(mEmptyCell)){ mData.remove(mEmptyCell); } add(mEmptyCell); } /** * 显示空view * <p>当页面没有数据的时候,调用{@link #showEmpty()}显示空View,给用户提示</p> */ public void showEmpty(){ clear(); add(mEmptyCell); } /** * 隐藏空View */ public void hideEmpty(){ if(mData.contains(mEmptyCell)){ remove(mEmptyCell); } }}

RVSimpleAdapter 是RVBaseAdapter 的默认实现类,添加了显示LoadMore View、Loading View 、Empty View、ErrorView 的功能。

3.ViewHolder------------------------持有item所有的用于绑定数据的View

由上一篇我们已经知道,RecyclerView已经实现了三种布局管理器,这里,我们就用最简单的LinearLayoutManager来尝试下添加HeaderView

6,AbsBaseFragment
/** * Created by zhouwei on 17/2/3. */public abstract class AbsBaseFragment<T> extends Fragment { protected RecyclerView mRecyclerView; protected RVSimpleAdapter mBaseAdapter; private FrameLayout mToolbarContainer; protected SwipeRefreshLayout mSwipeRefreshLayout; /** * RecyclerView 最后可见Item在Adapter中的位置 */ private int mLastVisiblePosition = -1; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.base_fragment_layout,null); return view; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.base_refresh_layout); mToolbarContainer = (FrameLayout) view.findViewById(R.id.toolbar_container); mRecyclerView = (RecyclerView) view.findViewById(R.id.base_fragment_rv); mRecyclerView.setLayoutManager(initLayoutManger; mBaseAdapter = initAdapter(); mRecyclerView.setAdapter(mBaseAdapter); mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { setRefreshing; onPullRefresh; mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if(layoutManager instanceof LinearLayoutManager){ mLastVisiblePosition = ((LinearLayoutManager)layoutManager).findLastVisibleItemPosition(); }else if(layoutManager instanceof GridLayoutManager){ mLastVisiblePosition = ((GridLayoutManager)layoutManager).findLastVisibleItemPosition(); }else if(layoutManager instanceof StaggeredGridLayoutManager){ StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager; int []lastPositions = new int[staggeredGridLayoutManager.getSpanCount()]; staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions); mLastVisiblePosition = findMax(lastPositions); } } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { View firstView = recyclerView.getChildAt; int top = firstView.getTop(); int topEdge = recyclerView.getPaddingTop(); //判断RecyclerView 的ItemView是否满屏,如果不满一屏,上拉不会触发加载更多 boolean isFullScreen = top < topEdge; RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); int itemCount = manager.getItemCount(); //因为LoadMore View 是Adapter的一个Item,显示LoadMore 的时候,Item数量+1了,导致 mLastVisibalePosition == itemCount-1 // 判断两次都成立,因此必须加一个判断条件 !mBaseAdapter.isShowLoadMore() if(newState == RecyclerView.SCROLL_STATE_IDLE && mLastVisiblePosition == itemCount-1 && isFullScreen && !mBaseAdapter.isShowLoadMore{ //最后一个Item了 mBaseAdapter.showLoadMore(); onLoadMore; View toolbarView = addToolbar(); if(toolbarView!=null && mToolbarContainer!=null ){ mToolbarContainer.addView(toolbarView); } onRecyclerViewInitialized(); } /** * hide load more progress */ public void hideLoadMore(){ if(mBaseAdapter!=null){ mBaseAdapter.hideLoadMore(); } } /** * 获取组数最大值 * @param lastPositions * @return */ private int findMax(int[] lastPositions) { int max = lastPositions[0]; for (int value : lastPositions) { if (value > max) { max = value; } } return max; } /** * 设置刷新进度条的颜色 * see{@link SwipeRefreshLayout#setColorSchemeResources} * @param colorResIds */ public void setColorSchemeResources(@ColorRes int... colorResIds){ if(mSwipeRefreshLayout!=null){ mSwipeRefreshLayout.setColorSchemeResources(colorResIds); } } /** * 设置刷新进度条的颜色 * see{@link SwipeRefreshLayout#setColorSchemeColors} * @param colors */ public void setColorSchemeColors(int... colors){ if(mSwipeRefreshLayout!=null){ mSwipeRefreshLayout.setColorSchemeColors; } } /** * 设置刷新进度条背景色 * see{@link SwipeRefreshLayout#setProgressBackgroundColorSchemeResource} } * @param colorRes */ public void setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) { if(mSwipeRefreshLayout!=null){ mSwipeRefreshLayout.setProgressBackgroundColorSchemeResource; } } /** * 设置刷新进度条背景色 * see{@link SwipeRefreshLayout#setProgressBackgroundColorSchemeColor} * @param color */ public void setProgressBackgroundColorSchemeColor(@ColorInt int color) { if(mSwipeRefreshLayout!=null){ mSwipeRefreshLayout.setProgressBackgroundColorSchemeColor; } } /** * Notify the widget that refresh state has changed. Do not call this when * refresh is triggered by a swipe gesture. * * @param refreshing Whether or not the view should show refresh progress. */ public void setRefreshing(boolean refreshing){ if(mSwipeRefreshLayout== null){ return; } mSwipeRefreshLayout.setRefreshing(refreshing); } /** * 子类可以自己指定Adapter,如果不指定默认RVSimpleAdapter * @return */ protected RVSimpleAdapter initAdapter(){ return new RVSimpleAdapter(); } /** * 子类自己指定RecyclerView的LayoutManager,如果不指定,默认为LinearLayoutManager,VERTICAL 方向 * @return */ protected RecyclerView.LayoutManager initLayoutManger(){ LinearLayoutManager manager = new LinearLayoutManager(getContext; manager.setOrientation(LinearLayoutManager.VERTICAL); return manager; } /** * 添加TitleBar * @param */ public View addToolbar(){ //如果需要Toolbar,子类返回Toolbar View return null; } /** *RecyclerView 初始化完毕,可以在这个方法里绑定数据 */ public abstract void onRecyclerViewInitialized(); /** * 下拉刷新 */ public abstract void onPullRefresh(); /** * 上拉加载更多 */ public abstract void onLoadMore(); /** * 根据实体生成对应的Cell * @param list 实体列表 * @return cell列表 */ protected abstract List<Cell> getCells(List<T> list);}

AbsBaseFragment,实现了上拉加载和下拉刷新功能,添加Toolbar等,上拉加载可以自定义View,下拉刷新用的是Google的SwipeRefreshLayout。要添加一个列表界面,只需要继承AbsBaseFragment,实现几个抽象方法添加Cell就行了,非常方便。

4.ItemDecoration---------------------负责绘制Item附近的分割线

mLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);mRecyclerView.setLayoutManager(mLayoutManager);mLayoutManager.addView(LayoutInflater.from.inflate(R .layout.layout_header, null, false), 0);

用法示例:

5.ItemAnimator-----------------------为Item的一般操作添加动画效果

不幸的是,在运行时爆出空指针异常

添加一个多Item的列表:

1,创建一个Fragment继承AbsBaseFragment,实现几个方法。

/** * Created by zhouwei on 17/2/3. */public class HomePageFragment extends AbsBaseFragment<Entry> { @Override public void onRecyclerViewInitialized() { //初始化View和数据加载 } @Override public void onPullRefresh() { //下拉刷新回调 } @Override public void onLoadMore() { //上拉加载回调 } protected List<Cell> getCells(List<Entry> entries){ //根据实体生成Cell return null; }}

实现上面几个抽象方法,实际上只实现onRecyclerViewInitialized和getCells两个方法就可以实现列表,其它两个方法是下拉刷新和上拉加载的。

2,创建Cell类

/** * Created by zhouwei on 17/2/7. */public class BannerCell extends RVBaseCell<List<String>> { public static final int TYPE = 2; private ConvenientBanner mConvenientBanner; public BannerCell(List<String> strings) { super; } @Override public int getItemType() { return TYPE; } @Override public RVBaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new RVBaseViewHolder(LayoutInflater.from(parent.getContext.inflate(R.layout.grid_cell_layoout,null)); } @Override public void onBindViewHolder(RVBaseViewHolder holder, int position) { mConvenientBanner = (ConvenientBanner) holder.getView(R.id.banner); mConvenientBanner.setPages(new CBViewHolderCreator<NetworkImageHolderView>() { @Override public NetworkImageHolderView createHolder() { return new NetworkImageHolderView(); } }, mData); mConvenientBanner.startTurning; } @Override public void releaseResource() { if(mConvenientBanner!=null){ mConvenientBanner.stopTurning(); } } public static class NetworkImageHolderView implements CBPageAdapter.Holder<String>{ private ImageView imageView; @Override public View createView(Context context) { imageView = new ImageView; imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); return imageView; } @Override public void UpdateUI(Context context, int position, String data) { ImageLoader.getInstance().displayImage(data,imageView); } }}

/** * Created by zhouwei on 17/1/19. */public class ImageCell extends RVBaseCell<Entry> { public static final int TYPE = 1; public ImageCell(Entry entry) { super; } @Override public int getItemType() { return TYPE; } @Override public RVBaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new RVBaseViewHolder(LayoutInflater.from(parent.getContext.inflate(R.layout.image_cell_layout,null)); } @Override public void onBindViewHolder(RVBaseViewHolder holder, int position) { Picasso.with(holder.getItemView().getContext.load(mData.imageUrl).into(holder.getImageView(R.id.image)); }}

/** * Created by zhouwei on 17/1/19. */public class TextCell extends RVBaseCell<Entry> { public static final int TYPE = 0; public TextCell(Entry entry) { super; } @Override public int getItemType() { return TYPE; } @Override public RVBaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new RVBaseViewHolder(LayoutInflater.from(parent.getContext.inflate(R.layout.text_cell_layout,null)); } @Override public void onBindViewHolder(RVBaseViewHolder holder, int position) { holder.setText(R.id.text_content,mData.content); }}

上面创建了3个Cell,也就是这个列表包含了3种不同类型的Item。

注意:一个列表内,每个Cell 的TYPE要不相同,也就是getItemType方法的返回值要不同。

3,onRecyclerViewInitialized ,做初始化和加载数据

@Override public void onRecyclerViewInitialized() { //初始化View和数据加载 //设置刷新进度条颜色 setColorSchemeResources(R.color.colorAccent); loadData(); } /** * 模拟从服务器取数据 */ private void loadData(){ View loadingView = LayoutInflater.from(getContext.inflate(R.layout.manu_loading_layout,null); mBaseAdapter.showLoading(loadingView); mRecyclerView.postDelayed(new Runnable() { @Override public void run() { mBaseAdapter.hideLoading(); mBaseAdapter.addAll(getCells(mockData; } },2000); }

4,实现getCells方法,生成Cell

 protected List<Cell> getCells(List<Entry> entries){ //根据实体生成Cell List<Cell> cells = new ArrayList<>(); cells.add(new BannerCell(Arrays.asList(DataMocker.images))); for (int i=0;i<entries.size{ Entry entry = entries.get; if(entry.type == Entry.TYPE_IMAGE){ cells.add(new ImageCell; }else{ cells.add(new TextCell; } } return cells; }

上面根据实体生成不同的Cell。有三种Cell,BannerCell,ImageCell和TextCell。

以上4个步骤就能实现一个界面复杂包含多做Item的列表了效果图如下:

图片 7adapter_cell.gif

HomePageFragment 的完整代码如下:

/** * Created by zhouwei on 17/2/3. */public class HomePageFragment extends AbsBaseFragment<Entry> { @Override public void onRecyclerViewInitialized() { //初始化View和数据加载 //设置刷新进度条颜色 setColorSchemeResources(R.color.colorAccent); loadData(); } @Override public void onPullRefresh() { //下拉刷新回调 mRecyclerView.postDelayed(new Runnable() { @Override public void run() { setRefreshing; } },2000); } @Override public void onLoadMore() { //上拉加载回调 loadMore(); } private void loadMore(){ mRecyclerView.postDelayed(new Runnable() { @Override public void run() { hideLoadMore(); mBaseAdapter.addAll(getCells(mockMoreData; } },10000); } protected List<Cell> getCells(List<Entry> entries){ //根据实体生成Cell List<Cell> cells = new ArrayList<>(); cells.add(new BannerCell(Arrays.asList(DataMocker.images))); for (int i=0;i<entries.size{ Entry entry = entries.get; if(entry.type == Entry.TYPE_IMAGE){ cells.add(new ImageCell; }else{ cells.add(new TextCell; } } return cells; } @Override public View addToolbar() { View toolbar = LayoutInflater.from(getContext.inflate(R.layout.title_bar_layout,null); return toolbar; } /** * 模拟从服务器取数据 */ private void loadData(){ View loadingView = LayoutInflater.from(getContext.inflate(R.layout.manu_loading_layout,null); mBaseAdapter.showLoading(loadingView); mRecyclerView.postDelayed(new Runnable() { @Override public void run() { mBaseAdapter.hideLoading(); mBaseAdapter.addAll(getCells(mockData; } },2000); }}

LayoutManager主要作用是,测量和摆放RecyclerView中itemView,以及当itemView对用户不可见时循环复用处理。 通过设置Layout Manager的属性,可以实现水平滚动、垂直滚动、Gird,瀑布显示

图片 8RecyclerView_Header_Error_1.png

Grid 列表和瀑布流列表:

上面演示了添加多Item type 的列表,添加单Item的列表也是一样的,只不过只有一个Cell而已。添加Grid 列表和瀑布流列表差不多的,只是RecylerView 的LayoutManager不同而已。

瀑布流列表示例:

/** * Created by zhouwei on 17/2/4. */public class DetailFragment extends AbsBaseFragment<DetailEntry> { @Override public void onRecyclerViewInitialized() { mBaseAdapter.setData(getCells(mockStaggerData; } @Override public void onPullRefresh() { } @Override public void onLoadMore() { } @Override protected RecyclerView.LayoutManager initLayoutManger() { StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL); return layoutManager; } @Override protected List<Cell> getCells(List<DetailEntry> list) { List<Cell> cells = new ArrayList<>(); for (int i=0;i<list.size{ cells.add(new DetailCell(list.get; } return cells; } }

只需要重写initLayoutManager这个方法,返回一个瀑布流的LayoutMannger就可以了。效果如下:

图片 9stagger_adapter_cell.gif

mRecyclerView.setLayoutManager(new LinearLayoutManager(this));设置横向布局

查看RecyclerView 7075行,发现holder为空

其它演示示例:LoadMore View 、Loading View 、Error View ,Empty View

1,显示LoadMore View提供了默认的Loading View,调用代码如下:

 mBaseAdapter.showLoadMore();

如果不想用默认的LoadMore View,当然也可以自定义LoadMore View,Adapter 提供方法:

mBaseAdapter.showLoadMore(loadMoreView);

像上面这样提供一个LoadMore View 的布局,还有一个重载的方法,可以指定显示的高度:

 mBaseAdapter.showLoadMore(loadMoreView,100);

如果是继承的AbsBaseFragment 创建列表,实现customLoadMoreView方法就ok了:

 @Override protected View customLoadMoreView() { View loadMoreView = LayoutInflater.from(getContext.inflate(R.layout.custeom_load_more_layout,null); return loadMoreView; }

隐藏LoadMore View 调用如下代码:

 if(mBaseAdapter!=null){ mBaseAdapter.hideLoadMore(); }

效果图看上面演示的瀑布流效果图。

**2,显示loading View **提供了默认的Loading View,调用代码如下:

mBaseAdapter.showLoading();

当然也可以自定义Loading View,提供一个布局即可:

View loadingView = LayoutInflater.from(getContext.inflate(R.layout.manu_loading_layout,null); mBaseAdapter.showLoading(loadingView);

效果如下:

图片 10loading_view.png

还有一种情况是,顶部有一个固定的HeaderCell,不需要加载数据,显示静态页面,下面加载数据时需要Loading态,Error状态,Empty状态等等。提供如下3个方法:

  • showLoadingKeepCount(int keepCount,int height,View loadingView)列表Loading状态显示的View,保留keepCountg个Item,并指定高度,指定显示的View

  • showLoadingKeepCount(int keepCount,int height)列表Loading状态显示的View,保留keepCountg个Item,并指定高度(显示的是提供的默认Loading View)

  • showLoadingKeepCount(int keepCount)显示默认LoadingView

使用代码如下:

 View loadingView = LayoutInflater.from(getContext.inflate(R.layout.manu_loading_layout,null);mBaseAdapter.showLoadingKeepCount(1,height,loadingView);

效果图如下:

图片 11loading_view_keep_count.png

隐藏Loading View 调用对应hide 方法:

 mBaseAdapter.hideLoading();

** 3, Error View 和 Empty View **显示Error View 和Empty View 与Loading View 的显示与隐藏是一样,不在过多讲,直接去看源码,提供了几个方法:

图片 12error_method.png

效果图:

图片 13error_tip.png

Empty View 的显示完全一样,就不再讲了。

当然还可以设置Gird布局GridLayoutManager,瀑布布局StaggeredGridLayoutManager

图片 14RecyclerView_SourceCode_4.png

最后

以上就是对RecyclerView Adapter 的封装和 该库的使用介绍,使用起来非常方便,添加一个列表不再是重复的写Adapter,ViewHolder 等等。添加一个Cell 填充到Adapter 就OK。增加一种Item或者加少一种Item对列表完全没有影响,耦合度几乎为0。更详细的思路分析请看文章RecyclerView 之Adapter的简化过程浅析详细的源码请看Gihub:Adapter优雅封装-CustomAdapter,欢迎star和follow。

还有一些其他的API:

而holder由7074行得来

findFirstVisibleItemPosition()返回当前第一个可见Item的position

final ViewHolder holder = getChildViewHolderInt;

findFirstCompletelyVisibleItemPosition()返回当前第一个完全可见Item的position

继续查看getChildViewHolderInt方法

findLastVisibleItemPosition()返回当前最后一个可见Item的position

图片 15RecyclerView_SourceCode_5.png

findLastCompletelyVisibleItemPosition()返回当前最后一个完全可见Item的position

holder是由子View的LayoutParams得来

RecyclerView.Adapter扮演着两个角色。一、根据不同ViewType创建与之相应的的Item-Layout,二、访问数据集合并将数据绑定到正确的View上。这就需要我们重写以下函数:

这个LayoutParams是RecyclerView内部静态内,里面包含了一个ViewHolder mViewHolder 成员变量

public VH onCreateViewHolder(ViewGroup parent, int viewType)创建Item视图,并返回相应的ViewHolder

由于我们添加的HeaderView是普通的 View / ViewGroup ,所以并没有什么ViewHolder, 于此,此路不通耶

public void onBindViewHolder(VH holder, int position)绑定数据到正确的Item视图上。

从ViewGroup入手

本着不抛弃,不放弃的精神,让我们来大开脑洞吧,由于RecyclerView是继承自ViewGroup的,我们知道ViewGroup有addView(View child, int index)方法,那我们试试不妨

mLayoutManager = new LinearLayoutManager(this, orientation, false);mRecyclerView.setLayoutManager(mLayoutManager);View header = LayoutInflater.from.inflate(R .layout.layout_header, null, false);mRecyclerView.addView(header, 0);

悲剧的是,在运行时依然爆出空指针异常

图片 16RecyclerView_Header_Error_2.png

继续查看源码(这里我就不贴了,有兴趣的可以自己翻看),发现依然是缺少ViewHolder的缘故,由此看来,这条路也不通了

public int getItemCount() 返回该Adapter所持有的Itme数量

思路

屡次受挫,确实是有点动摇军心,仿佛看不到希望,但是有句话叫做 Nerver say Nerver, 好吧,至少我们知道了一点,我们要想在RecylerView中添加任何子View,那么这个View必须要有ViewHolder

我们在上一篇中讲到:RecyclerView的Adapter默认要求使用ViewHolder ,嗯,似乎我们找到了正道。

我们创建RecyclerView的Adapter时,必须要实现三个方法

图片 17RecyclerView_SourceCode_6.png

其中

onCreateViewHolder(ViewGroup parent, int viewType)

第二个参数,int viewType,由名字我们猜测是Item的类型,如果没错的话,那么我们的Header和正常的Item就是两种类型了,那么,这个类型是怎么得来的呢?

查看源码,叫我们参考getItemViewType方法:

public int getItemViewType(int position) { return 0;}

源码里,传入一个position,默认返回0

这里我们得到了启发,我们在自己的Adapter里:

public class MyAdapter extends RecyclerView.Adapter {

实现
  1. 首先定义:
private int ITEM_TYPE_NORMAL = 0;private int ITEM_TYPE_HEADER = 1;
  1. getItemViewType()中,假如position传入0,我们的返回值返回 ITEM_TYPE_HEADER,其他的position,我们返回 ITEM_TYPE_NORMAL,这样就区分了viewType

  2. onCreateViewHolder()中,我们根据不同的viewType返回不同的ViewHolder

  3. onBindViewHolder()中,我们首先根据positon调用getItemViewType(int position)方法,得到不同的viewType,如果得到 ITEM_TYPE_HEADER ,我们直接return,如果得到 ITEM_TYPE_NORMAL,那么,由于有Header的存在,我们在设置Item的数据时,应该把position -1

  4. getItemCount()中,由于我们多了HeaderView,所以要在真实数据个数中+1

public String[] datas = null;

效果展示

图片 18RecyclerView_With_Header.png

public MyAdapter(String[] datas) {

为RecyclerView添加EmptyView, FooterView

为RecyclerView添加FooterView,EmptyView 其实和添加HeaderView是类似的,这里就不多言了,直接把同时添加Header,Footer和Empty View的源码贴上

public class DemoAdapter extends RecyclerView. Adapter<RecyclerView.ViewHolder> { private List<String> mDatas = new ArrayList<>(); private Context mContext; private View mHeaderView; private View mFooterView; private View mEmptyView; private int ITEM_TYPE_NORMAL = 0; private int ITEM_TYPE_HEADER = 1; private int ITEM_TYPE_FOOTER = 2; private int ITEM_TYPE_EMPTY = 3; public DemoAdapter(Context context) { mContext = context; } public void setDatas(List<String> datas) { mDatas = datas; notifyDataSetChanged(); } // 创建视图 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == ITEM_TYPE_HEADER) { return new ViewHolder(mHeaderView); } else if (viewType == ITEM_TYPE_EMPTY) { return new ViewHolder(mEmptyView); } else if (viewType == ITEM_TYPE_FOOTER) { return new ViewHolder(mFooterView); } else { View v = LayoutInflater.from .inflate( R.layout.layout_recyclerview_item_view, parent, false); return new ViewHolder; } } @Override public int getItemViewType(int position) { if (null != mHeaderView && position == 0) { return ITEM_TYPE_HEADER; } if (null != mFooterView && position == getItemCount { return ITEM_TYPE_FOOTER; } if (null != mEmptyView && mDatas.size{ return ITEM_TYPE_EMPTY; } return ITEM_TYPE_NORMAL; } // 为Item绑定数据 @Override public void onBindViewHolder (RecyclerView.ViewHolder holder, int position) { int type = getItemViewType; if (type == ITEM_TYPE_HEADER || type == ITEM_TYPE_FOOTER || type == ITEM_TYPE_EMPTY) { return; } int realPos = getRealItemPosition; ((DemoAdapter.ViewHolder) holder) .mTextView .setText(mDatas.get; } private int getRealItemPosition(int position) { if (null != mHeaderView) { return position - 1; } return position; } @Override public int getItemCount() { int itemCount = mDatas.size(); if (null != mEmptyView && itemCount == 0) { itemCount++; } if (null != mHeaderView) { itemCount++; } if (null != mFooterView) { itemCount++; } return itemCount; } public void addHeaderView(View view) { mHeaderView = view; notifyItemInserted; } public void addFooterView(View view) { mFooterView = view; notifyItemInserted(getItemCount; } public void setEmptyView(View view) { mEmptyView = view; notifyDataSetChanged(); } class ViewHolder extends RecyclerView.ViewHolder { TextView mTextView; ViewHolder { super; mTextView =  v.findViewById(R.id.item_title); } }}

Activity中

View footer = LayoutInflater.from.inflate(R.layout.layout_footer, mRecyclerView, false);View header = LayoutInflater.from.inflate(R.layout.layout_header, mRecyclerView, false);View empty = LayoutInflater.from.inflate(R.layout.layout_empty, mRecyclerView, false);adapter.addHeaderView;adapter.addFooterView;adapter.setEmptyView;

下图展示的是同时添加了Header和Footer View

图片 19RecyclerView_With_Header_Footer.png

下图展示的是Empty View

图片 20RecyclerView_With_Empty.png

this.datas = datas;

封装

其实上面的代码在一般情况对添加HeaderView ,FooterView ,Empty View已经够用了,不过麻烦的是,我们在不同的地方,需要重复Copy一些代码,显然,这是不能容忍的

那么,我们能不能像ListView那样 把什么addHeaderView(),addFooterView(),setEmptyView()直接封装在RecyclerView里呢?答案是肯定的!

这里我们用到了装饰模式,我们用自定义的RecyclerView中的内部类Adapter来装饰原始从Activity传入的Adapter,我们可以毫无影响之前的逻辑来添加这些额外的Header,Footer,Empty View

废话不多说,直接上源码,拿走,不谢

GloriousRecyclerView

/* * Copyright  2017 CXP 277371483@qq.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.xpc.recylerviewdemo;import android.content.Context;import android.support.annotation.Nullable;import android.support.v7.widget.RecyclerView;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;/** * Created on 17-2-14 * * @author cxp */public class GloriousRecyclerView extends RecyclerView { private View mHeaderView; private View mFooterView; private View mEmptyView; private GloriousAdapter mGloriousAdapter; public GloriousRecyclerView(Context context) { super; } public GloriousRecyclerView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public GloriousRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void addHeaderView(View view) { mHeaderView = view; mGloriousAdapter.notifyItemInserted; } public void addFooterView(View view) { mFooterView = view; mGloriousAdapter.notifyItemInserted(mGloriousAdapter.getItemCount; } public void setEmptyView(View view) { mEmptyView = view; mGloriousAdapter.notifyDataSetChanged(); } @Override public void setAdapter(Adapter adapter) { if (adapter != null) { mGloriousAdapter = new GloriousAdapter; } super.setAdapter(mGloriousAdapter); } private class GloriousAdapter extends RecyclerView.Adapter<ViewHolder> { private Adapter mOriginalAdapter; private int ITEM_TYPE_NORMAL = 0; private int ITEM_TYPE_HEADER = 1; private int ITEM_TYPE_FOOTER = 2; private int ITEM_TYPE_EMPTY = 3; //聪明的人会发现我们这里用了一个装饰模式 public GloriousAdapter(Adapter originalAdapter) { mOriginalAdapter = originalAdapter; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == ITEM_TYPE_HEADER) { return new GloriousViewHolder(mHeaderView); } else if (viewType == ITEM_TYPE_EMPTY) { return new GloriousViewHolder(mEmptyView); } else if (viewType == ITEM_TYPE_FOOTER) { return new GloriousViewHolder(mFooterView); } else { return mOriginalAdapter.onCreateViewHolder(parent, viewType); } } @Override public void onBindViewHolder(ViewHolder holder, int position) { int type = getItemViewType; if (type == ITEM_TYPE_HEADER || type == ITEM_TYPE_FOOTER || type == ITEM_TYPE_EMPTY) { return; } int realPosition = getRealItemPosition; mOriginalAdapter.onBindViewHolder(holder, realPosition); } @Override public int getItemCount() { int itemCount = mOriginalAdapter.getItemCount(); //加上其他各种View if (null != mEmptyView && itemCount == 0) itemCount++; if (null != mHeaderView) itemCount++; if (null != mFooterView) itemCount++; return itemCount; } @Override public int getItemViewType(int position) { if (null != mHeaderView && position == 0) return ITEM_TYPE_HEADER; if (null != mFooterView && position == getItemCount return ITEM_TYPE_FOOTER; if (null != mEmptyView && mOriginalAdapter.getItemCount return ITEM_TYPE_EMPTY; return ITEM_TYPE_NORMAL; } private int getRealItemPosition(int position) { if (null != mHeaderView) { return position - 1; } return position; } /** * ViewHolder 是一个抽象类 */ class GloriousViewHolder extends ViewHolder { GloriousViewHolder(View itemView) { super; } } }}

Activity

public class GloriousActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_glorious_recycler_view); GloriousRecyclerView recyclerView = (GloriousRecyclerView) findViewById(R.id.recycler_view); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false); recyclerView.setLayoutManager(layoutManager); NormalAdapter adapter = new NormalAdapter; adapter.setDatas(constructTestDatas; View footer = LayoutInflater.from.inflate(R.layout.layout_footer, recyclerView, false); View header = LayoutInflater.from.inflate(R.layout.layout_header, recyclerView, false); View empty = LayoutInflater.from.inflate(R.layout.layout_empty, recyclerView, false); recyclerView.setAdapter; recyclerView.addHeaderView; recyclerView.addFooterView; recyclerView.setEmptyView; } private List<String> constructTestDatas() { List<String> datas = new ArrayList<>(); datas.add; //... datas.add; return datas; }}

layout

<RelativeLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent" android:background="#888"> <com.xpc.recylerviewdemo.GloriousRecyclerView android: android:layout_width="match_parent" android:layout_height="match_parent" /></RelativeLayout>

}

总结

本章一步步带领大家为RecylerView添加HeaderView,FooterView,EmptyView,以及完成了对GloriousRecyclerView的封装,使我们的开发更加的便捷。

网上或许有类似的解决方案,但是都没有讲解为什么要这样做,正所谓知其然不知其所以然。如果你认真读了这篇文章,相信会对你有所帮助。

//创建新View,被LayoutManager所调用

上一篇

RecyclerView从入门到入神——初识RecyclerView

@Override

下一篇

RecyclerView从入门到入神——为RecyclerView添加下拉刷新和上拉加载更多

public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {

View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item,viewGroup,false);

ViewHolder vh = new ViewHolder(view);

return vh;

}

//将数据与界面进行绑定的操作

@Override

public void onBindViewHolder(ViewHolder viewHolder, int position) {

viewHolder.mTextView.setText(datas[position]);

}

//获取数据的数量

@Override

public int getItemCount() {

下一篇:没有了