#### 目录介绍 - 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](https://upload-images.jianshu.io/upload_images/4432347-6301e0c8563ecda0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 针对上面几个属性,最简单用法如下所示 ``` 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](https://upload-images.jianshu.io/upload_images/4432347-8cb81b547d4e8613.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 关于ViewHolder简单的封装代码如下所示: - 具体可以看:https://github.com/yangchong211/YCRefreshView ### 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](https://upload-images.jianshu.io/upload_images/4432347-293040ce3bfc192c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 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的正确尺寸并设置偏移量。 ```