内容简介:这是官网对 ItemDecoration 的描述,简单来说就是可以为此文会分析
ItemDecoration
是 RecyclerView
中的一个抽象静态内部类。
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.
这是官网对 ItemDecoration 的描述,简单来说就是可以为 RecyclerView
的每一个 ItemView 进行一些特殊的绘制或者特殊的布局。从而我们可以为 RecyclerView
添加一些实用好玩的效果,比如分割线,边框,饰品,粘性头部等。
此文会分析 ItemDecoration
的使用及原理,然后进行一些Demo的实现,包括分割线,网格布局的边框,以及粘性头部。
二 方法
1. 方法概述
ItemDecoration
中的实际方法只有6个,其中有3个是重载方法,都被标注为 @deprecated
,即弃用了,这些方法如下
修饰符 | 返回值类型 | 方法名 | 标注 |
---|---|---|---|
void | public | onDraw(Canvas c, RecyclerView parent, State state) | |
void | public | onDraw(Canvas c, RecyclerView parent) | @deprecated |
void | pulbic | onDrawOver(Canvas c, RecyclerView parent, State state) | |
void | public | onDrawOver(Canvas c, RecyclerView parent) | @deprecated |
void | public | getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) | |
void | public | getItemOffsets(Rect outRect, View view, RecyclerView parent) | @deprecated |
2. getItemOffsets
除了 getItemOffsets
方法,其他方法的默认实现都为空,而 getItemOffsets
的默认实现方法也很简单:
@Deprecated public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { outRect.set(0, 0, 0, 0); } 复制代码
两个 getItemOffsets
方法最终都是调用了上面实现,就一行代码,如果我们自定义过 ItemDecoration
的话,就会知道,我们可以为 outRect
设置四边的大小来为 itemView
设置一个偏移量. 这个偏移量有点类似于 View
的margin,看下面的图:
图片很清晰的表示了 ItemView 的结构(该图不是特别精确,后面会说到),这是只有一个 Child 的情况,我们从外往里看:
- 最外的边界即 RecyclerView 的边界
- 红色部分是 RecyclerView 的 Padding,这个我们应该能理解
- 橙色部分是我们为 ItemView 设置的 Margin,这个相信写过布局都能理解
- 蓝色部分就是我们在
getItemOffsets
方法中给outRect
对象设置的值 - 最后的的黄色部分就是我们的 ItemView 了
总体就是说, getItemOffsets
中设置的值就相当于 margin 的一个存在。"图说无凭",接下来就结合源码讲解一下这个图的"依据"。首先看一下 getItemOffsets
在哪里被调用了:
Rect getItemDecorInsetsForChild(View child) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); ... final Rect insets = lp.mDecorInsets; insets.set(0, 0, 0, 0); final int decorCount = mItemDecorations.size(); for (int i = 0; i < decorCount; i++) { mTempRect.set(0, 0, 0, 0); mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); //被调用 insets.left += mTempRect.left; insets.top += mTempRect.top; insets.right += mTempRect.right; insets.bottom += mTempRect.bottom; } lp.mInsetsDirty = false; return insets; } 复制代码
在 RecyclerView
源码中,这是 getItemOffsets
唯一被调用的地方,代码也很简单,就是将 RecyclerView
中所有的(即通过 addDecoration()
方法添加的) ItemDecoration
遍历一遍,然后将我们设在 getItemOffsets
中设置的四个方向的值分别累加并存储在 insets
这个 Rect
当中。那么这个 insets
又在哪里被调用了呢,顺着方法继续跟踪下去:
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); widthUsed += insets.left + insets.right; heightUsed += insets.top + insets.bottom; final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, canScrollHorizontally()); final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, canScrollVertically()); if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { child.measure(widthSpec, heightSpec); } } 复制代码
我们看到,在 measureChildWithMargins
方法中,将刚刚得到的 insets
的值与 Recyclerview 的 Padding 以及当前 ItemView 的 Margin 相加,然后作为 getChildMeasureSpec
的第三个参数传进去:
public static int getChildMeasureSpec(int parentSize, int parentMode, int padding, int childDimension, boolean canScroll) { int size = Math.max(0, parentSize - padding); int resultSize = 0; int resultMode = 0; //...省略部分代码 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } 复制代码
getChildMeasureSpec
方法的第三个参数标注为 padding
,在方法体这个 padding
的作用就是计算出 size
这个值,这个 size
是就是后面测量中 Child(ItemView) 能达到的最大值。
也就是说我们设置的 ItemView 的 Margin 以及 ItemDecoration.getItemOffsets
中设置的值到头来也是跟 Parent 的 Padding 一起来计算 ItemView 的可用空间,也就印证了上面的图片,在上面说了该图不精确就是因为
- parent-padding
- layout_margin
- insets(all outRect)
他们是一体的,并没有划分成一段一段这样,图中的 outRect
也应该改为 insets
,但是图中的形式可以更方便我们理解。
3. onDraw
public void onDraw(Canvas c, RecyclerView parent, State state) { onDraw(c, parent); } /** * @deprecated Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)} */ @Deprecated public void onDraw(Canvas c, RecyclerView parent) { } 复制代码
onDraw
方法有两个重载,一个被标注为 @deprecated
,即弃用了,我们知道,如果重写了 onDraw
,就可以在我们上面的 getItemOffsets
中设置的范围内绘制,知其然还要知其所以然,我们看下源码里面是怎样实现的 #RecyclerView.java
@Override public void onDraw(Canvas c) { super.onDraw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); } } 复制代码
在 ReyclerView
的 onDraw
方法中,将会把所有 Decoration
的 onDraw
方法调用一遍,而且会把 Recyclerview#onDraw(Canvas)
方法中的Canvas传递给 Decoration#onDraw
,也就是说我们在Decoration中拿到了整个 RecyclerView 的 Canvas,那么我们基本就可以随意绘制了,但是我们使用中会发现,我们绘制的区域如果在 ItemView 的范围内就会被盖住,这是为什么呢?
由于View的绘制是先执行 draw(Canvas)
再到 onDraw(Canvas)
的,我们复习一波自定义View的知识,看下View的绘制流程: #View.java
public void draw(Canvas canvas) { /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); //注释1 // Step 4, draw the children dispatchDraw(canvas); //注释2 ... // we're done... return; } } 复制代码
我们直接看注释1与注释2那段,可以看到,View的绘制是先绘制自身(onDraw调用),然后再绘制child,所以我们在 Decoration#onDraw
中绘制的界面会被 ItemView 遮挡也是理所当然了。
所以我们在绘制中就要计算好绘制的范围,使绘制范围在上面彩图中蓝色区域内,即 getItemOffsets
设置的范围内,避免没有显示或者过分绘制的情况。
4.onDrawOver
public void onDrawOver(Canvas c, RecyclerView parent, State state) { onDrawOver(c, parent); } /** * @deprecated * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)} */ @Deprecated public void onDrawOver(Canvas c, RecyclerView parent) { } 复制代码
onDrawOver
跟 onDraw
非常类似,也是两个重载,一个被弃用了,看名称我们就基本能知道这个方法的用途,它是用于补充 onDraw
的一个方法,由于 onDraw
会被 ItemView 覆盖,所以我们想要绘制一些漂浮在RecyclerView顶层的装饰就无法实现,所以就有了这个方法,他是在 ItemView 绘制完毕后才会被调用的,看下源码的实现: #RecyclerView.java
@Override public void draw(Canvas c) { super.draw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); } ... } 复制代码
super.draw(c)
就是我们在上面分析的 View#draw(Canvas)
方法,会调用一系列的绘制流程,包括 onDraw
(ItemDecoration的onDraw)以及 dispatchDraw
(ItemView的绘制),走完这些流程后才会调用 Decoration#onDrawOver
方法.
到此,我们就可以得出 onDraw
> dispatchDraw
(ItemView的绘制)> onDrawOver
的执行流程。
以上所述就是小编给大家介绍的《ItemDecoration深入解析与实战(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 新书上市 -《Elasticsearch 源码解析与优化实战》
- ItemDecoration深入解析与实战(二)—— 实际运用
- ItemDecoration深入解析与实战(一)——源码分析
- Spring Boot 外部化配置实战解析
- Spring Boot外部化配置实战解析
- Spring Cloud 上手实战-架构解析及实作
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
我的第一本算法书
[日]石田保辉、[日]宮崎修一 / 张贝 / 人民邮电出版社 / 2018-10 / 69.00元
本书采用大量图片,通过详细的分步讲解,以直观、易懂的方式展现了7个数据结构和26个基础算法的基本原理。第1章介绍了链表、数组、栈等7个数据结构;从第2章到第7章,分别介绍了和排序、查找、图论、安全、聚类等相关的26个基础算法,内容涉及冒泡排序、二分查找、广度优先搜索、哈希函数、迪菲 - 赫尔曼密钥交换、k-means 算法等。 本书没有枯燥的理论和复杂的公式,而是通过大量的步骤图帮助读者加深......一起来看看 《我的第一本算法书》 这本书的介绍吧!