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,具体的思路如下
// 重写getItemViewType
public 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去创建对应的viewholder
public 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;
}
基本的流程我们前面已经说过了。