Android clipToPadding 使用与疑难点解析

前言

ClipXX 系列:

Android clipChildren 使用与疑难点解析
Android clipToPadding 使用与疑难点解析

上篇文章分析了clipChildren,说到它不得不提它的孪生兄弟clipToPadding,一看名字就大概猜得到这俩就是用来clip画布的,接下来本篇将详细分析之。
通过本篇文章,你将了解到:

1、clipToPadding 使用场景
2、clipToPadding 如何使用
3、clipToPadding 原理
4、clipToPadding 在RecyclerView里的运用

1、clipToPadding 使用场景

先来看看Demo:

绿色为父布局,蓝色为子布局。
布局文件如下:

 

    <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 属性是立即生效的。

默认值

默认为true,也就是说默认子布局不能在padding区域绘制。
上述代码在ViewGroup#initFromAttributes 方法里执行的。

clipToPadding 设置效果

先看看默认情况下子布局滑动时的效果:

明显地看出,当子布局(蓝色)在向上移动的过程中,其绘制部分始终不能超越padding绘制。

此时设置父布局的clipToPadding="false",再来看效果:

可以看出,当子布局(蓝色)在向上移动的过程中,其可以在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

clipToPadding==false
 

紫色部分为padding区域。
与第三点的效果一致,此处不再赘述。

本文基于Android 10。
完整代码演示 若是有帮助,给github 点个赞呗~

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android/Java

1、Android各种Context的前世今生
2、Android DecorView 一窥全貌(上)
3、Android DecorView 一窥全貌(下)
4、Window/WindowManager 不可不知之事
5、View Measure/Layout/Draw 真明白了
6、Android事件分发全套服务
7、Android invalidate/postInvalidate/requestLayout 彻底厘清
8、Android Window 如何确定大小/onMeasure()多次执行原因
9、Android事件驱动Handler-Message-Looper解析
10、Android 键盘一招搞定
11、Android 各种坐标彻底明了
12、Android Activity/Window/View 的background
13、Android IPC 之Service 还可以这么理解
14、Android IPC 之Binder基础
15、Android IPC 之Binder应用
16、Android IPC 之AIDL应用(上)
17、Android IPC 之AIDL应用(下)
18、Android IPC 之Messenger 原理及应用
19、Android IPC 之获取服务(IBinder)
20、Android 存储基础
21、Android 10、11 存储完全适配(上)
22、Android 10、11 存储完全适配(下)
23、Java 并发系列不再疑惑

 

作者:fishforest
链接:https://www.jianshu.com/p/5404ff08f4fa
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值