Android RecyclerView 必知必会

d0f4sd01dd41 5年前
   <h2>导语</h2>    <p>RecyclerView是Android 5.0提出的新UI控件,可以用来代替传统的ListView。</p>    <p>Bugly之前也发过一篇相关文章,讲解了  RecyclerView 与 ListView 在缓存机制上的一些区别:</p>    <p>今天精神哥来给大家详细介绍关于  RecyclerView,你需要了解 的方方面面。</p>    <h2>前言</h2>    <p>下文中Demo的源代码地址:</p>    <p>https://github.com/xiazdong/RecyclerViewDemo</p>    <p>(点击文末阅读原文,直接访问该项目)</p>    <ul>     <li> <p>Demo1: RecyclerView添加HeaderView和FooterView,ItemDecoration范例。</p> </li>     <li> <p>Demo2: ListView实现局部刷新。</p> </li>     <li> <p>Demo3: RecyclerView实现拖拽、侧滑删除。</p> </li>     <li> <p>Demo4: RecyclerView闪屏问题。</p> </li>     <li> <p>Demo5: RecyclerView实现 setEmptyView() 。</p> </li>     <li> <p>Demo6: RecyclerView实现万能适配器,瀑布流布局,嵌套滑动机制。</p> </li>    </ul>    <h2>基本概念</h2>    <p>RecyclerView是Android 5.0提出的新UI控件,位于support-v7包中,可以通过在build.gradle中添加 compile 'com.android.support:recyclerview-v7:24.2.1' 导入。</p>    <p>RecyclerView的官方定义如下:</p>    <p>A flexible view for providing a limited window into a large data set.</p>    <p>从定义可以看出,flexible(可扩展性)是RecyclerView的特点。不过我们发现和ListView有点像,本文后面会介绍RecyclerView和ListView的区别。</p>    <h2>为什么会出现RecyclerView?</h2>    <p>RecyclerView并不会完全替代ListView(这点从ListView没有被标记为@Deprecated可以看出),两者的使用场景不一样。但是RecyclerView的出现会让很多开源项目被废弃,例如横向滚动的ListView, 横向滚动的GridView, 瀑布流控件,因为RecyclerView能够实现所有这些功能。</p>    <p>比如有一个需求是屏幕竖着的时候的显示形式是ListView,屏幕横着的时候的显示形式是2列的GridView,此时如果用RecyclerView,则通过设置LayoutManager一行代码实现替换。</p>    <h2>ListView vs RecyclerView</h2>    <p>ListView相比RecyclerView,有一些优点:</p>    <ul>     <li> <p>addHeaderView() , addFooterView() 添加头视图和尾视图。</p> </li>     <li> <p>通过”android:divider”设置自定义分割线。</p> </li>     <li> <p>setOnItemClickListener() 和 setOnItemLongClickListener() 设置点击事件和长按事件。</p> </li>    </ul>    <p>这些功能在RecyclerView中都没有直接的接口,要自己实现(虽然实现起来很简单),因此如果只是实现简单的显示功能,ListView无疑更简单。</p>    <p>RecyclerView相比ListView,有一些明显的优点:</p>    <ul>     <li> <p>默认已经实现了View的复用,不需要类似 if(convertView == null) 的实现,而且回收机制更加完善。</p> </li>     <li> <p>默认支持局部刷新。</p> </li>     <li> <p>容易实现添加item、删除item的动画效果。</p> </li>     <li> <p>容易实现拖拽、侧滑删除等功能。</p> </li>    </ul>    <p>RecyclerView是一个插件式的实现,对各个功能进行解耦,从而扩展性比较好。</p>    <h2>ListView实现局部刷新</h2>    <p>我们都知道ListView通过 adapter.notifyDataSetChanged() 实现ListView的更新,这种更新方法的缺点是全局更新,即对每个Item View都进行重绘。但事实上很多时候,我们只是更新了其中一个Item的数据,其他Item其实可以不需要重绘。</p>    <p>这里给出ListView实现局部更新的方法:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/bf0bb86d21aedc1f0ffc78fb6004a507.jpg"></p>    <p>可以看出,我们通过ListView的 getChildAt() 来获得需要更新的View,然后通过 getTag() 获得ViewHolder,从而实现更新。</p>    <h2>标准用法</h2>    <p>RecyclerView的标准实现步骤如下:</p>    <ul>     <li> <p>创建Adapter:创建一个继承 RecyclerView.Adapter<VH> 的Adapter类(VH是ViewHolder的类名),记为NormalAdapter。</p> </li>     <li> <p>创建ViewHolder:在NormalAdapter中创建一个继承 RecyclerView.ViewHolder 的静态内部类,记为VH。ViewHolder的实现和ListView的ViewHolder实现几乎一样。</p> </li>     <li> <p>在NormalAdapter中实现:</p>      <ul>       <li> <p>VH onCreateViewHolder(ViewGroup parent, int viewType) : 映射Item Layout Id,创建VH并返回。</p> </li>       <li> <p>void onBindViewHolder(VH holder, int position) : 为holder设置指定数据。</p> </li>       <li> <p>int getItemCount() : 返回Item的个数。</p> </li>      </ul> </li>    </ul>    <p>可以看出,RecyclerView将ListView中 getView() 的功能拆分成了 onCreateViewHolder() 和 onBindViewHolder() 。</p>    <p>基本的Adapter实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/44a7e60e38d31793303dcae4847cc276.jpg"></p>    <p>创建完Adapter,接着对RecyclerView进行设置,一般来说,需要为RecyclerView进行四大设置,也就是后文说的四大组成:Adapter(必选),Layout Manager(必选),Item Decoration(可选,默认为空), Item Animator(可选,默认为DefaultItemAnimator)。</p>    <p>需要注意的是在 onCreateViewHolder() 中,映射Layout必须为</p>    <pre>  <code class="language-java">View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);</code></pre>    <p>而不能是:</p>    <pre>  <code class="language-java">View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, null);</code></pre>    <p>如果要实现ListView的效果,只需要设置Adapter和Layout Manager,如下:</p>    <pre>  <code class="language-java">List<String> data = initData();  RecyclerView rv = (RecyclerView) findViewById(R.id.rv);  rv.setLayoutManager(new LinearLayoutManager(this));  rv.setAdapter(new NormalAdapter(data));</code></pre>    <p>ListView只提供了 notifyDataSetChanged() 更新整个视图,这是很不合理的。RecyclerView提供了 notifyItemInserted() , notifyItemRemoved() , notifyItemChanged() 等API更新单个或某个范围的Item视图。</p>    <h2>四大组成</h2>    <p>RecyclerView的四大组成是:</p>    <ul>     <li> <p>Adapter:为Item提供数据。</p> </li>     <li> <p>Layout Manager:Item的布局。</p> </li>     <li> <p>Item Animator:添加、删除Item动画。</p> </li>     <li> <p>Item Decoration:Item之间的Divider。</p> </li>    </ul>    <h3>Adapter</h3>    <p>Adapter的使用方式前面已经介绍了,功能就是为RecyclerView提供数据,这里主要介绍万能适配器的实现。其实万能适配器的概念在ListView就已经存在了,即 base-adapter-helper 。</p>    <p>这里我们只针对RecyclerView,聊聊万能适配器出现的原因。为了创建一个RecyclerView的Adapter,每次我们都需要去做重复劳动,包括重写 onCreateViewHolder() , getItemCount() 、创建ViewHolder,并且实现过程大同小异,因此万能适配器出现了,他能通过以下方式快捷地创建一个Adapter:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2ee302abb68c7feb7fb06b7c812055f5.jpg"></p>    <p>是不是很方便。当然复杂情况也可以轻松解决。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/16bfa4a02eab11f3b37f7f643f646176.jpg"></p>    <p>这里讲解下万能适配器的实现思路。</p>    <p>我们通过 public abstract class QuickAdapter<T> extends RecyclerView.Adapter<QuickAdapter.VH> 定义万能适配器QuickAdapter类,T是列表数据中每个元素的类型,QuickAdapter.VH是QuickAdapter的ViewHolder实现类,称为万能ViewHolder。</p>    <p>首先介绍QuickAdapter.VH的实现:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6a7017b7383329e3031dffb4f262903a.jpg"></p>    <p>其中的关键点在于通过 SparseArray<View> 存储item view的控件, getView(int id) 的功能就是通过id获得对应的View(首先在mViews中查询是否存在,如果没有,那么 findViewById() 并放入mViews中,避免下次再执行 findViewById() )。</p>    <p>QuickAdapter的实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5f84889beb93c88107a3a259f7f28a8f.jpg"></p>    <p>其中:</p>    <ul>     <li> <p>getLayoutId(int viewType) 是根据viewType返回布局ID。</p> </li>     <li> <p>convert() 做具体的bind操作。</p> </li>    </ul>    <p>就这样,万能适配器实现完成了。</p>    <h3>Item Decoration</h3>    <p>RecyclerView通过 addItemDecoration() 方法添加item之间的分割线。Android并没有提供实现好的Divider,因此任何分割线样式都需要自己实现。</p>    <p>方法是:创建一个类并继承RecyclerView.ItemDecoration,重写以下两个方法:</p>    <ul>     <li> <p>onDraw(): 绘制分割线。</p> </li>     <li> <p>getItemOffsets(): 设置分割线的宽、高。</p> </li>    </ul>    <p>Google在sample中给了一个参考的实现类:DividerItemDecoration,这里我们通过分析这个例子来看如何自定义Item Decoration。</p>    <p>首先看构造函数,构造函数中获得系统属性 android:listDivider ,该属性是一个Drawable对象。</p>    <p>因此如果要设置,则需要在value/styles.xml中设置:</p>    <pre>  <code class="language-java"><style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">      <item name="android:listDivider">@drawable/item_divider</item></style></code></pre>    <p>接着来看 getItemOffsets() 的实现:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7dfd2403fda3e4c8ea0ea8ed73bc5432.png"></p>    <p>这里只看 mOrientation == VERTICAL_LIST 的情况,outRect是当前item四周的间距,类似margin属性,现在设置了该item下间距为 mDivider.getIntrinsicHeight() 。</p>    <p>那么 getItemOffsets() 是怎么被调用的呢?</p>    <p>RecyclerView继承了ViewGroup,并重写了 measureChild() ,该方法在 onMeasure() 中被调用,用来计算每个child的大小,计算每个child大小的时候就需要加上 getItemOffsets() 设置的外间距:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9900fa6a30b673b194075589ee3c4393.png"></p>    <p>这里我们只考虑 mOrientation == VERTICAL_LIST 的情况,DividerItemDecoration的 onDraw() 实际上调用了 drawVertical() :</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5ef77475a5766f84b74af20ac59b67a8.jpg"></p>    <p>那么 onDraw() 是怎么被调用的呢?还有ItemDecoration还有一个方法 onDrawOver() ,该方法也可以被重写,那么 onDraw() 和 onDrawOver() 之间有什么关系呢?</p>    <p>我们来看下面的代码:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/66afe26ee1b8375a5795d0249d256888.jpg"></p>    <p>根据View的绘制流程,首先调用RecyclerView重写的 draw() 方法,随后 super.draw() 即调用View的 draw() ,该方法会先调用 onDraw() (这个方法在RecyclerView重写了),再调用 dispatchDraw() 绘制children。因此:ItemDecoration的 onDraw() 在绘制Item之前调用,ItemDecoration的 onDrawOver() 在绘制Item之后调用。</p>    <p>当然,如果只需要实现Item之间相隔一定距离,那么只需要为Item的布局设置margin即可,没必要自己实现ItemDecoration这么麻烦。</p>    <h3>Layout Manager</h3>    <p>LayoutManager负责RecyclerView的布局,其中包含了Item View的获取与回收。这里我们简单分析LinearLayoutManager的实现。</p>    <p>对于LinearLayoutManager来说,比较重要的几个方法有:</p>    <ul>     <li> <p>onLayoutChildren() : 对RecyclerView进行布局的入口方法。</p> </li>     <li> <p>fill() : 负责填充RecyclerView。</p> </li>     <li> <p>scrollVerticallyBy() :根据手指的移动滑动一定距离,并调用 fill() 填充。</p> </li>     <li> <p>canScrollVertically() 或 canScrollHorizontally() : 判断是否支持纵向滑动或横向滑动。</p> </li>    </ul>    <p>onLayoutChildren() 的核心实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0ea3299ef86a94c0a07e4815899f826e.jpg"></p>    <p>RecyclerView的回收机制有个重要的概念,即将回收站分为Scrap Heap和Recycle Pool,其中Scrap Heap的元素可以被直接复用,而不需要调用 onBindViewHolder() 。 detachAndScrapAttachedViews() 会根据情况,将原来的Item View放入Scrap Heap或Recycle Pool,从而在复用时提升效率。</p>    <p>fill() 是对剩余空间不断地调用 layoutChunk() ,直到填充完为止。 layoutChunk() 的核心实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e9b1c650ea5fd0e707783a290755e27b.png"></p>    <p>其中 next() 调用了 getViewForPosition(currentPosition) ,该方法是从RecyclerView的回收机制实现类Recycler中获取合适的View,在后文的回收机制中会介绍该方法的具体实现。</p>    <p>如果要自定义LayoutManager,可以参考:</p>    <p>创建一个 RecyclerView LayoutManager – Part 1</p>    <p>https://github.com/hehonghui/android-tech-frontier/blob/master/issue-9/%E5%88%9B%E5%BB%BA-RecyclerView-LayoutManager-Part-1.md</p>    <p>创建一个 RecyclerView LayoutManager – Part 2</p>    <p>https://github.com/hehonghui/android-tech-frontier/blob/master/issue-13/%E5%88%9B%E5%BB%BA-RecyclerView-LayoutManager-Part-2.md</p>    <p>创建一个 RecyclerView LayoutManager – Part 3</p>    <p>https://github.com/hehonghui/android-tech-frontier/blob/master/issue-13/%E5%88%9B%E5%BB%BA-RecyclerView-LayoutManager-Part-3.md</p>    <h3>Item Animator</h3>    <p>RecyclerView能够通过 mRecyclerView.setItemAnimator(ItemAnimator animator) 设置添加、删除、移动、改变的动画效果。RecyclerView提供了默认的ItemAnimator实现类:DefaultItemAnimator。这里我们通过分析DefaultItemAnimator的源码来介绍如何自定义Item Animator。</p>    <p>DefaultItemAnimator继承自SimpleItemAnimator,SimpleItemAnimator继承自ItemAnimator。</p>    <p>首先我们介绍ItemAnimator类的几个重要方法:</p>    <ul>     <li> <p><em>animateAppearance()</em> : 当ViewHolder出现在屏幕上时被调用(可能是add或move)。</p> </li>     <li> <p><em>animateDisappearance()</em> : 当ViewHolder消失在屏幕上时被调用(可能是remove或move)。</p> </li>     <li> <p><em>animatePersistence()</em> : 在没调用 notifyItemChanged() 和 notifyDataSetChanged() 的情况下布局发生改变时被调用。</p> </li>     <li> <p><em>animateChange()</em> : 在显式调用 notifyItemChanged() 或 notifyDataSetChanged() 时被调用。</p> </li>     <li> <p>runPendingAnimations(): RecyclerView动画的执行方式并不是立即执行,而是每帧执行一次,比如两帧之间添加了多个Item,则会将这些将要执行的动画Pending住,保存在成员变量中,等到下一帧一起执行。该方法执行的前提是前面 animateXxx() 返回true。</p> </li>     <li> <p>isRunning(): 是否有动画要执行或正在执行。</p> </li>     <li> <p>dispatchAnimationsFinished(): 当全部动画执行完毕时被调用。</p> </li>    </ul>    <p>上面用斜体字标识的方法比较难懂,不过没关系,因为Android提供了SimpleItemAnimator类(继承自ItemAnimator),该类提供了一系列更易懂的API,在自定义Item Animator时只需要继承SimpleItemAnimator即可:</p>    <ul>     <li> <p>animateAdd(ViewHolder holder): 当Item添加时被调用。</p> </li>     <li> <p>animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY): 当Item移动时被调用。</p> </li>     <li> <p>animateRemove(ViewHolder holder): 当Item删除时被调用。</p> </li>     <li> <p>animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop): 当显式调用 notifyItemChanged() 或 notifyDataSetChanged() 时被调用。</p> </li>    </ul>    <p>对于以上四个方法,注意两点:</p>    <ul>     <li> <p>当Xxx动画开始执行前(在 runPendingAnimations() 中)需要调用 dispatchXxxStarting(holder) ,执行完后需要调用 dispatchXxxFinished(holder) 。</p> </li>     <li> <p>这些方法的内部实际上并不是书写执行动画的代码,而是将需要执行动画的Item全部存入成员变量中,并且返回值为true,然后在 runPendingAnimations() 中一并执行。</p> </li>    </ul>    <p>DefaultItemAnimator类是RecyclerView提供的默认动画类。我们通过阅读该类源码学习如何自定义Item Animator。我们先看DefaultItemAnimator的成员变量:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/dd439e3d9722aac9dc559428ab9691d7.jpg"></p>    <p>DefaultItemAnimator实现了SimpleItemAnimator的 animateAdd() 方法,该方法只是将该item添加到mPendingAdditions中,等到 runPendingAnimations() 中执行。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a6d1f651871feda98ebd20ac71fef38b.png"></p>    <p>接着看 runPendingAnimations() 的实现,该方法是执行remove,move,change,add动画,执行顺序为:remove动画最先执行,随后move和change并行执行,最后是add动画。为了简化,我们将remove,move,change动画执行过程省略,只看执行add动画的过程,如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1a74254bf9f841acca0c360f69f29668.jpg"></p>    <p>为了防止在执行add动画时外面有新的add动画添加到mPendingAdditions中,从而导致执行add动画错乱,这里将mPendingAdditions的内容移动到局部变量additions中,然后遍历additions执行动画。</p>    <p>在 runPendingAnimations() 中, animateAddImpl() 是执行add动画的具体方法,其实就是将itemView的透明度从0变到1(在 animateAdd() 中已经将view的透明度变为0),实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/dec4db2f7976976a264ad1884237e6d8.jpg"></p>    <p>从DefaultItemAnimator类的实现来看,发现自定义Item Animator好麻烦,需要继承SimpleItemAnimator类,然后实现一堆方法。别急,recyclerview-animators解救你,原因如下:</p>    <p>首先,recyclerview-animators提供了一系列的Animator,比如FadeInAnimator,ScaleInAnimator。其次,如果该库中没有你满意的动画,该库提供了BaseItemAnimator类,该类继承自SimpleItemAnimator,进一步封装了自定义Item Animator的代码,使得自定义Item Animator更方便,你只需要关注动画本身。如果要实现DefaultItemAnimator的代码,只需要以下实现:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a19849b0d7b3d1e20b2ed3bdc7443729.jpg"></p>    <p>是不是比继承SimpleItemAnimator方便多了。</p>    <p>对于RecyclerView的Item Animator,有一个常见的坑就是”闪屏问题”。这个问题的描述是:当Item视图中有图片和文字,当更新文字并调用 notifyItemChanged() 时,文字改变的同时图片会闪一下。这个问题的原因是当调用 notifyItemChanged() 时,会调用DefaultItemAnimator的 animateChangeImpl() 执行change动画,该动画会使得Item的透明度从0变为1,从而造成闪屏。</p>    <p>解决办法很简单,在 rv.setAdapter() 之前调用 ((SimpleItemAnimator)rv.getItemAnimator()).setSupportsChangeAnimations(false) 禁用change动画。</p>    <h2>拓展RecyclerView</h2>    <h3>添加setOnItemClickListener接口</h3>    <p>RecyclerView默认没有像ListView一样提供 setOnItemClickListener() 接口,而 RecyclerView无法添加onItemClickListener最佳的高效解决方案 (http://blog.csdn.net/liaoinstan/article/details/51200600)这篇文章给出了通过 recyclerView.addOnItemTouchListener(...) 添加点击事件的方法,但我认为根本没有必要费这么大劲对外暴露这个接口,因为我们完全可以把点击事件的实现写在Adapter的 onBindViewHolder() 中,不暴露出来。具体方法就是通过:</p>    <pre>  <code class="language-java">   public void onBindViewHolder(VH holder, int position) {          holder.itemView.setOnClickListener(...);      }</code></pre>    <h3>添加HeaderView和FooterView</h3>    <p>RecyclerView默认没有提供类似 addHeaderView() 和 addFooterView() 的API,因此这里介绍如何优雅地实现这两个接口。</p>    <p>如果你已经实现了一个Adapter,现在想为这个Adapter添加 addHeaderView() 和 addFooterView() 接口,则需要在Adapter中添加几个Item Type,然后修改 getItemViewType() , onCreateViewHolder() , onBindViewHolder() , getItemCount() 等方法,并添加switch语句进行判断。那么如何在不破坏原有Adapter实现的情况下完成呢?</p>    <p>这里引入装饰器(Decorator)设计模式,该设计模式通过组合的方式,在不破话原有类代码的情况下,对原有类的功能进行扩展。</p>    <p>这恰恰满足了我们的需求。我们只需要通过以下方式为原有的Adapter(这里命名为NormalAdapter)添加 addHeaderView() 和 addFooterView() 接口:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2f814006061cba88865764f21ba68b6c.jpg"></p>    <p>是不是看起来特别优雅。具体实现思路其实很简单,创建一个继承 RecyclerView.Adapter<RecyclerView.ViewHolder> 的类,并重写常见的方法,然后通过引入ITEM TYPE的方式实现:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4b5b7ee206423553270d1641d31f9331.jpg"></p>    <h3>添加setEmptyView</h3>    <p>ListView提供了 setEmptyView() 设置Adapter数据为空时的View视图。RecyclerView虽然没提供直接的API,但是也可以很简单地实现。</p>    <ul>     <li> <p>创建一个继承RecyclerView的类,记为EmptyRecyclerView。</p> </li>     <li> <p>通过 getRootView().addView(emptyView) 将空数据时显示的View添加到当前View的层次结构中。</p> </li>     <li> <p>通过AdapterDataObserver监听RecyclerView的数据变化,如果adapter为空,那么隐藏RecyclerView,显示EmptyView。</p> </li>    </ul>    <p>具体实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/99ae25b3b54aab8acad86a3c954dc7bb.jpg"></p>    <h3>拖拽、侧滑删除</h3>    <p>Android提供了ItemTouchHelper类,使得RecyclerView能够轻易地实现滑动和拖拽,此处我们要实现上下拖拽和侧滑删除。首先创建一个继承自 ItemTouchHelper.Callback 的类,并重写以下方法:</p>    <ul>     <li> <p>getMovementFlags() : 设置支持的拖拽和滑动的方向,此处我们支持的拖拽方向为上下,滑动方向为从左到右和从右到左,内部通过 makeMovementFlags() 设置。</p> </li>     <li> <p>onMove() : 拖拽时回调。</p> </li>     <li> <p>onSwiped() : 滑动时回调。</p> </li>     <li> <p>onSelectedChanged() : 状态变化时回调,一共有三个状态,分别是ACTION_STATE_IDLE(空闲状态),ACTION_STATE_SWIPE(滑动状态),ACTION_STATE_DRAG(拖拽状态)。此方法中可以做一些状态变化时的处理,比如拖拽的时候修改背景色。</p> </li>     <li> <p>clearView() : 用户交互结束时回调。此方法可以做一些状态的清空,比如拖拽结束后还原背景色。</p> </li>     <li> <p>isLongPressDragEnabled() : 是否支持长按拖拽,默认为true。如果不想支持长按拖拽,则重写并返回false。</p> </li>    </ul>    <p>具体实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6033dfce241fdf96cbb162522a5940f2.jpg"></p>    <p>然后通过以下代码为RecyclerView设置该滑动、拖拽功能:</p>    <pre>  <code class="language-java">ItemTouchHelper helper = new ItemTouchHelper(new SimpleItemTouchCallback(adapter, data));  helper.attachToRecyclerView(recyclerview);</code></pre>    <p>前面拖拽的触发方式只有长按,如果想支持触摸Item中的某个View实现拖拽,则核心方法为 helper.startDrag(holder) 。首先定义接口:</p>    <pre>  <code class="language-java">interface <em>OnStartDragListener{ void startDrag(RecyclerView.ViewHolder holder); }</em></code></pre>    <p>然后让Activity实现该接口:</p>    <pre>  <code class="language-java">public MainActivity extends Activity implements OnStartDragListener{      ...      public void startDrag(RecyclerView.ViewHolder holder) {          mHelper.startDrag(holder);      }  }</code></pre>    <p>如果要对ViewHolder的text对象支持触摸拖拽,则在Adapter中的 onBindViewHolder() 中添加:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d5c4f50be7de802f8aee7da81ce48586.png"></p>    <p>其中mListener是在创建Adapter时将实现OnStartDragListener接口的Activity对象作为参数传进来。</p>    <h2>回收机制</h2>    <h3>ListView回收机制</h3>    <p>ListView为了保证Item View的复用,实现了一套回收机制,该回收机制的实现类是RecycleBin,他实现了两级缓存:</p>    <ul>     <li> <p>View[] mActiveViews : 缓存屏幕上的View,在该缓存里的View不需要调用 getView() 。</p> </li>     <li> <p>ArrayList<View>[] mScrapViews; : 每个Item Type对应一个列表作为回收站,缓存由于滚动而消失的View,此处的View如果被复用,会以参数的形式传给 getView() 。</p> </li>    </ul>    <p>接下来我们通过源码分析ListView是如何与RecycleBin交互的。其实ListView和RecyclerView的layout过程大同小异,ListView的布局函数是 layoutChildren() ,实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e7923108012ebab1a279b6025630d553.jpg"></p>    <p>其中 fillXxx() 实现了对Item View进行填充,该方法内部调用了 makeAndAddView() ,实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/64aa239743ecfe80b85cd495715c56ea.png"></p>    <p>其中, getActiveView() 是从mActiveViews中获取合适的View,如果获取到了,则直接返回,而不调用 obtainView() ,这也印证了如果从mActiveViews获取到了可复用的View,则不需要调用 getView() 。</p>    <p>obtainView() 是从mScrapViews中获取合适的View,然后以参数形式传给了 getView() ,实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6db12f300ad7c06f8f1fe334d0d061f4.png"></p>    <p>接下去我们介绍 getScrapView(position) 的实现,该方法通过position得到Item Type,然后根据Item Type从mScrapViews获取可复用的View,如果获取不到,则返回null,具体实现如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/194c347c3048cb19a248e360f106b384.jpg"></p>    <h3>RecyclerView回收机制</h3>    <p>RecyclerView和ListView的回收机制非常相似,但是ListView是以View作为单位进行回收,RecyclerView是以ViewHolder作为单位进行回收。Recycler是RecyclerView回收机制的实现类,他实现了四级缓存:</p>    <ul>     <li> <p>mAttachedScrap: 缓存在屏幕上的ViewHolder。</p> </li>     <li> <p>mCachedViews: 缓存屏幕外的ViewHolder,默认为2个。ListView对于屏幕外的缓存都会调用 getView() 。</p> </li>     <li> <p>mViewCacheExtensions: 需要用户定制,默认不实现。</p> </li>     <li> <p>mRecyclerPool: 缓存池,多个RecyclerView共用。</p> </li>    </ul>    <p>在上文Layout Manager中已经介绍了RecyclerView的layout过程,但是一笔带过了 getViewForPosition() ,因此此处介绍该方法的实现。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6e550217c925683303b232c4799a9ca6.jpg"></p>    <p>从上述实现可以看出,依次从mAttachedScrap, mCachedViews, mViewCacheExtension, mRecyclerPool寻找可复用的ViewHolder,如果是从mAttachedScrap或mCachedViews中获取的ViewHolder,则不会调用 onBindViewHolder() ,mAttachedScrap和mCachedViews也就是我们所说的Scrap Heap;而如果从mViewCacheExtension或mRecyclerPool中获取的ViewHolder,则会调用 onBindViewHolder() 。</p>    <p>RecyclerView局部刷新的实现原理也是基于RecyclerView的回收机制,即能直接复用的ViewHolder就不调用 onBindViewHolder() 。</p>    <h2>嵌套滑动机制</h2>    <p>Android 5.0推出了嵌套滑动机制,在之前,一旦子View处理了触摸事件,父View就没有机会再处理这次的触摸事件,而嵌套滑动机制解决了这个问题,能够实现如下效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/711c0c889bd9c425d0eff956f33db807.gif"></p>    <p>为了支持嵌套滑动,子View必须实现NestedScrollingChild接口,父View必须实现NestedScrollingParent接口,而RecyclerView实现了NestedScrollingChild接口,而CoordinatorLayout实现了NestedScrollingParent接口,上图是实现CoordinatorLayout嵌套RecyclerView的效果。</p>    <p>为了实现上图的效果,需要用到的组件有:</p>    <ul>     <li> <p>CoordinatorLayout: 布局根元素。</p> </li>     <li> <p>AppBarLayout: 包裹的内容作为应用的Bar。</p> </li>     <li> <p>CollapsingToolbarLayout: 实现可折叠的ToolBar。</p> </li>     <li> <p>ToolBar: 代替ActionBar。</p> </li>    </ul>    <p>实现中需要注意的点有:</p>    <ul>     <li> <p>我们为ToolBar的 app:layout_collapseMode 设置为pin,表示折叠之后固定在顶端,而为ImageView的 app:layout_collapseMode 设置为parallax,表示视差模式,即渐变的效果。</p> </li>     <li> <p>为了让RecyclerView支持嵌套滑动,还需要为它设置 app:layout_behavior="@string/appbar_scrolling_view_behavior" 。</p> </li>     <li> <p>为CollapsingToolbarLayout设置 app:layout_scrollFlags="scroll|exitUntilCollapsed" ,其中scroll表示滚动出屏幕,exitUntilCollapsed表示退出后折叠。</p> </li>    </ul>    <p>具体实现参见Demo6。</p>    <h2>回顾</h2>    <p>回顾整篇文章,发现我们已经实现了RecyclerView的很多扩展功能,包括:打造万能适配器、添加Item事件、添加头视图和尾视图、设置空布局、侧滑拖拽。BaseRecyclerViewAdapterHelper是一个比较火的RecyclerView扩展库,仔细一看发现,这里面80%的功能在我们这篇文章中都实现了。</p>    <p> </p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s/CzrKotyupXbYY6EY2HP_dA</p>    <p> </p>