Skip to content

Latest commit

 

History

History
261 lines (219 loc) · 12.8 KB

wiki3.md

File metadata and controls

261 lines (219 loc) · 12.8 KB

目录介绍

  • 01.recyclerView
  • 02.layoutManager
  • 03.adapter
  • 04.viewHolder
  • 05.SnapHelper
  • 06.ItemTouchHelper
  • 07.SpanSizeLookup
  • 08.ItemDecoration
  • 09.ItemAnimator

01.recyclerView

  • 关于RecyclerView,大家都已经很熟悉了,用途十分广泛,大概结构如下所示
    • RecyclerView.Adapter - 处理数据集合并负责绑定视图
    • ViewHolder - 持有所有的用于绑定数据或者需要操作的View
    • LayoutManager - 负责摆放视图等相关操作
    • ItemDecoration - 负责绘制Item附近的分割线
    • ItemAnimator - 为Item的一般操作添加动画效果,如,增删条目等
  • 如图所示,直观展示结构
    • image
  • 针对上面几个属性,最简单用法如下所示
    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    //设置layoutManager
    recyclerView.setLayoutManager(layoutManager);
    adapter = new MultipleItemAdapter(this,list);
    //设置adapter
    recyclerView.setAdapter(adapter);
    //设置刷新
    adapter.notifyDataSetChanged();
    

02.layoutManager

  • LayoutManager作用
    • LayoutManager的职责是摆放Item的位置,并且负责决定何时回收和重用Item。
    • RecyclerView 允许自定义规则去放置子 view,这个规则的控制者就是 LayoutManager。一个 RecyclerView 如果想展示内容,就必须设置一个 LayoutManager
  • LayoutManager样式
    • LinearLayoutManager 水平或者垂直的Item视图。
    • GridLayoutManager 网格Item视图。
    • StaggeredGridLayoutManager 交错的网格Item视图。
  • LayoutManager当前有且仅有一个抽象函数
    • 具体如下:
    public LayoutParams generateDefaultLayoutParams()
    
  • setLayoutManager(LayoutManager layout)源码
    • a.setLayoutManager入口源码
    • 分析:当之前设置过 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();
    }
    

03.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参数)是哪种类型的布局
    
  • notifyDataSetChanged()刷新数据
    • 当时据集合发生改变时,我们通过调用.notifyDataSetChanged(),来刷新列表,因为这样做会触发列表的重绘,所以并不会出现任何动画效果,因此需要调用一些以notifyItem*()作为前缀的特殊方法,比如:
    • public final void notifyItemInserted(int position) 向指定位置插入Item
    • public final void notifyItemRemoved(int position) 移除指定位置Item
    • public final void notifyItemChanged(int position) 更新指定位置Item

04.viewHolder

  • 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
  • 关于ViewHolder简单的封装代码如下所示:

05.SnapHelper

  • SnapHelper作用
    • 在某些场景下,卡片列表滑动浏览[有的叫轮播图],希望当滑动停止时可以将当前卡片停留在屏幕某个位置,比如停在左边,以吸引用户的焦点。那么可以使用RecyclerView + Snaphelper来实现,SnapHelper旨在支持RecyclerView的对齐方式,也就是通过计算对齐RecyclerView中TargetView 的指定点或者容器中的任何像素点。
  • SnapHelper类分析
    • 查阅可知,SnapHelper继承自RecyclerView.OnFlingListener,并且重写了onFling方法,这个类代码并不多,下面会对重要方法一一解析。
      • 支持SnapHelper的RecyclerView.LayoutManager必须实现的方式:
        • RecyclerView.SmoothScroller.ScrollVectorProvider接口
        • 或者自己实现onFling(int,int)方法手动处理逻辑。
    • 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);
    

06.ItemTouchHelper

07.SpanSizeLookup

  • 该页面中,同时包含列表,2列的网格,3列的网格如何优雅实现?
    • image
  • 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列
    mDataList = new ArrayList<>();
    for (int i = 0; i < 50; i++) {
        SpanModel model = new SpanModel();
        model.setName(i + "");
        if (i == 0) {
            model.setType(1);
        } else if (i < 7) {
            model.setType(2);
        } else if (i>=8 && i<11){
            model.setType(3);
        } else if (i>=13 && i<16){
            model.setType(4);
        } else {
            model.setType(1);
        }
        mDataList.add(model);
    }
    
    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;
            }
        }
    });
    

08.ItemDecoration

  • 作用
    • 通过设置recyclerView.addItemDecoration(new DividerDecoration(this));来改变Item之间的偏移量或者对Item进行装饰。
    • 当然,你也可以对RecyclerView设置多个ItemDecoration,列表展示的时候会遍历所有的ItemDecoration并调用里面的绘制方法,对Item进行装饰。
  • RecyclerView.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的正确尺寸并设置偏移量。