RecyclerView问题汇总

目录介绍

  • 25.0.0.0 请说一下RecyclerView?adapter的做用是什么,几个方法是作什么用的?如何理解adapter订阅者模式?
  • 25.0.0.1 ViewHolder的做用是什么?如何理解ViewHolder的复用?何时中止调用onCreateViewHolder?
  • 25.0.0.2 ViewHolder封装如何对findViewById优化?ViewHolder中为什么使用SparseArray替代HashMap存储viewId?
  • 25.0.0.3 LayoutManager做用是什么?LayoutManager样式有哪些?setLayoutManager源码里作了什么?
  • 25.0.0.4 SnapHelper主要是作什么用的?SnapHelper是怎么实现支持RecyclerView的对齐方式?
  • 25.0.0.5 SpanSizeLookup的做用是干什么的?SpanSizeLookup如何使用?SpanSizeLookup实现原理如何理解?
  • 25.0.0.6 ItemDecoration的用途是什么?自定义ItemDecoration有哪些重写方法?分析一下addItemDecoration()源码?
  • 25.0.0.7 上拉加载更多的功能是如何作的?添加滚动监听事件须要注意什么问题?网格布局上拉加载如何优化?
  • 25.0.0.8 RecyclerView绘制原理如何理解?性能优化本质是什么?RecyclerView绘制原理过程大概是怎样的?
  • 25.0.0.9 RecyclerView的Recyler是如何实现ViewHolder的缓存?如何理解recyclerView三级缓存是如何实现的?
  • 25.0.1.0 屏幕滑动(状态是item状态可见,不可见,即将可见变化)时三级缓存是如何理解的?adapter中的几个方法是如何变化?
  • 25.0.1.1 SnapHelper有哪些重要的方法,其做用就是是什么?LinearSnapHelper中是如何实现滚动中止的?
  • 25.0.1.2 LinearSnapHelper代码中calculateDistanceToFinalSnap做用是什么?那么out[0]和out[1]分别指什么?
  • 25.0.1.3 如何实现能够设置分割线的颜色,宽度,以及到左右两边的宽度间距的自定义分割线,说一下思路?
  • 25.0.1.4 如何实现复杂type首页需求?若是不封装会出现什么问题和弊端?如何提升代码的简便性和高效性?
  • 25.0.1.5 关于item条目点击事件在onCreateViewHolder中写和在onBindViewHolder中写有何区别?如何优化?
  • 25.0.1.6 RecyclerView滑动卡顿缘由有哪些?如何解决嵌套布局滑动冲突?如何解决RecyclerView实现画廊卡顿?
  • 25.0.1.7 RecyclerView常见的优化有哪些?实际开发中都是怎么作的,优化先后对比性能上有何提高?
  • 25.0.1.8 如何解决RecyclerView嵌套RecyclerView条目自动上滚的Bug?如何解决ScrollView嵌套RecyclerView滑动冲突?
  • 25.0.1.9 如何处理ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager?如何解决RecyclerView使用Glide加载图片致使图片错乱问题?

给本身相个亲

系列博客

  • 00.RecyclerView复杂封装库
    • 几乎融合了该系列博客中绝大部分的知识点,欢迎一遍看博客一遍实践,一步步从简单实现功能强大的库
  • 01.RecyclerView
    • RecycleView的结构,RecyclerView简单用法介绍
  • 02.Adapter
    • RecyclerView.Adapter扮演的角色,通常经常使用的重写方法说明,数据变动通知之观察者模式,查看.notifyChanged();源码
  • 03.ViewHolder
    • ViewHolder的做用,如何理解对于ViewHolder对象的数量“够用”以后就中止调用onCreateViewHolder方法,ViewHolder简单封装
  • 04.LayoutManager
    • LayoutManager做用是什么?setLayoutManager源码分析
  • 05.SnapHelper
    • SnapHelper做用,什么是Fling操做 ,SnapHelper类重要的方法,
  • 06.ItemTouchHelper
  • 07.SpanSizeLookup
    • SpanSizeLookup如何使用,同时包含列表,2列的网格,3列的网格如何优雅实现?
  • 08.ItemDecoration
    • ItemDecoration的用途,addItemDecoration()源码分析
  • 09.RecycledViewPool
    • RecyclerViewPool用于多个RecyclerView之间共享View。
  • 10.ItemAnimator
    • 官方有一个默认Item动画类DafaultItemAnimator,其中DefaultItemAnimator继承了SimpleItemAnimator,在继承了RecyclerView.ItemAnimator,它是如何实现动画呢?
  • 11.RecyclerView上拉加载
    • 添加recyclerView的滑动事件,上拉加载分页数据,设置上拉加载的底部footer布局,显示和隐藏footer布局
  • 12.RecyclerView缓存原理
    • RecyclerView作性能优化要说复杂也复杂,好比说布局优化,缓存,预加载,复用池,刷新数据等等
  • 13.SnapHelper源码分析
    • SnapHelper旨在支持RecyclerView的对齐方式,也就是经过计算对齐RecyclerView中TargetView 的指定点或者容器中的任何像素点。
  • 16.自定义SnapHelper
    • 自定义SnapHelper
  • 18.ItemTouchHelper 实现交互动画
    • 须要自定义类实现ItemTouchHelper.Callback类
  • 19.自定义ItemDecoration分割线
    • 须要自定义类实现RecyclerView.ItemDecoration类,并选择重写合适方法
  • 21.RecyclerView优化处理
    • RecyclerView滑动卡顿缘由有哪些?如何解决嵌套布局滑动冲突?如何解决RecyclerView实现画廊卡顿?
  • 22.RecyclerView问题汇总
    • getLayoutPosition()和getAdapterPosition()的区别
  • 23.RecyclerView滑动冲突
    • 01.如何判断RecyclerView控件滑动到顶部和底部
    • 02.RecyclerView嵌套RecyclerView 条目自动上滚的Bug
    • 03.ScrollView嵌套RecyclerView滑动冲突
    • 04.ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager
    • 05.RecyclerView嵌套RecyclerView的滑动冲突问题
    • 06.RecyclerView使用Glide加载图片致使图片错乱问题解决
  • 24.ScrollView嵌套RecyclerView问题
    • 要实如今NestedScrollView中嵌入一个或多个RecyclerView,会出现滑动冲突,焦点抢占,显示不全等。如何处理?
  • 25.RecyclerView封装库和综合案例
    • 自定义支持上拉加载更多【加载中,加载失败[好比没有更多数据],加载异常[无网络],加载成功等多种状态】,下拉刷新,能够实现复杂的状态页面,支持自由切换状态【加载中,加载成功,加载失败,没网络等状态】的控件,拓展功能[支持长按拖拽,侧滑删除]能够选择性添加。具体使用方法,能够直接参考demo案例。

25.0.0.0 请说一下RecyclerView?adapter的做用是什么,几个方法是作什么用的?如何理解adapter订阅者模式?

  • 关于RecyclerView,你们都已经很熟悉了,用途十分普遍,大概结构以下所示
    • RecyclerView.Adapter - 处理数据集合并负责绑定视图
    • ViewHolder - 持有全部的用于绑定数据或者须要操做的View
    • LayoutManager - 负责摆放视图等相关操做
    • ItemDecoration - 负责绘制Item附近的分割线
    • ItemAnimator - 为Item的通常操做添加动画效果,如,增删条目等
  • 如图所示,直观展现结构
    • image
  • adapter的做用是什么
    • RecyclerView.Adapter扮演的角色
    • 一是,根据不一样ViewType建立与之相应的的Item-Layout
    • 二是,访问数据集合并将数据绑定到正确的View上
  • 几个方法是作什么用的
    • 通常经常使用的重写方法有如下这么几个:博客
    public VH onCreateViewHolder(ViewGroup parent, int viewType)
    建立Item视图,并返回相应的ViewHolder
    public void onBindViewHolder(VH holder, int position)
    绑定数据到正确的Item视图上。
    public int getItemCount()
    返回该Adapter所持有的Itme数量
    public int getItemViewType(int position)
    用来获取当前项Item(position参数)是哪一种类型的布局
    复制代码
  • 如何理解adapter订阅者模式
    • 当时据集合发生改变时,咱们经过调用.notifyDataSetChanged(),来刷新列表,由于这样作会触发列表的重绘。
    • 注意这里须要理解什么是订阅者模式……
    • a.首先看.notifyDataSetChanged()源码
      public final void notifyDataSetChanged() {
          mObservable.notifyChanged();
      }
      复制代码
    • b.接着查看.notifyChanged();源码
      • 被观察者AdapterDataObservable,内部持有观察者AdapterDataObserver集合
      static class AdapterDataObservable extends Observable<AdapterDataObserver> {
          public boolean hasObservers() {
              return !mObservers.isEmpty();
          }
      
          public void notifyChanged() {
              for (int i = mObservers.size() - 1; i >= 0; i--) {
                  mObservers.get(i).onChanged();
              }
          }
      
          public void notifyItemRangeChanged(int positionStart, int itemCount) {
              notifyItemRangeChanged(positionStart, itemCount, null);
          }
      
          public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
              for (int i = mObservers.size() - 1; i >= 0; i--) {
                  mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
              }
          }
      
          public void notifyItemRangeInserted(int positionStart, int itemCount) {
              for (int i = mObservers.size() - 1; i >= 0; i--) {
                  mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
              }
          }
      }
      复制代码
      • 观察者AdapterDataObserver,具体实现为RecyclerViewDataObserver,当数据源发生变动时,及时响应界面变化
      public static abstract class AdapterDataObserver {
          public void onChanged() {
              // Do nothing
          }
      
          public void onItemRangeChanged(int positionStart, int itemCount) {
              // do nothing
          }
      
          public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
              onItemRangeChanged(positionStart, itemCount);
          }
      }
      复制代码
    • c.接着查看setAdapter()源码中的setAdapterInternal(adapter, false, true)方法
      public void setAdapter(Adapter adapter) {
          // bail out if layout is frozen
          setLayoutFrozen(false);
          setAdapterInternal(adapter, false, true);
          requestLayout();
      }
      复制代码
      • setAdapterInternal(adapter, false, true)源码
      private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
              boolean removeAndRecycleViews) {
          if (mAdapter != null) {
              mAdapter.unregisterAdapterDataObserver(mObserver);
              mAdapter.onDetachedFromRecyclerView(this);
          }
          if (!compatibleWithPrevious || removeAndRecycleViews) {
              removeAndRecycleViews();
          }
          mAdapterHelper.reset();
          final Adapter oldAdapter = mAdapter;
          mAdapter = adapter;
          if (adapter != null) {
              //注册一个观察者RecyclerViewDataObserver
              adapter.registerAdapterDataObserver(mObserver);
              adapter.onAttachedToRecyclerView(this);
          }
          if (mLayout != null) {
              mLayout.onAdapterChanged(oldAdapter, mAdapter);
          }
          mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
          mState.mStructureChanged = true;
          markKnownViewsInvalid();
      }
      复制代码
    • d.notify……方法被调用,刷新数据
      • 当数据变动时,调用notify**方法时,Adapter内部的被观察者会遍历通知已经注册的观察者的对应方法,这时界面就会响应变动。博客

25.0.0.1 ViewHolder的做用是什么?如何理解ViewHolder的复用?何时中止调用onCreateViewHolder?

  • ViewHolder做用大概有这些:
    • adapter应当拥有ViewHolder的子类,而且ViewHolder内部应当存储一些子view,避免时间代价很大的findViewById操做
    • 其RecyclerView内部定义的ViewHolder类包含不少复杂的属性,内部使用场景也有不少,而咱们常用的也就是onCreateViewHolder()方法和onBindViewHolder()方法,onCreateViewHolder()方法在RecyclerView须要一个新类型。item的ViewHolder时调用来建立一个ViewHolder,而onBindViewHolder()方法则当RecyclerView须要在特定位置的item展现数据时调用。博客
  • 如何理解ViewHolder的复用
    • 在复写RecyclerView.Adapter的时候,须要咱们复写两个方法:博客
      • onCreateViewHolder
      • onBindViewHolder
      • 这两个方法从字面上看就是建立ViewHolder和绑定ViewHolder的意思
    • 复用机制是怎样的?
      • 模拟场景:只有一种ViewType,上下滑动的时候须要的ViewHolder种类是只有一种,可是须要的ViewHolder对象数量并不止一个。因此在后面建立了9个ViewHolder以后,须要的数量够了,不管怎么滑动,都只须要复用之前建立的对象就好了。那么逗比程序员们思考一下,为何会出现这种状况呢
      • 看到了下面log以后,第一反应是在这个ViewHolder对象的数量“够用”以后就中止调用onCreateViewHolder方法,可是onBindViewHolder方法每次都会调用的
      • image
    • 查看一下createViewHolder源代码
      • 发现这里并无限制
      public final VH createViewHolder(ViewGroup parent, int viewType) {
          TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
          final VH holder = onCreateViewHolder(parent, viewType);
          holder.mItemViewType = viewType;
          TraceCompat.endSection();
          return holder;
      }
      复制代码
  • 对于ViewHolder对象的数量“够用”以后就中止调用onCreateViewHolder方法,能够查看
    • 获取为给定位置初始化的视图。博客
    • 此方法应由{@link LayoutManager}实现使用,以获取视图来表示来自{@LinkAdapter}的数据。
    • 若是共享池可用于正确的视图类型,则回收程序能够重用共享池中的废视图或分离视图。若是适配器没有指示给定位置上的数据已更改,则回收程序将尝试发回一个之前为该数据初始化的报废视图,而不进行从新绑定。
    public View getViewForPosition(int position) {
        return getViewForPosition(position, false);
    }
    
    View getViewForPosition(int position, boolean dryRun) {
        return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
    }
    
    @Nullable
    ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
        //代码省略了,有须要的小伙伴能够本身看看,这里面逻辑实在太复杂呢
    }
    复制代码

25.0.0.2 ViewHolder封装如何对findViewById优化?ViewHolder中为什么使用SparseArray替代HashMap存储viewId?

  • ViewHolder封装如何对findViewById优化?
    class MyViewHolder extends RecyclerView.ViewHolder {
    
        private SparseArray<View> viewSparseArray;
        private TextView tvTitle;
    
        MyViewHolder(final View itemView) {
            super(itemView);
            if(viewSparseArray==null){
                viewSparseArray = new SparseArray<>();
            }
            tvTitle = (TextView) viewSparseArray.get(R.id.tv_title);
            if (tvTitle == null) {
                tvTitle = itemView.findViewById(R.id.tv_title);
                viewSparseArray.put(R.id.tv_title, tvTitle);
            }
        }
    }
    复制代码
  • 为什么使用SparseArray替代HashMap存储viewId
    • HashMap
      • 基本上就是一个 HashMap.Entry 的数组(Entry 是 HashMap 的一个内部类)。更准确来讲,Entry 类中包含如下字段:
      • 一个非基本数据类型的 key
      • 一个非基本数据类型的 value
      • 保存对象的哈希值
      • 指向下一个 Entry 的指针
    • 当有键值对插入时,HashMap 会发生什么 ?
      • 首先,键的哈希值被计算出来,而后这个值会赋给 Entry 类中对应的 hashCode 变量。
      • 而后,使用这个哈希值找到它将要被存入的数组中“桶”的索引。
      • 若是该位置的“桶”中已经有一个元素,那么新的元素会被插入到“桶”的头部,next 指向上一个元素——本质上使“桶”造成链表。
    • 如今,当你用 key 去查询值时,时间复杂度是 O(1)。虽然时间上 HashMap 更快,但同时它也花费了更多的内存空间。
    • 缺点:
      • 自动装箱的存在乎味着每一次插入都会有额外的对象建立。这跟垃圾回收机制同样也会影响到内存的利用。
      • HashMap.Entry 对象自己是一层额外须要被建立以及被垃圾回收的对象。
      • “桶” 在 HashMap 每次被压缩或扩容的时候都会被从新安排。这个操做会随着对象数量的增加而变得开销极大
      • 在Android中,当涉及到快速响应的应用时,内存相当重要,由于持续地分发和释放内存会出发垃圾回收机制,这会拖慢应用运行。垃圾回收机制会影响应用性能表现,垃圾回收时间段内,应用程序是不会运行的,最终应用使用上就显得卡顿。
    • SparseArray博客
      • 它里面也用了两个数组。一个int[] mKeys和Object[] mValues。从名字均可以看得出来一个用来存储key一个用来保存value的。
    • 当保存一对键值对的时候:
      • key(不是它的hashcode)保存在mKeys[]的下一个可用的位置上。因此不会再对key自动装箱了。
      • value保存在mValues[]的下一个位置上,value仍是要自动装箱的,若是它是基本类型。
    • 查找的时候:
      • 查找key仍是用的二分法查找。也就是说它的时间复杂度仍是O(logN)
      • 知道了key的index,也就能够用key的index来从mValues中检索出value。
    • 相较于HashMap,咱们舍弃了Entry和Object类型的key,放弃了HashCode并依赖于二分法查找。在添加和删除操做的时候有更好的性能开销。

25.0.0.3 LayoutManager做用是什么?LayoutManager样式有哪些?setLayoutManager源码里作了什么?

  • LayoutManager做用是什么?
    • LayoutManager的职责是摆放Item的位置,而且负责决定什么时候回收和重用Item。博客
    • RecyclerView 容许自定义规则去放置子 view,这个规则的控制者就是 LayoutManager。一个 RecyclerView 若是想展现内容,就必须设置一个 LayoutManager
  • LayoutManager样式有哪些?
    • LinearLayoutManager 水平或者垂直的Item视图。
    • GridLayoutManager 网格Item视图。
    • StaggeredGridLayoutManager 交错的网格Item视图。
  • setLayoutManager(LayoutManager layout)源码
    • 分析:当以前设置过 LayoutManager 时,移除以前的视图,并缓存视图在 Recycler 中,将新的 mLayout 对象与 RecyclerView 绑定,更新缓存 View 的数量。最后去调用 requestLayout ,从新请求 measure、layout、draw。
    public void setLayoutManager(LayoutManager layout) {
        if (layout == mLayout) {
            return;
        }
        // 中止滑动
        stopScroll();
        if (mLayout != null) {
            // 若是有动画,则中止全部的动画
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
            }
            // 移除并回收视图
            mLayout.removeAndRecycleAllViews(mRecycler);
            // 回收废弃视图
            mLayout.removeAndRecycleScrapInt(mRecycler);
            //清除mRecycler
            mRecycler.clear();
            if (mIsAttached) {
                mLayout.dispatchDetachedFromWindow(this, mRecycler);
            }
            mLayout.setRecyclerView(null);
            mLayout = null;
        } else {
            mRecycler.clear();
        }
        mChildHelper.removeAllViewsUnfiltered();
        mLayout = layout;
        if (layout != null) {
            if (layout.mRecyclerView != null) {
                throw new IllegalArgumentException("LayoutManager " + layout +
                        " is already attached to a RecyclerView: " + layout.mRecyclerView);
            }
            mLayout.setRecyclerView(this);
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        //更新新的缓存数据
        mRecycler.updateViewCacheSize();
        //从新请求 View 的测量、布局、绘制
        requestLayout();
    }
    复制代码

25.0.0.4 SnapHelper主要是作什么用的?SnapHelper是怎么实现支持RecyclerView的对齐方式?

  • SnapHelper主要是作什么用的
    • 在某些场景下,卡片列表滑动浏览[有的叫轮播图],但愿当滑动中止时能够将当前卡片停留在屏幕某个位置,好比停在左边,以吸引用户的焦点。那么可使用RecyclerView + Snaphelper来实现
  • SnapHelper是怎么实现支持RecyclerView的对齐方式
    • SnapHelper旨在支持RecyclerView的对齐方式,也就是经过计算对齐RecyclerView中TargetView 的指定点或者容器中的任何像素点。博客
  • SnapHelper类重要的方法
    • attachToRecyclerView: 将SnapHelper attach 到指定的RecyclerView 上。
    • calculateDistanceToFinalSnap:复写这个方法计算对齐到TargetView或容器指定点的距离,这是一个抽象方法,由子类本身实现,返回的是一个长度为2的int 数组out,out[0]是x方向对齐要移动的距离,out[1]是y方向对齐要移动的距离。
    • calculateScrollDistance: 根据每一个方向给定的速度估算滑动的距离,用于Fling 操做。
    • findSnapView:提供一个指定的目标View 来对齐,抽象方法,须要子类实现
    • findTargetSnapPosition:提供一个用于对齐的Adapter 目标position,抽象方法,须要子类本身实现。
    • onFling:根据给定的x和 y 轴上的速度处理Fling。
  • 什么是Fling操做
    • 手指在屏幕上滑动 RecyclerView而后松手,RecyclerView中的内容会顺着惯性继续往手指滑动的方向继续滚动直到中止,这个过程叫作 Fling 。 Fling 操做从手指离开屏幕瞬间被触发,在滚动中止时结束。
  • LinearSnapHelper类分析
    • LinearSnapHelper 使当前Item居中显示,经常使用场景是横向的RecyclerView,相似ViewPager效果,可是又能够快速滑动(滑动多页)。博客
    • 最简单的使用就是,以下代码
      • 几行代码就能够用RecyclerView实现一个相似ViewPager的效果,而且效果还不错。能够快速滑动多页,当前页剧中显示,而且显示前一页和后一页的部分。
      LinearSnapHelper snapHelper = new LinearSnapHelper();
      snapHelper.attachToRecyclerView(mRecyclerView);
      复制代码
  • PagerSnapHelper类分析
    • PagerSnapHelper看名字可能就能猜到,使RecyclerView像ViewPager同样的效果,每次只能滑动一页(LinearSnapHelper支持快速滑动), PagerSnapHelper也是Item居中对齐。
    • 最简单的使用就是,以下代码
      PagerSnapHelper snapHelper = new PagerSnapHelper();
      snapHelper.attachToRecyclerView(mRecyclerView);
      复制代码

25.0.0.5 SpanSizeLookup的做用是干什么的?SpanSizeLookup如何使用?SpanSizeLookup实现原理如何理解?

  • SpanSizeLookup的做用是干什么的?
    • RecyclerView 能够经过 GridLayoutManager 实现网格布局, 可是不多有人知道GridLayoutManager 还能够用来设置网格中指定Item的列数,相似于合并单元格的功能,而全部的这些咱们仅仅只需经过定义一个RecycleView列表就能够完成,要实现指定某个item所占列数的功能咱们须要用到GridLayoutManager.SpanSizeLookup这个类,该类是一个抽象类,里面包含了一个getSpanSize(int position)的抽象方法,该方法的返回值就是指定position所占的列数
  • SpanSizeLookup如何使用?
    • 先是定义了一个6列的网格布局,而后经过GridLayoutManager.SpanSizeLookup这个类来动态的指定某个item应该占多少列。博客
    • 好比getSpanSize返回6,就表示当前position索引处的item占用6列,那么显示就只会展现一个ItemView【占用6列】。
    • 好比getSpanSize返回3,就表示当前position索引处的item占用3列
    GridLayoutManager manager = new GridLayoutManager(this, 6);
    manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            SpanModel model = mDataList.get(position);
            if (model.getType() == 1) {
                return 6;
            } else if(model.getType() == 2){
                return 3;
            }else if (model.getType() == 3){
                return 2;
            }else if (model.getType() == 4){
                return 2;
            } else {
                return 1;
            }
        }
    });
    复制代码

25.0.0.6 ItemDecoration的用途是什么?自定义ItemDecoration有哪些重写方法?分析一下addItemDecoration()源码?

  • ItemDecoration的用途是什么?
    • 经过设置recyclerView.addItemDecoration(new DividerDecoration(this));来改变Item之间的偏移量或者对Item进行装饰。
    • 固然,你也能够对RecyclerView设置多个ItemDecoration,列表展现的时候会遍历全部的ItemDecoration并调用里面的绘制方法,对Item进行装饰。博客
  • 自定义ItemDecoration有哪些重写方法
    • 该抽象类常见的方法以下所示:博客
    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的正确尺寸并设置偏移量。
    复制代码
  • 分析一下addItemDecoration()源码?
    • a.经过下面代码可知,mItemDecorations是一个ArrayList,咱们将ItemDecoration也就是分割线对象,添加到其中。
      • 能够看到,当经过这个方法添加分割线后,会指定添加分割线在集合中的索引,而后再从新请求 View 的测量、布局、(绘制)。注意: requestLayout会调用onMeasure和onLayout,不必定调用onDraw!
      • 关于View自定义控件源码分析,能够参考个人其余博客:github.com/yangchong21…
      public void addItemDecoration(ItemDecoration decor) {
          addItemDecoration(decor, -1);
      }
      
      //主要看这个方法,个人GitHub:https://github.com/yangchong211/YCBlogs
      public void addItemDecoration(ItemDecoration decor, int index) {
          if (mLayout != null) {
              mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
                      + " layout");
          }
          if (mItemDecorations.isEmpty()) {
              setWillNotDraw(false);
          }
          if (index < 0) {
              mItemDecorations.add(decor);
          } else {
              // 指定添加分割线在集合中的索引
              mItemDecorations.add(index, decor);
          }
          markItemDecorInsetsDirty();
          // 从新请求 View 的测量、布局、绘制
          requestLayout();
      }
      复制代码
      • 总结归纳博客
        • 能够看到在 View 的以上两个方法中,分别调用了 ItemDecoration 对象的 onDraw onDrawOver 方法。
        • 这两个抽象方法,由咱们继承 ItemDecoration 来本身实现,他们区别就是 onDraw 在 item view 绘制以前调用,onDrawOver 在 item view 绘制以后调用。
        • 因此绘制顺序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。

25.0.0.7 上拉加载更多的功能是如何作的?添加滚动监听事件须要注意什么问题?网格布局上拉加载如何优化?

  • 上拉加载更多的功能是如何作的?
    • 01.添加recyclerView的滑动事件
      • 首先给recyclerView添加滑动监听事件。那么咱们知道,上拉加载时,须要具有两个条件。第一个是监听滑动到最后一个item,第二个是滑动到最后一个而且是向上滑动。
      • 设置滑动监听器,RecyclerView自带的ScrollListener,获取最后一个彻底显示的itemPosition,而后判断是否滑动到了最后一个item,
    • 02.上拉加载分页数据
      • 而后开始调用更新上拉加载更多数据的方法。注意这里的刷新数据,能够直接用notifyItemRangeInserted方法,不要用notifyDataSetChanged方法。
    • 03.设置上拉加载的底部footer布局
      • 在adapter中,能够上拉加载时处理footerView的逻辑
        • 在getItemViewType方法中设置最后一个Item为FooterView
        • 在onCreateViewHolder方法中根据viewType来加载不一样的布局
        • 最后在onBindViewHolder方法中设置一下加载的状态显示就能够
        • 因为多了一个FooterView,因此要记得在getItemCount方法的返回值中加上1。
    • 04.显示和隐藏footer布局
      • 通常状况下,滑动底部最后一个item,而后显示footer上拉加载布局,而后让其加载500毫秒,最后加载出下一页数据后再隐藏起来。博客
  • 网格布局上拉加载如何优化
    • 若是是网格布局,那么上拉刷新的view则不是居中显示,到加载更多的进度条显示在了一个Item上,若是想要正常显示的话,进度条须要横跨两个Item,这该怎么办呢?
    • 在adapter中的onAttachedToRecyclerView方法中处理网格布局状况,代码以下所示,主要逻辑是若是当前是footer的位置,那么该item占据2个单元格,正常状况下占据1个单元格。
    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    // 若是当前是footer的位置,那么该item占据2个单元格,正常状况下占据1个单元格
                    return getItemViewType(position) == footType ? gridManager.getSpanCount() : 1;
                }
            });
        }
    }
    复制代码
  • 那么如何实现自动进行上拉刷新?
    • 设置滑动监听,判断是否滑动到底部,也就是最后一条数据,当滑动到最后时就开始加载下一页数据,而且显示加载下一页loading。当加载数据成功后,则直接隐藏该布局。
  • 那么如何实现手动上拉刷新呢?
    • 在上面步骤的基础上进行修改,当滑动到最后一个数据时,展现上拉加载更多布局。而后设置它的点击事件,点击以后开始加载下一页数据,当加载完成后,则直接隐藏该布局。

25.0.0.8 RecyclerView绘制原理如何理解?性能优化本质是什么?RecyclerView绘制原理过程大概是怎样的?

  • RecyclerView绘制原理如何理解?
    • image
  • 性能优化本质是什么?
    • RecyclerView作性能优化要说复杂也复杂,好比说布局优化,缓存,预加载,复用池,刷新数据等等。
      • 其优化的点不少,在这些看似独立的点之间,其实存在一个枢纽:Adapter。由于全部的ViewHolder的建立和内容的绑定都须要通过Adapter的两个函数onCreateViewHolder和onBindViewHolder。
    • 所以性能优化的本质就是要减小这两个函数的调用时间和调用的次数。博客
      • 若是咱们想对RecyclerView作性能优化,必须清楚的了解到咱们的每一步操做背后,onCreateViewHolder和onBindViewHolder调用了多少次。
  • RecyclerView绘制原理过程大概是怎样的?
    • 简化问题
      RecyclerView
          以LinearLayoutManager为例
          忽略ItemDecoration
          忽略ItemAnimator
          忽略Measure过程
          假设RecyclerView的width和height是肯定的
      Recycler
          忽略mViewCacheExtension
      复制代码
    • 绘制过程
      • 类的职责介绍
        • LayoutManager:接管RecyclerView的Measure,Layout,Draw的过程
        • Recycler:缓存池
        • Adapter:ViewHolder的生成器和内容绑定器。博客
      • 绘制过程简介
        • RecyclerView.requestLayout开始发生绘制,忽略Measure的过程
        • 在Layout的过程会经过LayoutManager.fill去将RecyclerView填满
        • LayoutManager.fill会调用LayoutManager.layoutChunk去生成一个具体的ViewHolder
        • 而后LayoutManager就会调用Recycler.getViewForPosition向Recycler去要ViewHolder
        • Recycler首先去一级缓存(Cache)里面查找是否命中,若是命中直接返回。若是一级缓存没有找到,则去三级缓存查找,若是三级缓存找到了则调用Adapter.bindViewHolder来绑定内容,而后返回。若是三级缓存没有找到,那么就经过Adapter.createViewHolder建立一个ViewHolder,而后调用Adapter.bindViewHolder绑定其内容,而后返回为Recycler。
        • 一直重复步骤3-5,知道建立的ViewHolder填满了整个RecyclerView为止。

25.0.0.9 RecyclerView的Recyler是如何实现ViewHolder的缓存?如何理解recyclerView三级缓存是如何实现的?

  • RecyclerView的Recyler是如何实现ViewHolder的缓存?
    • 首先看看代码
      public final class Recycler {
          final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
          ArrayList<ViewHolder> mChangedScrap = null;
          final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
          private final List<ViewHolder>
                  mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
          private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
          int mViewCacheMax = DEFAULT_CACHE_SIZE;
          RecycledViewPool mRecyclerPool;
          private ViewCacheExtension mViewCacheExtension;
          static final int DEFAULT_CACHE_SIZE = 2;
      }
      复制代码
    • RecyclerView在Recyler里面实现ViewHolder的缓存,Recycler里面的实现缓存的主要包含如下5个对象:
      • ArrayList mAttachedScrap:未与RecyclerView分离的ViewHolder列表,若是仍依赖于 RecyclerView (好比已经滑动出可视范围,但尚未被移除掉),但已经被标记移除的 ItemView 集合会被添加到 mAttachedScrap 中
        • 按照id和position来查找ViewHolder
      • ArrayList mChangedScrap:表示数据已经改变的viewHolder列表,存储 notifXXX 方法时须要改变的 ViewHolder,匹配机制按照position和id进行匹配
      • ArrayList mCachedViews:缓存ViewHolder,主要用于解决RecyclerView滑动抖动时的状况,还有用于保存Prefetch的ViewHoder
        • 最大的数量为:mViewCacheMax = mRequestedCacheMax + extraCache(extraCache是由prefetch的时候计算出来的)
      • ViewCacheExtension mViewCacheExtension:开发者可自定义的一层缓存,是虚拟类ViewCacheExtension的一个实例,开发者可实现方法getViewForPositionAndType(Recycler recycler, int position, int type)来实现本身的缓存。
        • 位置固定
        • 内容不变
        • 数量有限
      • mRecyclerPool ViewHolder缓存池,在有限的mCachedViews中若是存不下ViewHolder时,就会把ViewHolder存入RecyclerViewPool中。
        • 按照Type来查找ViewHolder
        • 每一个Type默认最多缓存5个博客
  • 如何理解recyclerView三级缓存是如何实现的?
    • RecyclerView在设计的时候讲上述5个缓存对象分为了3级。每次建立ViewHolder的时候,会按照优先级依次查询缓存建立ViewHolder。每次讲ViewHolder缓存到Recycler缓存的时候,也会按照优先级依次缓存进去。三级缓存分别是:
    • 一级缓存:返回布局和内容都都有效的ViewHolder
      • 按照position或者id进行匹配
      • 命中一级缓存无需onCreateViewHolder和onBindViewHolder
      • mAttachScrap在adapter.notifyXxx的时候用到
      • mChanedScarp在每次View绘制的时候用到,由于getViewHolderForPosition非调用屡次,后面将
      • mCachedView:用来解决滑动抖动的状况,默认值为2
    • 二级缓存:返回View
      • 按照position和type进行匹配
      • 直接返回View
      • 须要本身继承ViewCacheExtension实现
      • 位置固定,内容不发生改变的状况,好比说Header若是内容固定,就可使用
    • 三级缓存:返回布局有效,内容无效的ViewHolder
      • 按照type进行匹配,每一个type缓存值默认=5
      • layout是有效的,可是内容是无效的
      • 多个RecycleView可共享,可用于多个RecyclerView的优化
  • 图解博客
    • image

25.0.1.0 屏幕滑动(状态是item状态可见,不可见,即将可见变化)时三级缓存是如何理解的?adapter中的几个方法是如何变化?

  • 屏幕滑动(状态是item状态可见,不可见,即将可见变化)时三级缓存是如何理解的?
    • 如图所示
      • image
    • 实例解释:
      • 因为ViewCacheExtension在实际使用的时候较少用到,所以本例中忽略二级缓存。mChangedScrap和mAttchScrap是RecyclerView内部控制的缓存,本例暂时忽略。
      • 图片解释:
        • RecyclerView包含三部分:已经出屏幕,在屏幕里面,即将进入屏幕,咱们滑动的方向是向上
        • RecyclerView包含三种Type:1,2,3。屏幕里面的都是Type=3
        • 红色的线表明已经出屏幕的ViewHolder与Recycler的交互状况
        • 绿色的线表明,即将进入屏幕的ViewHolder进入屏幕时候,ViewHolder与Recycler的交互状况
      • 出屏幕时候的状况
        • 当ViewHolder(position=0,type=1)出屏幕的时候,因为mCacheViews是空的,那么就直接放在mCacheViews里面,ViewHolder在mCacheViews里面布局和内容都是有效的,所以能够直接复用。 ViewHolder(position=1,type=2)同步骤1
        • 当ViewHolder(position=2,type=1)出屏幕的时候因为一级缓存mCacheViews已经满了,所以将其放入RecyclerPool(type=1)的缓存池里面。此时ViewHolder的内容会被标记为无效,当其复用的时候须要再次经过Adapter.bindViewHolder来绑定内容。 ViewHolder(position=3,type=2)同步骤3
      • 进屏幕时候的状况博客
        • 当ViewHolder(position=3-10,type=3)进入屏幕绘制的时候,因为Recycler的mCacheViews里面找不到position匹配的View,同时RecyclerPool里面找不到type匹配的View,所以,其只能经过adapter.createViewHolder来建立ViewHolder,而后经过adapter.bindViewHolder来绑定内容。
        • 当ViewHolder(position=11,type=1)进入屏幕的时候,发现ReccylerPool里面能找到type=1的缓存,所以直接从ReccylerPool里面取来使用。因为内容是无效的,所以还须要调用bindViewHolder来绑定布局。同时ViewHolder(position=4,type=3)须要出屏幕,其直接进入RecyclerPool(type=3)的缓存池中
        • ViewHolder(position=12,type=2)同步骤6
      • 屏幕往下拉ViewHolder(position=1)进入屏幕的状况
        • 因为mCacheView里面的有position=1的ViewHolder与之匹配,直接返回。因为内容是有效的,所以无需再次绑定内容
        • ViewHolder(position=0)同步骤8

25.0.1.1 SnapHelper有哪些重要的方法,其做用就是是什么?LinearSnapHelper中是如何实现滚动中止的?

  • SnapHelper有哪些重要的方法,其做用就是是什么?
    • calculateDistanceToFinalSnap抽象方法
      • 计算最终对齐要移动的距离
        • 计算二个参数对应的 ItemView 当前的坐标与须要对齐的坐标之间的距离。该方法返回一个大小为 2 的 int 数组,分别对应out[0] 为 x 方向移动的距离,out[1] 为 y 方向移动的距离。
      @SuppressWarnings("WeakerAccess")
      @Nullable
      public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager,
              @NonNull View targetView);
      复制代码
    • findSnapView抽象方法
      • 找到要对齐的View
        • 该方法会找到当前 layoutManager 上最接近对齐位置的那个 view ,该 view 称为 SanpView ,对应的 position 称为 SnapPosition 。若是返回 null ,就表示没有须要对齐的 View ,也就不会作滚动对齐调整。
      @SuppressWarnings("WeakerAccess")
      @Nullable
      public abstract View findSnapView(LayoutManager layoutManager);
      复制代码
    • findTargetSnapPosition抽象方法
      • 找到须要对齐的目标View的的Position。博客
        • 更加详细一点说就是该方法会根据触发 Fling 操做的速率(参数 velocityX 和参数 velocityY )来找到 RecyclerView 须要滚动到哪一个位置,该位置对应的 ItemView 就是那个须要进行对齐的列表项。咱们把这个位置称为 targetSnapPosition ,对应的 View 称为 targetSnapView 。若是找不到 targetSnapPosition ,就返回RecyclerView.NO_POSITION 。
      public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX,
              int velocityY);
      复制代码
  • LinearSnapHelper中是如何实现滚动中止的?
    • SnapHelper继承了 RecyclerView.OnFlingListener,实现了onFling方法。android

      • 获取RecyclerView要进行fling操做须要的最小速率,为啥呢?由于只有超过该速率,ItemView才会有足够的动力在手指离开屏幕时继续滚动下去。该方法返回的是一个布尔值!
      @Override
      public boolean onFling(int velocityX, int velocityY) {
          LayoutManager layoutManager = mRecyclerView.getLayoutManager();
          if (layoutManager == null) {
              return false;
          }
          RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
          if (adapter == null) {
              return false;
          }
          int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
          return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
                  && snapFromFling(layoutManager, velocityX, velocityY);
      }
      复制代码
    • 接着看看snapFromFling方法源代码,就是经过该方法实现平滑滚动并使得在滚动中止时itemView对齐到目的坐标位置git

      • 首先layoutManager必须实现ScrollVectorProvider接口才能继续往下操做
      • 而后经过createSnapScroller方法建立一个SmoothScroller,这个东西是一个平滑滚动器,用于对ItemView进行平滑滚动操做
      • 根据x和y方向的速度来获取须要对齐的View的位置,须要子类实现
      • 最终经过 SmoothScroller 来滑动到指定位置博客
      private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX,
              int velocityY) {
          if (!(layoutManager instanceof ScrollVectorProvider)) {
              return false;
          }
      
          RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
          if (smoothScroller == null) {
              return false;
          }
      
          int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
          if (targetPosition == RecyclerView.NO_POSITION) {
              return false;
          }
      
          smoothScroller.setTargetPosition(targetPosition);
          layoutManager.startSmoothScroll(smoothScroller);
          return true;
      }
      复制代码
      • 总结一下可知:snapFromFling()方法会先判断layoutManager是否实现了ScrollVectorProvider接口,若是没有实现该接口就不容许经过该方法作滚动操做。接下来就去建立平滑滚动器SmoothScroller的一个实例,layoutManager能够经过该平滑滚动器来进行滚动操做。SmoothScroller须要设置一个滚动的目标位置,将经过findTargetSnapPosition()方法来计算获得的targetSnapPosition给它,告诉滚动器要滚到这个位置,而后就启动SmoothScroller进行滚动操做。
    • 接着看下createSnapScroller这个方法源码博客程序员

      • 先判断layoutManager是否实现了ScrollVectorProvider这个接口,没有实现该接口就不建立SmoothScroller
      • 这里建立一个LinearSmoothScroller对象,而后返回给调用函数,也就是说,最终建立出来的平滑滚动器就是这个LinearSmoothScroller
      • 在建立该LinearSmoothScroller的时候主要考虑两个方面:
        • 第一个是滚动速率,由calculateSpeedPerPixel()方法决定;
        • 第二个是在滚动过程当中,targetView即将要进入到视野时,将匀速滚动变换为减速滚动,而后一直滚动目的坐标位置,使滚动效果更真实,这是由onTargetFound()方法决定。
      @Nullable
      protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) {
          if (!(layoutManager instanceof ScrollVectorProvider)) {
              return null;
          }
          return new LinearSmoothScroller(mRecyclerView.getContext()) {
              @Override
              protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
                  int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(),
                          targetView);
                  final int dx = snapDistances[0];
                  final int dy = snapDistances[1];
                  final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
                  if (time > 0) {
                      action.update(dx, dy, time, mDecelerateInterpolator);
                  }
              }
      
              @Override
              protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                  return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
              }
          };
      }
      复制代码

25.0.1.2 LinearSnapHelper代码中calculateDistanceToFinalSnap做用是什么?那么out[0]和out[1]分别指什么?

  • calculateDistanceToFinalSnap的做用是什么
    • 若是是水平方向滚动的,则计算水平方向须要移动的距离,不然水平方向的移动距离为0
    • 若是是竖直方向滚动的,则计算竖直方向须要移动的距离,不然竖直方向的移动距离为0
    • distanceToCenter方法主要做用是:计算水平或者竖直方向须要移动的距离
    @Override
    public int[] calculateDistanceToFinalSnap(
            @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
        int[] out = new int[2];
        if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToCenter(layoutManager, targetView,
                    getHorizontalHelper(layoutManager));
        } else {
            out[0] = 0;
        }
    
        if (layoutManager.canScrollVertically()) {
            out[1] = distanceToCenter(layoutManager, targetView,
                    getVerticalHelper(layoutManager));
        } else {
            out[1] = 0;
        }
        return out;
    }
    复制代码
    • 接着看看distanceToCenter方法
      • 计算对应的view的中心坐标到RecyclerView中心坐标之间的距离
      • 首先是找到targetView的中心坐标
      • 接着也就是找到容器【RecyclerView】的中心坐标
      • 两个中心坐标的差值就是targetView须要滚动的距离
      private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager,
              @NonNull View targetView, OrientationHelper helper) {
          final int childCenter = helper.getDecoratedStart(targetView)
                  + (helper.getDecoratedMeasurement(targetView) / 2);
          final int containerCenter;
          if (layoutManager.getClipToPadding()) {
              containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
          } else {
              containerCenter = helper.getEnd() / 2;
          }
          return childCenter - containerCenter;
      }
      复制代码
  • 那么out[0]和out[1]分别指什么
    • 返回的是一个长度为2的int 数组out,out[0]是x方向对齐要移动的距离,out[1]是y方向对齐要移动的距离。

25.0.1.3 如何实现能够设置分割线的颜色,宽度,以及到左右两边的宽度间距的自定义分割线,说一下思路?

  • 须要实现的分割线功能
    • 能够设置分割线的颜色,宽度,以及到左右两边的宽度间距。item默认分割线的颜色不可改变,那么只有重写onDraw方法,经过设置画笔point颜色来绘制分割线颜色。而设置分割线左右的间隔是经过getItemOffsets方法实现的。
  • 几个重要的方法说明
    • 须要自定义类实现RecyclerView.ItemDecoration类,并选择重写合适方法。注意下面这三个方法有着强烈的因果关系!
    //获取当前view的位置信息,该方法主要是设置条目周边的偏移量
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
    //在item背后draw
    public void onDraw(Canvas c, RecyclerView parent, State state)
    //在item上边draw
    public void onDrawOver(Canvas c, RecyclerView parent, State state)
    复制代码
  • 注意的是三个方法的调用顺序
    • 首先调用的是getItemOffsets会被屡次调用,在layoutManager每次测量可摆放的view的时候回调用一次,在当前状态下须要摆放多少个view这个方法就会回调多少次。
    • 其次会调用onDraw方法,ItemDecoration的onDraw方法是在RecyclerView的onDraw方法中调用的,注意这时候传入的canvas是RecyclerView的canvas,要时刻注意这点,它是和RecyclerView的边界是一致的。这个时候绘制的内容至关于背景,会被item覆盖。
    • 最后调用的是onDrawOver方法,ItemDecoration的onDrawOver方法是在RecyclerView的draw方法中调用的,一样传入的是RecyclerView的canvas,这时候onlayout已经调用,因此此时绘制的内容会覆盖item。
  • 为每一个item实现索引的思路
    • 要实现上面的能够设置分割线颜色和宽度,确定是要绘制的,也就是须要使用到onDraw方法。那么在getItemOffsets方法中须要让view摆放位置距离bottom的距离是分割线的宽度。博客
    • 而后经过parent.getChildCount()方法拿到当前显示的view的数量[注意,该方法并不会获取不显示的view的数量],循环遍历后,直接用paint画笔进行绘制[注意至于分割线的颜色就是须要设置画笔的颜色]。

25.0.1.4 如何实现复杂type首页需求?若是不封装会出现什么问题和弊端?如何提升代码的简便性和高效性?

  • 如何实现复杂type首页需求
    • 一般写一个多Item列表的方法
      • 根据不一样的ViewType 处理不一样的item,若是逻辑复杂,这个类的代码量是很庞大的。若是版本迭代添加新的需求,修改代码很麻烦,后期维护困难。
    • 主要操做步骤
      • 在onCreateViewHolder中根据viewType参数,也就是getItemViewType的返回值来判断须要建立的ViewHolder类型
      • 在onBindViewHolder方法中对ViewHolder的具体类型进行判断,分别为不一样类型的ViewHolder进行绑定数据与逻辑处理
    • 代码以下所示
      public class HomeAdapter extends RecyclerView.Adapter {
          public static final int TYPE_BANNER = 0;
          public static final int TYPE_AD = 1;
          @Override
          public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
              switch (viewType){
                  case TYPE_BANNER:
                      return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null));
                  case TYPE_AD:
                      return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_ad_item_layout,null));
              }
              return null;
          }
      
          @Override
          public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
              int type = getItemViewType(position);
              switch (type){
                  case TYPE_BANNER:
                      // banner 逻辑处理
                      break;
                  case TYPE_AD:
                      // 广告逻辑处理
                      break;
                  // ... 此处省去N行代码
              }
          }
      
          @Override
          public int getItemViewType(int position) {
              if(position == 0){
                  return TYPE_BANNER;//banner在开头
              }else {
                  return mData.get(position).type;//type 的值为TYPE_AD,TYPE_IMAGE,TYPE_AD,等其中一个
              }
          }
          public static class BannerViewHolder extends RecyclerView.ViewHolder{
              public BannerViewHolder(View itemView) {
                  super(itemView);
              }
          }
          public static class NewViewHolder extends RecyclerView.ViewHolder{
              public VideoViewHolder(View itemView) {
                  super(itemView);
              }
          }
      }
      复制代码
  • 若是不封装会出现什么问题和弊端
    • RecyclerView 能够用ViewType来区分不一样的item,也能够知足需求,但仍是存在一些问题,好比:
      • 1,在item过多逻辑复杂列表界面,Adapter里面的代码量庞大,逻辑复杂,后期难以维护。
      • 2,每次增长一个列表都须要增长一个Adapter,重复搬砖,效率低下。
      • 3,没法复用adapter,假若有多个页面有多个type,那么就要写多个adapter。
      • 4,要是有局部刷新,那么就比较麻烦了,好比广告区也是一个九宫格的RecyclerView,点击局部刷新当前数据,比较麻烦。
    • 上面那样写的弊端
      • 类型检查与类型转型,因为在onCreateViewHolder根据不一样类型建立了不一样的ViewHolder,因此在onBindViewHolder须要针对不一样类型的ViewHolder进行数据绑定与逻辑处理,这致使须要经过instanceof对ViewHolder进行类型检查与类型转型。
      • 不利于扩展,目前的需求是列表中存在5种布局类类型,那么若是需求变更,极端一点的状况就是数据源是从服务器获取的,数据中的model决定列表中的布局类型。这种状况下,每当model改变或model类型增长,咱们都要去改变adapter中不少的代码,同时Adapter还必须知道特定的model在列表中的位置(position)除非跟服务端约定好,model(位置)不变,很显然,这是不现实的。
      • 不利于维护,这点应该是上一点的延伸,随着列表中布局类型的增长与变动,getItemViewType、onCreateViewHolder、onBindViewHolder中的代码都须要变动或增长,Adapter 中的代码会变得臃肿与混乱,增长了代码的维护成本。
  • 如何提升代码的简便性和高效性。具体封装库看:recyclerView复杂type封装库
    • 核心目的就是三个
      • 避免类的类型检查与类型转型
      • 加强Adapter的扩展性
      • 加强Adapter的可维护性
    • 当列表中类型增长或减小时Adapter中主要改动的就是getItemViewType、onCreateViewHolder、onBindViewHolder这三个方法,所以,咱们就从这三个方法中开始着手。
    • 既然可能存在多个type类型的view,那么能不能把这些好比banner,广告,文本,视频,新闻等当作一个HeaderView来操做。
    • 在getItemViewType方法中。
      • 减小if之类的逻辑判断简化代码,能够简单粗暴的用hashCode做为增长type标识。
      • 经过建立列表的布局类型,同时返回的再也不是简单的布局类型标识,而是布局的hashCode值
    • onCreateViewHolder
      • getItemViewType返回的是布局hashCode值,也就是onCreateViewHolder(ViewGroup parent, int viewType)参数中的viewType
    • 在onBindViewHolder方法中。能够看到,在此方法中,添加一种header类型的view,则经过onBindView进行数据绑定。
    • 封装后好处
      • 拓展性——Adapter并不关心不一样的列表类型在列表中的位置,所以对于Adapter来讲列表类型能够随意增长或减小。十分方便,同时设置类型view的布局和数据绑定都不须要在adapter中处理。充分解耦。
      • 可维护性——不一样的列表类型由adapter添加headerView处理,哪怕添加多个headerView,相互之间互不干扰,代码简洁,维护成本低。

25.0.1.5 关于item条目点击事件在onCreateViewHolder中写和在onBindViewHolder中写有何区别?如何优化?

  • 关于rv设置item条目点击事件有两种方式:1.在onCreateViewHolder中写;2.在onBindViewHolder中写;3.在ViewHolder中写。那么到底是哪种好呢?
    • 1.在onCreateViewHolder中写
      @NonNull
      @Override
      public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
          final View view = LayoutInflater.from(mContext).inflate(R.layout.item_me_gv_grid, parent, false);
          final MyViewHolder holder = new MyViewHolder(view);
          view.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  if (listener != null) {
                      listener.onItemClick(view, holder.getLayoutPosition());
                  }
              }
          });
          return holder;
      }
      复制代码
    • 2.在onBindViewHolder中写
      @Override
      public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
          holder.itemView.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  if (listener != null) {
                      listener.onItemClick(holder.itemView, holder.getAdapterPosition());
                  }
              }
          });
      }
      复制代码
  • onBindViewHolder() 中频繁建立新的 onClickListener 实例没有必要,建议实际开发中应该在 onCreateViewHolder() 中每次为新建的 View 设置一次就行。

25.0.1.6 RecyclerView滑动卡顿缘由有哪些?如何解决嵌套布局滑动冲突?如何解决RecyclerView实现画廊卡顿?

  • RecyclerView滑动卡顿缘由有哪些
    • 第一种:嵌套布局滑动冲突
      • 致使嵌套滑动难处理的关键缘由在于当子控件消费了事件, 那么父控件就不会再有机会处理这个事件了, 因此一旦内部的滑动控件消费了滑动操做, 外部的滑动控件就再也没机会响应这个滑动操做了
    • 第二种:嵌套布局层次太深,好比六七层等
      • 测量,绘制布局可能会致使滑动卡顿
    • 第三种:好比用RecyclerView实现画廊,加载比较大的图片,若是快速滑动,则可能会出现卡顿,主要是加载图片须要时间
    • 第四种:在onCreateViewHolder或者在onBindViewHolder中作了耗时的操做致使卡顿。
  • 如何解决嵌套布局滑动冲突
  • 如何解决RecyclerView实现画廊卡顿?
    • RecyclerView 滑动时不让 Glide 加载图片。滚动中止后才开始恢复加载图片。
    //RecyclerView.SCROLL_STATE_IDLE //空闲状态
    //RecyclerView.SCROLL_STATE_FLING //滚动状态
    //RecyclerView.SCROLL_STATE_TOUCH_SCROLL //触摸后状态
    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                LoggerUtils.e("initRecyclerView"+ "恢复Glide加载图片");
                Glide.with(ImageBrowseActivity.this).resumeRequests();
            }else {
                LoggerUtils.e("initRecyclerView"+"禁止Glide加载图片");
                Glide.with(ImageBrowseActivity.this).pauseRequests();
            }
        }
    });
    复制代码
  • 在onCreateViewHolder或者在onBindViewHolder中作了耗时的操做致使卡顿
    • 按stackoverflow上面比较通俗的解释:RecyclerView.Adapter里面的onCreateViewHolder()方法和onBindViewHolder()方法对时间都很是敏感。相似I/O读写,Bitmap解码一类的耗时操做,最好不要在它们里面进行。

25.0.1.7 RecyclerView常见的优化有哪些?实际开发中都是怎么作的,优化先后对比性能上有何提高?

  • RecyclerView常见的优化有哪些
    • DiffUtil刷新优化
      • 分页拉取远端数据,对拉取下来的远端数据进行缓存,提高二次加载速度;对于新增或者删除数据经过 DiffUtil 来进行局部刷新数据,而不是一味地全局刷新数据。
    • 布局优化
      • 减小 xml 文件 inflate 时间
        • 这里的 xml 文件不只包括 layout 的 xml,还包括 drawable 的 xml,xml 文件 inflate 出 ItemView 是经过耗时的 IO 操做,尤为当 Item 的复用概率很低的状况下,随着 Type 的增多,这种 inflate 带来的损耗是至关大的,此时咱们能够用代码去生成布局,即 new View() 的方式,只要搞清楚 xml 中每一个节点的属性对应的 API 便可。
      • 减小 View 对象的建立
        • 一个稍微复杂的 Item 会包含大量的 View,而大量的 View 的建立也会消耗大量时间,因此要尽量简化 ItemView;设计 ItemType 时,对多 ViewType 可以共用的部分尽可能设计成自定义 View,减小 View 的构造和嵌套。博客
    • 对itemView中孩子View的点击事件优化
      • onBindViewHolder() 中频繁建立新的 onClickListener 实例没有必要,建议实际开发中应该在 onCreateViewHolder() 中每次为新建的 View 设置一次就行。
  • 其余的一些优化点
    • 若是 Item 高度是固定的话,可使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源;
    • 设置 RecyclerView.addOnScrollListener(listener); 来对滑动过程当中中止加载的操做。
    • 若是不要求动画,能够经过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭来提神效率。
    • 经过重写 RecyclerView.onViewRecycled(holder) 来回收资源。
    • 经过 RecycleView.setItemViewCacheSize(size); 来加大 RecyclerView 的缓存,用空间换时间来提升滚动的流畅性。
    • 若是多个 RecycledView 的 Adapter 是同样的,好比嵌套的 RecyclerView 中存在同样的 Adapter,能够经过设置 RecyclerView.setRecycledViewPool(pool); 来共用一个 RecycledViewPool。

25.0.1.8 如何解决RecyclerView嵌套RecyclerView条目自动上滚的Bug?如何解决ScrollView嵌套RecyclerView滑动冲突?

  • RecyclerView嵌套RecyclerView 条目自动上滚的Bug
    • RecyclerViewA嵌套RecyclerViewB 进入页面自动跳转到RecyclerViewB上面页面会自动滚动。
    • 解决办法以下所示
    • 一,recyclerview去除焦点
      • recyclerview.setFocusableInTouchMode(false);
      • recyclerview.requestFocus();
    • 二,在代码里面 让处于ScrollView或者RecyclerView1 顶端的某个控件得到焦点便可
      • 好比顶部的一个textview
      • tv.setFocusableInTouchMode(true);
      • tv.requestFocus();
    • 三,能够直接在RecyclerView父布局中添加上descendantFocusability属性的值有三种:android:descendantFocusability="beforeDescendants"
      beforeDescendants:viewgroup会优先其子类控件而获取到焦点
      afterDescendants:viewgroup只有当其子类控件不须要获取焦点时才获取焦点
      blocksDescendants:viewgroup会覆盖子类控件而直接得到焦点
      复制代码
  • 如何解决ScrollView嵌套RecyclerView滑动冲突?
    • 第一种方式:
      • 重写父控件,让父控件 ScrollView 直接拦截滑动事件,不向下分发给 RecyclerView,具体是定义一个ScrollView子类,重写其 onInterceptTouchEvent()方法
      public class NoNestedScrollview extends NestedScrollView {
          @Override
          public boolean onInterceptTouchEvent(MotionEvent e) {
              int action = e.getAction();
              switch (action) {
                  case MotionEvent.ACTION_DOWN:
                      downX = (int) e.getRawX();
                      downY = (int) e.getRawY();
                      break;
                  case MotionEvent.ACTION_MOVE:
                      //判断是否滑动,若滑动就拦截事件
                      int moveY = (int) e.getRawY();
                      if (Math.abs(moveY - downY) > mTouchSlop) {
                          return true;
                      }
                      break;
                  default:
                      break;
              }
              return super.onInterceptTouchEvent(e);
          }
      }
      复制代码
    • 第二种解决方式博客
      • a.禁止RecyclerView滑动
      recyclerView.setLayoutManager(new GridLayoutManager(mContext,2){
          @Override
          public boolean canScrollVertically() {
              return false;
          }
          
          @Override
          public boolean canScrollHorizontally() {
              return super.canScrollHorizontally();
          }
      });
      
      recyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayout.VERTICAL,false){
          @Override
          public boolean canScrollVertically() {
              return false;
          }
      });
      复制代码
    • 可能会出现的问题博客
      • 虽然上面两种方式解决了滑动冲突,可是有的手机上出现了RecyclerView会出现显示不全的状况。
      • 针对这种情形,使用网上的方法一种是使用 RelativeLayout 包裹 RecyclerView 并设置属性:android:descendantFocusability="blocksDescendants"
        • android:descendantFocusability="blocksDescendants",该属>性是当一个view 获取焦点时,定义 ViewGroup 和其子控件直接的关系,经常使用来>解决父控件的焦点或者点击事件被子空间获取。
        • beforeDescendants: ViewGroup会优先其子控件获取焦点
        • afterDescendants: ViewGroup只有当其子控件不须要获取焦点时才获取焦点
        • blocksDescendants: ViewGroup会覆盖子类控件而直接得到焦点
      • 相关代码案例:github.com/yangchong21…
      <RelativeLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:descendantFocusability="blocksDescendants">
          <android.support.v7.widget.RecyclerView
              android:id="@+id/rv_hot_review"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:foregroundGravity="center" />
      </RelativeLayout>
      复制代码

25.0.1.9 如何处理ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager?如何解决RecyclerView使用Glide加载图片致使图片错乱问题?

  • ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager
    • 继承RecyclerView,重写dispatchTouchEvent,根据ACTION_MOVE的方向判断是否调用getParent().requestDisallowInterceptTouchEvent去阻止父view拦截点击事件
    @Override 
    public boolean dispatchTouchEvent(MotionEvent ev) { 
        /*---解决垂ViewPager嵌套直RecyclerView嵌套水平RecyclerView横向滑动到底后不滑动ViewPager start ---*/ 
        ViewParent parent = this; 
        while(!((parent = parent.getParent()) instanceof ViewPager));
        // 循环查找viewPager 
        parent.requestDisallowInterceptTouchEvent(true); 
        return super.dispatchTouchEvent(ev); 
    }
    复制代码
  • 如何解决RecyclerView使用Glide加载图片致使图片错乱问题
    • 为什么会致使图片加载后出现错乱效果
      • 由于有ViewHolder的重用机制,每个item在移除屏幕后都会被从新使用以节省资源,避免滑动卡顿。而在图片的异步加载过程当中,从发出网络请求到彻底下载并加载成Bitmap的图片须要花费很长时间,而这时候颇有可能原先须要加载图片的item已经划出界面并被重用了。而原先下载的图片在被加载进ImageView的时候没有判断当前的ImageView是否是原先那个要求加载的,故可能图片被加载到被重用的item上,就产生了图片错位的问题。解决思路也很简单,就是在下载完图片,准备给ImageView装上的时候检查一下这个ImageView。博客
    • 第一种方法
      • 使用settag()方式,这种方式仍是比较好的,可是,须要注意的是,Glide图片加载也是使用将这个方法的,因此当你在Bindviewholder()使用时会直接抛异常,你须要使用settag(key,value)方式进行设置,这种方式是不错的一种解决方式,注意取值的时候应该是gettag(key)这个方法哈,当异步请求回来的时候对比下tag是否同样在判断是否显示图片。这边直接复制博主的代码了。
      //给ImageView打上Tag做为特有标记
      imageView.setTag(tag);
       
      //下载图片
      loadImage();
       
      //根据tag判断是否是须要设置给ImageView
      if(tag == iamgeView.getTag()) {
          imageView.setBitmapImage(iamge);
      }
      复制代码

项目开源地址:github.com/yangchong21…

相关文章
相关标签/搜索