前言
ClipXX 系列:
Android clipChildren 使用与疑难点解析
Android clipToPadding 使用与疑难点解析
上篇文章分析了clipChildren,说到它不得不提它的孪生兄弟clipToPadding,一看名字就大概猜得到这俩就是用来clip画布的,接下来本篇将详细分析之。
通过本篇文章,你将了解到:
1、clipToPadding 使用场景
2、clipToPadding 如何使用
3、clipToPadding 原理
4、clipToPadding 在RecyclerView里的运用
1、clipToPadding 使用场景
先来看看Demo:
162057364460.jpg
绿色为父布局,蓝色为子布局。
布局文件如下:
<com.example.androiddemo.clippadding.ClipPaddingViewGroup
android:background="@color/green"
android:clipToPadding="true"
android:layout_marginTop="20dp"
android:paddingTop="20dp"
android:scrollX="-20dp"
android:layout_width="300dp"
android:layout_height="300dp">
<com.example.androiddemo.clippadding.ClipPaddingView
android:clickable="true"
android:background="@color/red"
android:layout_width="200dp"
android:layout_height="200dp">
</com.example.androiddemo.clippadding.ClipPaddingView>
</com.example.androiddemo.clippadding.ClipPaddingViewGroup>
可以看出,给父布局设定了paddingTop=20dp,因此测量、摆放、绘制子布局时留出对应的padding距离。
思考一个问题:子布局能够在padding的区域绘制吗?
答案是肯定的,这工作将由clipToPadding 属性来完成。
2、clipToPadding 如何使用
clipToPadding 是ViewGroup的属性,通常来说自定义属性都有两种设置方式:
1、xml(静态)
2、代码设置(动态)
静态设置
在对应的父布局xml里设置即可。
android:clipToPadding="true"
android:clipToPadding="false"
动态设置
#ViewGroup.java
public void setClipToPadding(boolean clipToPadding) {
if (hasBooleanFlag(FLAG_CLIP_TO_PADDING) != clipToPadding) {
//若是值有改变,则重新设置
setBooleanFlag(FLAG_CLIP_TO_PADDING, clipToPadding);
//刷新
invalidate(true);
}
}
可以看出,设置值之后立即刷新了,说明clipToPadding 属性是立即生效的。
默认值
image.png默认为true,也就是说默认子布局不能在padding区域绘制。
上述代码在ViewGroup#initFromAttributes 方法里执行的。
clipToPadding 设置效果
先看看默认情况下子布局滑动时的效果:
tt0.top-836202.gif
明显地看出,当子布局(蓝色)在向上移动的过程中,其绘制部分始终不能超越padding绘制。
此时设置父布局的clipToPadding="false",再来看效果:
tt0.top-051418.gif
可以看出,当子布局(蓝色)在向上移动的过程中,其可以在padding区域绘制了。
问题又来了:为什么一开始没绘制在padding区域,而滑动的时候可以绘制呢?
这就需要从源码的角度去分析问题了,接着来看看。
3、clipToPadding 原理
既然是ViewGroup 里的属性,先从它下手寻找,别说,还真发现了端倪。
#ViewGroup.java
protected void dispatchDraw(Canvas canvas) {
//查询是否设置了clipToPadding
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
//若设置了,则将canvas裁减
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
}
以关闭硬件加速为例,该Canvas会传递给子布局,也就是说当clipToPadding == true时,子布局的Canvas会被父布局裁减,而裁减的尺寸即为padding的距离(此处忽略scroll距离)。
接着来分析:为什么一开始没绘制在padding区域,而滑动的时候可以绘制呢?
初始绘制
以最开始的Demo为例:
父布局尺寸:
width == height == 300dp
子布局尺寸:
width == height == 200dp
若是设置父布局paddingTop == 20dp,那么正常情况下,子布局的Canvas尺寸如下:
Canvas = (0, 20dp, 200dp, 200dp)
这是由父布局配合子布局测量和摆放过程确定的,也就是说子布局的mTop = 20dp
因此一开始,子布局就不能在padding里绘制的。
滑动绘制
虽然Canvas被限制了尺寸,但是我们还可以移动Canvas。因此当子布局进行滑动的时候,因为设置了clipToPadding==true,所以父布局不会对子布局的Canvas进行裁减,既然不进行裁减,当然可以显示了(在父布局范围内,若是超出父布局,就涉及到ClipChildren属性了)。
转为数值表示如下:
子布局的Canvas 在纵向上移动到0~20dp是可以显示的,也即是子布局没有被padding限制住。
由上分析可知clipToPadding 使用场景为:
当父布局设置了padding,而又想子布局在滑动的时候可以无视padding,也就是在padding区域绘制,此时就需要clipToPadding 属性。
4、clipToPadding 在RecyclerView里的运用
网上几乎所有文章在分析clipToPadding 时,都先抛出RecyclerView的滑动效果,并没有说明为啥会这样。通过前面几点的分析,我们清晰地知道了clipToPadding的引入原因及其原理,而RecyclerView Item滑动至padding区域只是clipToPadding 运用场景之一。
先看布局文件:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:visibility="visible"
android:layout_marginTop="30dp"
android:clipToPadding="false"
android:paddingTop="20dp"
android:background="@color/purple_200"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
RecyclerView 设置paddingTop="20dp"。
再看看分别设置clipToPadding 不同值的效果:
clipToPadding==true
tt0.top-203678.gifclipToPadding==false
tt0.top-233112.gif紫色部分为padding区域。
与第三点的效果一致,此处不再赘述。
本文基于Android 10。
完整代码演示 若是有帮助,给github 点个赞呗~
网友评论