版权声明:本文为博主原创文章,未经博主允许不得转载。https://blog.csdn.net/sinat_25074703/article/details/83216019
8、实现StaggeredItemDecoration定制分割线
在实际的项目开发过程中,为了更好的用户体验,分割线是必不可少的,甚至有异想天开的产品会脑补出千姿百态的分割效果。这里主要通过简单的直线分割来阐述RecyclerView#ItemDecoration的风采。
8.1从定义说起
Google官方对RecyclerView#ItemDecoration的定义如下:
/**
* An ItemDecoration allows the application to add a special drawing and layout offset
* to specific item views from the adapter’s data set. This can be useful for drawing dividers
* between items, highlights, visual grouping boundaries and more.
*
*All ItemDecorations are drawn in the order they were added, before the item
* views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
* and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
* RecyclerView.State)}.
*/
译文:
ItemDecoration允许(既然是允许,不添加也不犯法)应用添加一种特殊的绘制和布局补偿系数(注意这两个词汇,绘制是为了渲染,绘在什么地方由补偿系数说了算)到指定的item views中,这些item views来自于adapter数据源(这里的adapter指的是前面提到的RecyclerView#Adapter,具体到本项目是StaggeredAdapter)。这种被允许的方式一定(注意情态动词can be)是有用的,哪里有用?对于绘制items之间的分割线,高亮,可视化分组分割线等一系列需求都是有用的。
*
所有的ItemDecorations按照被添加的顺序绘制(这一句信息量大啊!他告诉我们ItemDecorations可以随意添加,无论多少个。但是他们要按照添加的顺序排队绘制,先绘制的或许会被覆盖看不到。砌墙的砖头——后来居上嘛!),所有的这些ItemDecorations的添加时间段是在ItemDecoration#onDraw()方法被调用之前(我能理解,不然无法绘制), 在ItemDecoration#onDrawOver()之后(我理解不了啊,因为没有意义啊!在此,我大胆推测Google想表达的是,绘制期间不能有添加逻辑,否则就会报异常)。
看完了定义,我们可以确定RecyclerView#ItemDecoration的一些关键信息:使用前需要先绘制,而绘制使用的onDraw()和onDrawOver()方法。那么我们来看一下这个类吧!
8.2类及其方法
public abstract static class ItemDecoration{
}
// 这是一个共有的静态抽象内部类,从这点来说跟ViewHolder和Adapter没啥两样,都是待价而沽,静候明主。
既然是抽象类必然有抽象方法,如下图:
截图中清晰地展示ItemDecoration的三个抽象方法onDraw()、onDrawOver()、getItemOffsets(),有人说你瞎吗?明明是6个。我不瞎,另外三个是同名过期的方法。
- onDraw()
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn before the item views are drawn,
* and will thus appear underneath the views.
*
* 绘制任何适合的decorations进入到画布中,这张画布是提供给RecyclerView使用的(这个当然,否则如何保证同步呢)
* 任何被这个方法绘制的内容都是在item views(指的是RecyclerView的每一个ItemView)被绘制之前绘制,并且(该方法绘制的东西)将出现在所有view的最底层。【这一段传递两个信息,ItemDecoration会被首先绘制,它在最底层。换句话说,如果不做布局上的调整,哪辈子也看不到它】
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}
- onDrawOver()
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn after the item views are drawn
* and will thus appear over the views.
* 绘制任何适合的decorations进入到画布中,这张画布是提供给RecyclerView使用的(这个当然,否则如何保证同步呢)
* 任何被这个方法绘制的内容都是在item views(指的是RecyclerView的每一个ItemView)被绘制之后绘制,并且(该方法绘制的东西)将出现在所有view的最顶层。【这一段传递两个信息,ItemDecoration会被最后绘制,它在最顶层。换句话说,如果不做布局上的调整,天天都能看到它】
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView.
*/
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}
- getItemOffsets()
/**
* Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
* the number of pixels that the item view should be inset by, similar to padding or margin.
* The default implementation sets the bounds of outRect to 0 and returns.
* 译文:
* 取回指定item的任何补偿系数(这里指的是布局补偿系数)。参数outRect的每个属性是以item应该被插入的像素数为基准的(就是说item上下左右都可以插入,但插入的是一个矩形,就算他在视觉上是一条线,它的本质是一个矩形),类似于margin或padding
* 默认outRect的边界设置为0,就是没有布局。
* <p>
* If this ItemDecoration does not affect the positioning of item views, it should set
* all four fields of <code>outRect</code> (left, top, right, bottom) to zero
* before returning.
* 译文:
* 如果该ItemDecoration不影响item views的位置(比如绘制的位置没有交叉重叠),那么outRect的四个属性值都可以设置为0,但是需要在返回之前设置。
* <p>
* If you need to access Adapter for additional data, you can call
* {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the View.
* 译文:如果你需要访问Adapter里的其他数据的话,可以通过调用RecyclerView#getChildAdapterPosition(View)方法来获取中指定位置的view
*
* @param outRect Rect to receive the output.
* @param view The child view to decorate
* @param parent RecyclerView this ItemDecoration is decorating
* @param state The current state of RecyclerView.
*/
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
通过源码追踪不难发现,getItemOffsets()最终会被StaggeredGridLayoutManager的onLayoutChildren()调用【onLayoutChildren()—>fill()—>measureChildWithDecorationsAndMargin()—>calculateItemDecorationsForChild()—>getItemDecorInsetsForChild()—>getItemOffsets()】,StaggeredGridLayoutManager是RecyclerView#LayoutManager的派生类,onLayoutChildren()是其派生方法,定义在RecyclerView中,用于完成布局任务。结合View的绘制流程:测量(onMeasure)——布局(onLayout)——绘制(onDraw),ItemDecoration的执行流程应该是线调用getItemOffsets,接着调用onDraw,最后调用onDrawOver
8.3实现RecyclerView#ItemDecoration绘制分割线
在项目文件夹中找到staggered文件夹右键创建StaggeredItemDecoration并继承自RecyclerView.ItemDecoration,代码如下:
package com.edwin.idea.staggered;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.<