RecyclerView的概述

谷歌的官方的话语A flexible view for providing a limited window into a large data set.看翻译似乎和listView 和GridView这些列表差不多,但是在部分细节上做的似乎比listview的这些view要更好一点
简单介绍下RecyclerView的常见的几种类的

  1. RecyclerView : 总体的类,起到调控者的作用。具体的事务不是他去处理
  2. RecyclerView.Adapter : 用来将数据展示到每个item的上的,
  3. RecyclerView.Recycler : 用来回收RecyclerAdapter的item的。
  4. RecyclerView.ViewHolder: 用来管理视图的view的
  5. 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);
        }
    }

最后的形成的效果图为:

android recyclerview第三方库 androidx.recyclerview.widget.recyclerview_recyclervi

一些高级的用法

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);
    }
}

生成效果图为:

android recyclerview第三方库 androidx.recyclerview.widget.recyclerview_widget_02


android recyclerview第三方库 androidx.recyclerview.widget.recyclerview_android_03


注意 上面均没有实现onBindViewHolder()的实现方法。在实际的需要根据的业务去实现的具体的view和数据进行绑定

GridLayoutManager中的setSpanSizeLookup();

也许你会发现上面的代码对于GridLayoutManager并没有那么管用的,因为我们的在网格上的头文件的应该占据一行,而不是在网格的上在前面在占一个的,类似于下图这样的效果

android recyclerview第三方库 androidx.recyclerview.widget.recyclerview_recyclervi_04

这个时候我们需要将的设置一下的代码

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;
    }
});

形成的效果图

android recyclerview第三方库 androidx.recyclerview.widget.recyclerview_数据_05

recyclerView的setRecycledViewPool和recyclerview的getRecycledViewPool();

recylcerview的回收机制Recycler具有两及缓存,
Scrap中的ViewHolder,不用通过Adapter重新处理,只需要attach后回到LayoutManager就可以重用。
RecycledViewPool中的ViewHolder,数据往往是错误的,则需要通过Adapter重新绑定正确的数据后在回到LayoutManager。具体的回收的机制会在回收的机制里面去说明,总之,现在只需要记住当LayoutManager需要一个新的View时

  1. Recycler会行检查scrap中是否有合适的ViewHolder,如果有直接返回给LayoutManager使用;
  2. 如果没有,就需要从Pool里面寻找,然后右Adapter重新绑定数据后,返回到LayoutManager;
  3. 如果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时候需要注意下面的

  1. recycledViewPool是根据getItemViewType的type值去寻找viewholder的,所以在共享的时候,需要确保RecyclerView中的adapter的getItemViewType()的返回值是不会冲突的。
  2. recycledViewPool可以自主的控制需要缓存的viewholder的数量的。setMaxRecycledViews(itemViewType, number)去设置相应的type和viewholder的数量
  3. recyclerView 是可以设置自己的所需要的viewholder的数量的。只有超出这个数量的viewholder并且detached过后才会扔进viewpool里面与其他的recyclerview共享。手动设置的数量为setItemViewCacheSize(10);
  4. 在恰当的时候,recycledViewPool会去自动清除这些viewhodlder的对象。也可以手动调用清除clear();

OnItemClickListener的实现

前文说到recyclerview其实是一个调度器,他不负责其中的量测,布局,等一些事情,它把所有的事情都交给每个类去做。它只起到宏观的调控作用。具体的实现它也不管。所以类似点击事件的监听它也不去做处理。
所以我们需要的想清楚,item的点击事件是属于谁的事务,adapter的
所以你需要在adapter中去实现监听的事件。网上这样的例子太多了,这里不做说明了。

谈谈封装的事

这里的封装,不包括将recyclerview的作为视图放在activity中,或者放在fragment中。只是对基本的recycler中的adapter进行封装。

在前文我们写了一个的adapter事对type的那种。其实想想不能这么去写。一个adapter处理两种type的事。这样是不好的。我们应该做到让一个adapter去实现一个type就行了。这样的话。后期修改方便。举个栗子 ,我们需要作出下列图表的效果。我们该如何去封装adapter呢

android recyclerview第三方库 androidx.recyclerview.widget.recyclerview_widget_06

我们先抽出一个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全部重新构建。想到的。具体的优点

  1. 类和类之间的耦合度很低
  2. 如果你需要更改viewholder的布局,但是布局中的元素没有更改的话,那么你只需要去重写layout即可然后在viewholder里面。
  3. 如果你需要viewholder的布局,而且需要布局的元素需要更改的话,你你只需要的去重写一个viewholder,然后然后将adapter接受范性的viewholder替换掉
  4. 如果你需要修改列表的中排版,即相应位置的item的时候,你只需要去修改哪个耦合的adapter的就行了。
  5. 对应多个地方运用相同的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;
        }

基本的流程我们前面已经说过了。