android material design widget recyclerview
RecyclerView的概述
谷歌的官方的话语A flexible view for providing a limited window into a large data set.看翻译似乎和listView 和GridView这些列表差不多,但是在部分细节上做的似乎比listview的这些view要更好一点
简单介绍下RecyclerView的常见的几种类的
- RecyclerView : 总体的类,起到调控者的作用。具体的事务不是他去处理
- RecyclerView.Adapter : 用来将数据展示到每个item的上的,
- RecyclerView.Recycler : 用来回收RecyclerAdapter的item的。
- RecyclerView.ViewHolder: 用来管理视图的view的
- LayoutManager:用来决定每个item放置的位置。
从上面来看RecyclerView 从设计的过程中,就将每个功能分配到具体某项类。形成单一职责的原则。
RecyclerView的用法
RecyclerViewd的基本用法
和listView一样的。RecyclerView也需要设置适配器将数据关联上view上
具体的代码如下
// 初始化recyclerview的对象RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_main_list);RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);RecyclerView.Adapter adapter = new Adapter();// 给recyclerview的设置相应的参数recyclerView.setLayoutManager(layoutManager);recyclerView.setAdapter(adapter);
我们来看下adapter的实现的过程
class Adapter extends RecyclerView.Adapter<ListViewHolder>{ //创建一个viewholder的对象,这个方法的调用取决于视图的显示的item的个数和缓存的个数 @Override public ListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_main_list,parent,false); return new ListViewHolder(itemView); } // 将取到的adapter的绑定数据,这里就不做任何处理 @Override public void onBindViewHolder(ListViewHolder holder, int position) { } //item的个数 @Override public int getItemCount() { return 40; } } // item viewholder的具体实现类 public static class ListViewHolder extends RecyclerView.ViewHolder{ public ListViewHolder(View itemView) { super(itemView); } }
最后的形成的效果图为:
一些高级的用法
adapter中的getItemViewType的方法
//default 0; public int getItemViewType(int position) { return 0; }
我们可以使用这个方法,来实现多type的item的显示,常见我们可以实现HeaderView和FooterView的RecyclerView,具体的思路如下
// 重写getItemViewTypepublic int getItemViewType(int position){ //列表中第一个item为header if(position ==0 ){ return HEADER_TYPE; } //列表中最后一个item为footer if(position == getItemCount() - 1){ return FOOTER_TYPE; } return LIST_TYPE;}//在onCreateViewHolder()中,根据type去创建对应的viewholderpublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = null; switch (viewType){ case HEADER_TYPE: itemView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_main_list_header, parent, false); return new HeaderViewHolder(itemView); case FOOTER_TYPE: itemView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_main_list_footer, parent, false); return new FooterViewHolder(itemView); default: case BODY_TYPE: itemView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_main_list, parent, false); return new ListViewHolder(itemView); }}
生成效果图为:
注意 上面均没有实现onBindViewHolder()的实现方法。在实际的需要根据的业务去实现的具体的view和数据进行绑定
GridLayoutManager中的setSpanSizeLookup();
也许你会发现上面的代码对于GridLayoutManager并没有那么管用的,因为我们的在网格上的头文件的应该占据一行,而不是在网格的上在前面在占一个的,类似于下图这样的效果
这个时候我们需要将的设置一下的代码
final GridLayoutManager manager = new GridLayoutManager(this, 4); manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return position == 0 || position == manager.getItemCount() - 1 ? 4 : 1; }});
形成的效果图
recyclerView的setRecycledViewPool和recyclerview的getRecycledViewPool();
recylcerview的回收机制Recycler具有两及缓存,
Scrap中的ViewHolder,不用通过Adapter重新处理,只需要attach后回到LayoutManager就可以重用。
RecycledViewPool中的ViewHolder,数据往往是错误的,则需要通过Adapter重新绑定正确的数据后在回到LayoutManager。具体的回收的机制会在回收的机制里面去说明,总之,现在只需要记住当LayoutManager需要一个新的View时
- Recycler会行检查scrap中是否有合适的ViewHolder,如果有直接返回给LayoutManager使用;
- 如果没有,就需要从Pool里面寻找,然后右Adapter重新绑定数据后,返回到LayoutManager;
- 如果pool还是没有,就需要由Adapter创建一个新的Viewholder
而上面的setRecycledViewPoo()和getRecycledViewPool()的方法就是用来的让多个相同的类似的recyclerview公用一个recyclerpool的。先看一个简单的使用的方法。
//需要的能够共享的RecyclerView的设置的setRecycleChildrenOnDetach(true);从而让detach过后的viewholder放入到线程池中。RecyclerView view1 = new RecyclerView(context);LinearLayoutManager layout = new LinearLayoutManager(context);layout.setRecycleChildrenOnDetach(true);view1.setLayoutManager(layout);RecycledViewPool pool = view1.getRecycledViewPool();//将view2的recylceredViewPool()设成一个的。RecyclerView view2 = new RecyclerView(context);view2.setLayoutManager(layout);view2.setRecycledViewPool(pool);RecyclerView view3 = new RecyclerView(context);view3.setLayoutManager(layout);view3.setRecycledViewPool(pool);
实际中的viewpager的使用中的使用的情况的最多。viewpager的中多个list具有同样的viewholder,这个时候我们就可以选择的使用的setRecycledViewPool()的方法来让viewpager里面的list的都共用一个recycleredpool,我们这里以fragmentpageradpter为例
public static class PoolPagerAdapter extends FragmentPagerAdapter{ private RecyclerView.RecycledViewPool mPool = new RecyclerView.RecycledViewPool(); public PoolPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { RecyclerViewFragment fragment = new RecyclerViewFragment(); fragment.pool = mPool; return fragment; } @Override public int getCount() { return 4; }} //RecyclerViewFragment public static class RecyclerViewFragment extends Fragment{ RecyclerView.RecycledViewPool pool; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { RecyclerView view = new RecyclerView(inflater.getContext()); LinearLayoutManager layout = new LinearLayoutManager(inflater.getContext()); layout.setRecycleChildrenOnDetach(true); if (pool != null) { view.setRecycledViewPool(pool); } view.setLayoutManager(layout); view.setAdapter(new PoolPagerAdapter(fm)); return view; } }
通过以上的代码我们就可以让的viewpager里面的fragment的里面的recyclcerview的里面的都共用一个recycledpool的
同时在使用功存的recycledpool时候需要注意下面的
- recycledViewPool是根据getItemViewType的type值去寻找viewholder的,所以在共享的时候,需要确保RecyclerView中的adapter的getItemViewType()的返回值是不会冲突的。
- recycledViewPool可以自主的控制需要缓存的viewholder的数量的。setMaxRecycledViews(itemViewType, number)去设置相应的type和viewholder的数量
- recyclerView 是可以设置自己的所需要的viewholder的数量的。只有超出这个数量的viewholder并且detached过后才会扔进viewpool里面与其他的recyclerview共享。手动设置的数量为setItemViewCacheSize(10);
- 在恰当的时候,recycledViewPool会去自动清除这些viewhodlder的对象。也可以手动调用清除clear();
OnItemClickListener的实现
前文说到recyclerview其实是一个调度器,他不负责其中的量测,布局,等一些事情,它把所有的事情都交给每个类去做。它只起到宏观的调控作用。具体的实现它也不管。所以类似点击事件的监听它也不去做处理。
所以我们需要的想清楚,item的点击事件是属于谁的事务,adapter的
所以你需要在adapter中去实现监听的事件。网上这样的例子太多了,这里不做说明了。
谈谈封装的事
这里的封装,不包括将recyclerview的作为视图放在activity中,或者放在fragment中。只是对基本的recycler中的adapter进行封装。
在前文我们写了一个的adapter事对type的那种。其实想想不能这么去写。一个adapter处理两种type的事。这样是不好的。我们应该做到让一个adapter去实现一个type就行了。这样的话。后期修改方便。举个栗子 ,我们需要作出下列图表的效果。我们该如何去封装adapter呢
我们先抽出一个BaseRecyclerAdapter
E 用来的指明数据的type
T 用来指明viewholder的类型
public abstract class BaseRecyclerAdapter<E,T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> { protected Activity mActivity; protected List<E> mDataList = new ArrayList<>(); protected OnClickListener mOnClickListener; protected OnLongClickListener mOnLongClickListener; public BaseRecyclerAdapter(Activity activity) { mActivity = activity; } @Override public int getItemCount() { return mDataList.size(); } public void setDataList(@NonNull List<E> list) { mDataList.clear(); mDataList.addAll(list); notifyDataSetChanged(); } public List<E> getDataList() { return mDataList; } public void setOnClickListener(OnClickListener onClickListener) { mOnClickListener = onClickListener; } public void setOnLongClickListener(OnLongClickListener onLongClickListener) { mOnLongClickListener = onLongClickListener; }}
当然你也可以添加其他的基本的属性,这里面以最简单的实例为主。这里是以最简模式存在的,
记下来我们需要的实现的上面的效果的adapter的,在实现之前,按照我们一个之前的,我们的需要一个adapter去实现一个type的类型的viewholder的对吧,所以在这里我们需要两个的viewholder一个viewholder为名字即上面的标题,一个viewholder为列表的,并且的两个adapter的一个adapter负责处理标题的viewholder,一个adapter处理列表的viewholder,所以接下来很简单了,先准备好材料
标题的中的视图就一个textview
public class CategoryNameViewHolder extends RecyclerView.ViewHolder { public TextView mCategoryName; public CategoryNameViewHolder(View itemView) { super(itemView); mCategoryName = (TextView) itemView.findViewById(R.id.tv_item_category_list_name); } public CategoryNameViewHolder(Context context, ViewGroup parent) { this(LayoutInflater.from(context).inflate(R.layout.item_category_list_name, parent, false)); }}
列表的中的list的viewholder
public class CategoryViewHolder extends RecyclerView.ViewHolder { public TextView mListCounts; public TextView mListName; public RourdCornerNetworkImageView mListImage; public View mDiviverView; public CategoryViewHolder(View itemView) { super(itemView); mListCounts = (TextView) itemView.findViewById(R.id.tv_item_category_list_counts); mListName = (TextView) itemView.findViewById(R.id.tv_item_category_list_name); mListImage = (RourdCornerNetworkImageView) itemView.findViewById(R.id.rcniv_item_category_list_image); mDiviverView = itemView.findViewById(R.id.view_item_category_list_divier); } public CategoryViewHolder(Context context, ViewGroup parent) { this(LayoutInflater.from(context).inflate(R.layout.item_category_list, parent, false)); }}
接下来的adapter的实现。
标题的adapter 的实现。
public class CategoryNameAdapter extends BaseRecyclerAdapter<String,CategoryNameViewHolder> { public CategoryNameAdapter(Activity activity) { super(activity); } @Override public CategoryNameViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new CategoryNameViewHolder(mActivity, parent); } @Override public void onBindViewHolder(CategoryNameViewHolder viewHolder, int position) { holder.mCategoryName.setText(mDataList.get(position)); } @Override public int getItemCount() { return mDataList.size(); }}
列表的list的adapter的实现
public class CategoryListAdapter extends BaseRecyclerAdapter<PlayListModel> { public CategoryListAdapter(Activity activity) { super(activity); } @Override public CategoryListAdapter onCreateViewHolder(ViewGroup parent, int viewType) { return new CategoryViewHolder(mActivity, parent); } @Override public void onBindViewHolder(CategoryListAdapter viewHolder, int position) { onBindViewHolder(viewHolder, position, false); } public void onBindViewHolder(CategoryListAdapter holder, int position, boolean listEnd) { PlayListModel playListModel = mDataList.get(position); final String playlist_name = playListModel.getName(); final String playlist_image = playListModel.getImage(); final int playlist_counts = playListModel.getCount(); final int playlist_id = playListModel.getId(); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { VideoPlayListActivity.start(mActivity, playlist_id, playlist_name, playlist_image, playlist_counts); if(mOnClickListener!=null){ mOnClickListener.onClick(holder.itemView,position); } } }); }}
你会发现上面的四个类其实很简单,都是遵守着单一的职责的原则,一个adapter只负责处理的一个type的事。但是在我们的界面中,我们需要显示,一个标题下后面跟着它说list的,在list的结尾后会有标题。形成一种类似的通讯录的形式。但是上面的四个类只是材料,我们还需要一个耦合的adapter来处理我们的需求的。
耦合的adapter的是根据具体的需求来改动。所以在需求改动比较大的时候,我们只需要改动用来耦合的adapter就可以了。
耦合的adapter
public class VideoCategoryListAdapter extends RecyclerView.Adapter { private Activity mActivity; private static final int CATEGORYLIST_ADAPTER_TYPE = 2; private static final int CATEGORY_LIST_NAME_TYPE = 1; private CategoryListAdapter mNormalAdapter; private CategoryNameAdapter mNameAdapter; private List<PlayListModel> mPlayListDatas = new ArrayList<>(); private Map<Integer, Integer> mCategoryListCountsMap = new HashMap<>(); private List<String> mNames = new ArrayList<>(); public VideoCategoryListAdapter(Activity activity) { mActivity = activity; mNormalAdapter = new CategoryListAdapter(activity); mNameAdapter = new CategoryNameAdapter(activity); } @Override public int getItemViewType(int position) { int count = 0; for (int i = 0; i < mCategoryListCountsMap.size(); i++) { if (position == count + i) { return CATEGORY_LIST_NAME_TYPE; } count += mCategoryListCountsMap.get(i); } return CATEGORYLIST_ADAPTER_TYPE; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case CATEGORY_LIST_NAME_TYPE: return new CategoryNameViewHolder(mActivity, parent); default: case CATEGORYLIST_ADAPTER_TYPE: return new CategoryViewHolder(mActivity, parent); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { int type = getItemViewType(position); int offset = 0; int count = 0; boolean isSongListEnd = false; for (int i = 0; i < mCategoryListCountsMap.size(); i++) { if (position >= count + i) {//0+0 //5+1 //18+2 count += mCategoryListCountsMap.get(i); offset++; } if (position == count + i - 1) { isSongListEnd = true; } } switch (type) { case CATEGORY_LIST_NAME_TYPE: mNameAdapter.onBindViewHolder(holder, offset - 1); break; case CATEGORYLIST_ADAPTER_TYPE: mNormalAdapter.onBindViewHolder(holder, position - offset, isSongListEnd); break; } } @Override public int getItemCount() { return mNameAdapter.getItemCount() + mNormalAdapter.getItemCount(); } public void setCategoryList(List<CategoryPlaylistModel.Categoryplaylists> categoryList) { mPlayListDatas.clear(); mNames.clear(); List<PlayListModel> models = new ArrayList<>(); int size = categoryList.size(); for (int i = 0; i < size; i++) { models.clear(); CategoryPlaylistModel.Categoryplaylists categoryplaylists = categoryList.get(i); mCategoryListCountsMap.put(i, categoryplaylists.getPlaylist().size()); mNames.add(categoryplaylists.getCategory().getName()); for (PlayListModel playListModel : categoryplaylists.getPlaylist()) { models.add(playListModel); } mPlayListDatas.addAll(models); } mNameAdapter.setDataList(mNames); mNormalAdapter.setDataList(mPlayListDatas); notifyDataSetChanged(); }
关于耦合的adapter 我们只需要关心在getItemViewType()的过程中的根据position 的位置去返回对应的type就行。然后在onBindViewHolder(),getItemCount(),onCreateViewHolder()等这些方法中我们只需要根据的type去分配好我们之前处理好的adapter就行了。这个一个页面的效果用这么多类似乎显得繁琐了,但是在后期的修改和维护中,绝对比一个类去实现的要好的多。这是我被坑想出来的方法。需求变化太快,我来不及一点点防备,然后我花了一天的时间,将我们的项目中的adapter全部重新构建。想到的。具体的优点
- 类和类之间的耦合度很低
- 如果你需要更改viewholder的布局,但是布局中的元素没有更改的话,那么你只需要去重写layout即可然后在viewholder里面。
- 如果你需要viewholder的布局,而且需要布局的元素需要更改的话,你你只需要的去重写一个viewholder,然后然后将adapter接受范性的viewholder替换掉
- 如果你需要修改列表的中排版,即相应位置的item的时候,你只需要去修改哪个耦合的adapter的就行了。
- 对应多个地方运用相同的adapter的时候,你可以直接去用。
关于recyclerview的回收机制
这个我感觉作为了解即可了,毕竟我现在作为业务层的android的。我们更多的是知道怎么回事就行了。
上源码
View getViewForPosition(int position, boolean dryRun) { if (position < 0 || position >= mState.getItemCount()) { throw new IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount()); } boolean fromScrap = false; ViewHolder holder = null; // 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrap = holder != null; } // 1) Find from scrap by position 在scrap的里面寻找 if (holder == null) { holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); if (holder != null) { if (!validateViewHolderForOffsetPosition(holder)) { // recycle this scrap if (!dryRun) { // we would like to recycle this but need to make sure it is not used by // animation logic etc. holder.addFlags(ViewHolder.FLAG_INVALID); if (holder.isScrap()) { removeDetachedView(holder.itemView, false); holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); } holder = null; } else { fromScrap = true; } } } if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " + "position " + position + "(offset:" + offsetPosition + ")." + "state:" + mState.getItemCount()); } final int type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap via stable ids, if exists if (mAdapter.hasStableIds()) { holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrap = true; } } if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); if (view != null) { holder = getChildViewHolder(view); if (holder == null) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view which does not have a ViewHolder"); } else if (holder.shouldIgnore()) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view that is ignored. You must call stopIgnoring before" + " returning this view."); } } } if (holder == null) { // fallback to recycler // try recycler. // Head to the shared pool. if (DEBUG) { Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared " + "pool"); } holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } if (holder == null) { holder = mAdapter.createViewHolder(RecyclerView.this, type); if (DEBUG) { Log.d(TAG, "getViewForPosition created new ViewHolder"); } } } boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { if (DEBUG && holder.isRemoved()) { throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); holder.mOwnerRecyclerView = RecyclerView.this; mAdapter.bindViewHolder(holder, offsetPosition); attachAccessibilityDelegate(holder.itemView); bound = true; if (mState.isPreLayout()) { holder.mPreLayoutPosition = position; } } final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrap && bound; return holder.itemView; }
基本的流程我们前面已经说过了。
更多相关文章
- SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
- 一句话锁定MySQL数据占用元凶
- 解决WebView加载Https无法显示的问题
- Android(安卓)ListView拖动时,背景颜色会变成黑色
- Android(安卓)studio 3.0安装配置方法图文教程
- 打开Android(安卓)PVLogger的方法
- Android_SQLite数据库详解
- 学习ANDROID GOOGLE地图5部曲
- Android数据存储操作