RecyclerView技术栈

作者: 小鄧子 | 来源:发表于2016-02-01 14:24 被阅读25249次

    概述

    随着2014年Google IO的召开,Android L Preview版随之发布,对于开发着来说,带来了性能上的改善,而对于消费者来说,得到了体验上的提升。我想,无论是开发者还是使用者,一定都非常喜欢这次的版本跟新。

    同时,这次也带来了两个全新的View控件:RecyclerViewCardView。这篇文章将重点介绍RecyclerView,它有许多内部类和接口。接下来,我将介绍它们的功能,已经如何使用。

    当然,在这之前,我要声明的是:RecyclerView 是Support Library的一部分。所以只需要在app/build.gradle中添加以下依赖,便能立即使用:

    dependencies {
        compile 'com.android.support:recyclerview-v7:23.2.0'
    }
    

    然后点击“Sync Project with Gradle files”,让IDE去下载适当的资源文件。

    为什么命名为RecyclerView?

    先让我们来看看Google在L Preview中是如何定义RecyclerView的:

    A flexible view for providing a limited window into a large data set.
    (能够在有限的窗口中展示大数据集合的灵活视图。)

    所以我们能够理解为,RecyclerView一个恰当的使用场景是:由于尺寸限制,用户的设备不能一次性展现所有条目,用户需要上下滚动以查看更多条目。滚出可见区域的条目将被回收,并在下一个条目可见的时候被复用。

    我们可以从下图中得到更直观的解释:

    左边的图是数据初始化后的示例,当向上滚动视图的时候,当条目不可见之后将被回收。右图中红色区域内的两条不可见条目,将被放到缓存队列中以便新的条目可见时进行复用。

    对于减少内存开销和CPU的计算,缓存条目是一个非常有用的方法,因为这意味着我们不必每次都创建新的条目,从而减小内存开销和CPU的计算,而且还能够有效降低屏幕的卡顿,保证滑动的顺滑和16ms准则。

    看到这里,你可能不禁会问:并没有什么新东西啊,这和ListView有什么区别呀?我们已经使用ListView很长一段时间了呀,它一样可以做到呀。不过,视图回收本身并不是什么新鲜事。但是回想之前我们写的ListView,无论从它的的性能表现着手,还是语法的书写,甚至数据的绑定都未免略显臃肿。那么现在,我们将再也不会出现上述症状,因为Google提供了一个更好,更灵活的控件——RecyclerView

    OK,从现在开始,让我们一步一步,开始了解它。

    结构

    如果你想使用RecyclerView,需要做以下操作:

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

    • ViewHolder - 持有所有的用于绑定数据或者需要操作的View

    • LayoutManager - 负责摆放视图等相关操作

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

    • ItemAnimator - 为Item的一般操作添加动画效果,如,增删条目等

    我们可以从下图更直观的了解到RecyclerView的基本结构:

    由此可见,想要在ListView中实现条目的增删动画是一件非常困难的事情,但是RecyclerView为我们提供了很好的便利。而且RecyclerView增强了ViewHolder设计模式,这在当前所使用的ListView中是不曾有的。

    与传统ListView比较

    RecyclerView与老前辈ListView的不同点,主要在于以下几个特性:

    • Adapter中的ViewHolder模式 - 对于ListView来说,通过创建ViewHolder来提升性能并不是必须的。因为ListView并没有严格的ViewHolder设计模式。但是在使用RecyclerView的时候,Adapter必须实现至少一个ViewHolder,必须遵循ViewHolder设计模式。

    • 定制Item条目 - ListView只能实现垂直线性排列的列表视图,与之不同的是,RecyclerView可以通过设置RecyclerView.LayoutManager来定制不同风格的视图,比如水平滚动列表或者不规则的瀑布流列表。

    • Item动画 - 在ListView中没有提供任何方法或者接口,方便开发者实现Item的增删动画。相反地,可以通过设置RecyclerViewRecyclerView.ItemAnimator来为条目增加动画效果。

    • 设置数据源 - 在LisView中针对不同数据封装了各种类型的Adapter,比如用来处理数组的ArrayAdapter和用来展示Database结果的CursorAdapter。相反地,在RecyclerView中必须自定义实现RecyclerView.Adapter并为其提供数据集合。

    • 设置条目分割线 - 在ListView中可以通过设置android:divider属性来为两个Item间设置分割线。如果想为RecyclerView添加此效果,则必须使用RecyclerView.ItemDecoration,这种实现方式不仅更灵活,而且样式也更加丰富。

    • 设置点击事件 - 在ListView中存在AdapterView.OnItemClickListener接口,用来绑定条目的点击事件。但是,很遗憾的是在RecyclerView中,并没有提供这样的接口,不过,提供了另外一个接口RcyclerView.OnItemTouchListener,用来响应条目的触摸事件。

    RecyclerView组件

    RecyclerView.Adapter

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

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

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

    另外我们还需要重写另一个方法,像ListView-Adapter那样,同样地告诉RecyclerView-Adapter列表Items的总数:

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

    RecyclerView.ViewHolder

    ViewHolder的基本用法是用来存放View对象。Android团队很早之前就推荐使用“ViewHolder设计模式”,但实际上他们并没有把这种概念强加给开发者,而且也没有要求开发者在Adapter中必须使用ViewHolder pattern。那么现在对于这种新型的RecyclerView.Adapter,我们必须实现并使用它。

    另外值得一提的是,可以通过打印ViewHolder.toString来获取更多有效信息:

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("ViewHolder{" +
                    Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId +
                    ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
            if (isScrap()) sb.append(" scrap");
            if (isInvalid()) sb.append(" invalid");
            if (!isBound()) sb.append(" unbound");
            if (needsUpdate()) sb.append(" update");
            if (isRemoved()) sb.append(" removed");
            if (shouldIgnore()) sb.append(" ignored");
            if (isChanged()) sb.append(" changed");
            if (isTmpDetached()) sb.append(" tmpDetached");
            if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
            if (isAdapterPositionUnknown()) sb.append("undefined adapter position");
            if (itemView.getParent() == null) sb.append(" no parent");
            sb.append("}");
            return sb.toString();
        }
    

    因此,一个基本的RecyclerView.Adapter如下:

    public class SimplerItemAdapter extends RecyclerView.Adapter<SimplerItemAdapter.SimpleItemViewHolder> {
    
      private List<String> items;
    
      public SimplerItemAdapter(@NonNull List<String> dateItems) {
        this.items = (dateItems != null ? dateItems : new ArrayList<String>());
      }
    
      @Override public SimpleItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
        return new SimpleItemViewHolder(itemView);
      }
    
      @Override public void onBindViewHolder(SimpleItemViewHolder viewHolder, int position) {
        viewHolder.textView.setText(items.get(position));
      }
    
      @Override public int getItemCount() {
        return (this.items != null) ? this.items.size() : 0;
      }
    
      protected final static class SimpleItemViewHolder extends RecyclerView.ViewHolder {
        protected TextView textView;
    
        public SimpleItemViewHolder(View itemView) {
          super(itemView);
          this.textView = (TextView) itemView.findViewById(R.id.text);
        }
      }
    }
    

    RecyclerView.LayoutManager

    LayoutManager的职责是摆放Item的位置,并且负责决定何时回收和重用Item。

    必须为RecyclerView指定LayoutManager,否则会出现以下异常:

     AndroidRuntime java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.support.v7.widget.RecyclerView$LayoutManager.onMeasure(android.support.v7.widget.RecyclerView$Recycler, android.support.v7.widget.RecyclerView$State, int, int)’ on a null object reference
    
    • LinearLayoutManager 水平或者垂直的Item视图。

    • GridLayoutManager 网格Item视图。

    • StaggeredGridLayoutManager 交错的网格Item视图。

    当然还有一些很实用的API:

    • findFirstVisibleItemPosition() 返回当前第一个可见Item的position
    • findFirstCompletelyVisibleItemPosition() 返回当前第一个完全可见Item的position
    • findLastVisibleItemPosition() 返回当前最后一个可见Item的position
    • findLastCompletelyVisibleItemPosition() 返回当前最后一个完全可见Item的position

    LayoutManager当前有且仅有一个抽象函数:

    public LayoutParams generateDefaultLayoutParams()
    

    另外值得注意的是,自定义LayoutManager还应该实现以下方法:

       /**
         * Scroll to the specified adapter position.
         *
         * Actual position of the item on the screen depends on the LayoutManager implementation.
         * @param position Scroll to this adapter position.
         */
        public void scrollToPosition(int position) {
            if (DEBUG) {
                Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
            }
        }
    

    RecyclerView.ItemDecoration

    通过设置recyclerView.addItemDecoration(new DividerDecoration(this));来改变Item之间的偏移量或者对Item进行装饰。

    当然,你也可以对RecyclerView设置多个ItemDecoration,列表展示的时候会遍历所有的ItemDecoration并调用里面的绘制方法,对Item进行装饰。

    RecyclerView.ItemDecoration是一个抽象类,可以通过重写以下三个方法,来实现Item之间的偏移量或者装饰效果:

    • public void onDraw(Canvas c, RecyclerView parent) 装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡

    • public void onDrawOver(Canvas c, RecyclerView parent) 装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上

    • public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量。

    RecyclerView.ItemAnimator

    ItemAnimator能够帮助Item实现独立的动画。

    ItemAnimator作触发于以下三种事件:

    1. 某条数据被插入到数据集合中
    2. 从数据集合中移除某条数据
    3. 更改数据集合中的某条数据

    幸运的是,在Android中默认实现了一个DefaultItemAnimator,我们可以通过以下代码为Item增加动画效果:

    recyclerView.setItemAnimator(new DefaultItemAnimator());
    

    在之前的版本中,当时据集合发生改变时,我们通过调用.notifyDataSetChanged(),来刷新列表,因为这样做会触发列表的重绘,所以并不会出现任何动画效果,因此需要调用一些以notifyItem*()作为前缀的特殊方法,比如:

    • public final void notifyItemInserted(int position) 向指定位置插入Item

    • public final void notifyItemRemoved(int position) 移除指定位置Item

    • public final void notifyItemChanged(int position) 更新指定位置Item

    Listeners

    很遗憾,RecyclerView并没有像ListView那样提供以下两个Item的点击监听事件

    • public void setOnItemClickListener(@Nullable OnItemClickListener listener) Item点击事件监听

    • public void setOnItemLongClickListener(OnItemLongClickListener listener) Item长按事件监听

    但是存在这样一个触摸事件的监听RecyclerView.OnItemTouchListener虽然变得更灵活,但是对应的代码量和书写难度却有了一定的增长,至少对我是这样的。

    至此,所有与本文章相关的代码都可以从Github上获取到,另外这个仓库中还有一份本人精心制作的PPT,可供参考。

    参考资料:

    Codepath - Codepath Website

    A First Glance at Android’s RecyclerView - WolframRittmeyer

    相关文章

      网友评论

      • 74cf8659ae9a:不错不错,收藏了。

        推荐下,分库分表中间件 Sharding-JDBC 源码解析 17 篇:http://t.cn/R0UfGFT


      • developerHaoz:基本上覆盖了 RecyclerView 所有的知识点,作者有心了。希望能更深入一点,这文章读了有点不够过瘾啊,哈哈。
      • Maat红飞:感谢
      • 桂林的小河:和recyclerView相关的,绝对可以减少70%开发时间 http://www.jianshu.com/p/3dfc44155bcf
      • e174ef92b5e8:弱弱的问下怎么混淆这个lib?
      • sugaryaruan:干货不够啊,求深入
      • dhe:您好最近在学习这个控件,想请教个问题。我写了一个很简单的demo,基本就是仿照listview的。
        但是只显示出来了第一项,很郁闷。请问您有没有遇到过这个问题,帮忙解决一下。
      • e6e85ebd3b2d:来签到グッ!(๑•̀ㅂ•́)و✧
      • wayne4106:深入浅出,好
      • 氹鹿:凑100
      • Rainbow冰糖葫芦娃:把如此复杂的控件写得浅显易懂,节约了我去看英文文档的大量时间 :+1:
      • 后端沉思录:写的不错!
      • 不可不知不用:写的真不错。
        应该也分享 下,上拉下拉刷新技术
      • 9dd419daa899:小橙子,膜拜啊,总结的有条有理。。。
      • 00e0275feac3:写的真不错。
      • 静默加载:Android L Preview就出了RecycleView,现在我使用的时候compileSDKVersion要是23呢
      • Nickyzhang:讲的很清晰
      • APOLOIZE:继续努力
      • bhfo:之前一直没有用到layoutmanager,省功夫
      • Tikitoo:感觉看着正意犹未尽地时候,发现已经读完了。
        小鄧子:@Tikitoo 这个是搭配着ppt来讲的,如果有可能,还会有第二期。所以比较短,而且还大部分来自参考的译文。
      • zayn_xu:博主,OnItemTouchListener这个好像不是这样用的。我看到的一般写法都是在viewHolder.oncreate里面直接在view上加的onclick事件,而OnItemTouchListener处理一些侧滑删除等比较复杂的界面效果,不涉及功能业务。
      • 泥阿布_Abner:棒棒哒
      • 程序亦非猿:棒棒的
      • HaKu:对回收几种状态变换不太明白,scrap,dirty转变 是根据离开屏幕时间,还是距离window边缘距离转化?
      • 食梦兽:我见过的写得最好的RecyclerView介绍
      • 齐昕:易懂,33个赞,多给你一个,让你骄傲!
        小鄧子:@齐昕 哇哦,那我能开心好几天了。
      • 68768b474bfc:非常有条理的总结,草稿都如此,正文确实让人期待
        小鄧子:@TellH 正文来啦,其实没啥变化。。。。
      • 6bb2c998229e:支持,小鄧子!

      本文标题:RecyclerView技术栈

      本文链接:https://www.haomeiwen.com/subject/exnlkttx.html