美文网首页Android自定义View
自定义 view - 3大核心方法补充

自定义 view - 3大核心方法补充

作者: 前行的乌龟 | 来源:发表于2018-06-29 03:29 被阅读44次

自定义 view 的3个核心方法

  • onMeasure
    根据 view 的测量模式计算确定 view 的宽高
  • onLayout
    ViewGroup 中对所有的子 view 排版,决定子 view 的位置
  • onDraw
    具体绘制 view

要继续了解的点

我们在了解了自定义 view 的3大核心方法后就完了吗,没有啊,这只是开始呢,3大核心方法中还有一些要补充的点,这些我们清楚之后,会在今后的开发中帮助我们理顺思路:

  • view 的宽高生命周期内的变化
  • onSizeChange 方法是不是一定会执行
  • view 的位移,位置变化会触发 view 自身的那些函数
  • ViewGroup 会执行2次 onMeasure ,2次 onLayout ,1次 onDraw
  • 自定义的ViewGroup 的 setWillNotDraw(false)

setWillNotDraw(false)

在自定义的ViewGroup, 还需要调用setWillNotDraw(false)方法, 否则onDraw方法不会执行哦


view 的宽高生命周期内的变化

view 自身大小的失计算问题其实涉及 view 的 onMeasure 和 onSizeChange 方法,我们要在 view 的真个生命周期范围内观察 view 宽高的变化

重新回顾一下,view 的宽高涉及到2套 API:

  • getWidth() / getHeight():获得View最终的宽 / 高
  • getMeasuredWidth() / getMeasuredHeight():获得 View测量的宽 / 高

然后我们设计一个自定义 view 然后打印一下在 view 的各个生命周期方法中 宽高的数值

自定义 view

public class MyView2 extends View {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        Log.d("AAA", "onMeasure /" + " widthSize = " + widthSize + " , heightSize = " + heightSize);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        Log.d("AAA", "onSizeChanged / w = " + w + " , h = " + h + " , oldw = " + oldw + " , oldh = " + oldh);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        Log.d("AAA", "onLayout / measureWidth = " + getMeasuredWidth() + " , measureHeight = " + getMeasuredHeight() + " , width = " + getWidth() + " , height = " + getHeight());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Log.d("AAA", "onLayout / measureWidth = " + getMeasuredWidth() + " , measureHeight = " + getMeasuredHeight() + " , width = " + getWidth() + " , height = " + getHeight());

    }
}

然后使用一个最简单的布局方案来观察

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.bloodcrown.aaa02.MeasureActivity">

        <com.bloodcrown.aaa02.MyView2
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

</FrameLayout>

结果:


Snip20180627_4.png

从结果来看:

  • onMeasure 方法执行多次,其中计算出来的宽高值都可能会改变
  • onMeasure 方法中 getWidth、getHeight 方法的返回值都是0
  • view 宽高的改变触发了 onSizeChange 方法,onSizeChange 方法在 onLayout 之前执行
  • onLayout 方法中2套 API 的取值都是一样的,在这个时候 getWidth、getHeight 已经可以获取到具体的返回值

然后有一个问题,这里 onSizeChange 方法的执行是因为 onMeasure 执行了2次,view 的大小发生了改变。那么若是view 的大小不改变,那么 onSizeChange 还有机会执行吗

上面的例子中,自定义 view 使用了 match_parent 计算方法引起了 view 大小的变化,那么我们给一个具体值呢。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.bloodcrown.aaa02.MeasureActivity">

        <com.bloodcrown.aaa02.MyView2
            android:layout_width="200dp"
            android:layout_height="200dp"/>

</FrameLayout>

结果:


Snip20180627_5.png

view 的大小没有变化,onSizeChange 方法 一样执行了,其实很好理解,view 的会记录自己的大小,在 view 初始化时 view 的大小是 0啊,所以 onSizeChange 方法是一定会执行的,至少会执行一次


view 的坐标属性变化和位移会触发 view 的哪些生命周期方法

1. UI 重绘方法

UI 重绘方法有下面几个方法:

  • invalidate()
    重绘视图,必须 Ui 线程执行
  • postInvalidate()
    重绘视图,使用 handle 消息机制允许异步执行
  • requestLayout()
    重新布局
  • requestFocus
    局部刷新,只重绘焦点部分或是我们指定的部分

然后我们来看下这几个方法会触发哪几个 view 的函数

  • invalidate


    Snip20180628_3.png

    这么看的话重绘真的是重绘,只会触发 view 的绘制方法

  • postInvalidate


    Snip20180628_5.png

    一样只会触发 view 的绘制方法,看来 postInvalidate 和 invalidate 区别的地方只是支不支持异步执行

  • requestLayout


    Snip20180628_2.png

    计算,布局,绘制 3个方法方法都触发了,看来是把 view 彻底重新走了一遍,大家想想也是,view 的位置和大小要是变化了呢。有的文章说 requestLayout 不会触发 onMeasure 方法,但是自己跑一下,还不是发现3个方法都执行了,也许是 android 版本的问题,这里我使用的是 API 26

  • requestFocus
    这个方法我试了下,不管是游参数的,没参数的都不会触发任何函数,这个方法我也不熟,姑且简单的直接执行了下,没触发任何 view 的函数我也是有些奇怪。就这样吧,哪位看到这里有了解的,请在评论里指点一下

2. view 位移

我们就来简单的设置下 translationY 来看看

结果是没有触发任何 view 的方法,简单翻翻 setTranslationY 方法的源码,里面调用的也是 invalidate 方法,但是没看到 view 的3个核心方法被触发,这么说 view 的位移并不是我们简单想想的 重新布局,具体啥怎么执行的,我也不知道啊,大家自己去研究把

3. view 位置变化

上面我们操作的是 view 的以为属性参数来移动的 view ,那么我们来试试 setTop 这类直接改变 view 坐标参数的方法


Snip20180628_7.png

view 的大小本质是由 left,top,buttom,right 4个参数决定的,所以我们改了这4个参数的任意一个就是改变了 view 的大小,可以看到 触发了 view 的 onSizeChange 大小改变函数,然后重新绘制,布局方法没有触发我有点想不通,大小改变了其实也算是 view 的位置改变了啊。


视图层级和多次测量,布局的关系

view 的计算,布局,绘制函数不是自己调用的,而是父控件 ViewGroup 决定何时,何地调用子 view 的相关方法

这里涉及到一个结论:

ViewGroup 绘制一次会调用子 view 的2次 onMeasure ,2次 onLayout ,1次 onDraw,部分 ViewGroup 具体子类会对自身测量2次,那么就会 调用子 view 的4次 onMeasure

官方文档有句话:

ViewGroup 会执行2 测量,第一次使用 USPENCIFIED 模式测量子 view 的真实大小,第二次使用 EXACTLY 模式再次测量子view

所以就产生了 2次 onMeasure ,2次 onLayout ,1次 onDraw。我打印了一下 view 2次测量,发现其实2次测量传入的测量模式参数都是一样的,具体原因有人说是 viewRootImpl 的原因,viewRootImpl 会执行2测测量,详细的自己去 Google 下

然后我测试了下,发现不同的 ViewGroup 子类中,根据子 view 的测量模式不同,会对子 view 的测量方法执行次数造成影响

FrameLayout 帧布局

view 不论用的哪种测量模式都会造成子 view 2次测量

ConstraintLayout 约束布局

view 使用 match_parent 会执行4测测量,使用具体的宽高值时,比如200dp,只会执行2次测量

match_parent


Snip20180629_9.png

200dp * 300dp


Snip20180629_10.png

从上面的打印信息中,可以清晰的看到 ConstraintLayout 约束布局中 match_parent 测量模式会测量出前后不同的高度参数。

RelativeLayout 相对布局

不管是 match_parent 还是 200dp ,都会触发自子 view 4次测量

match_parent


Snip20180629_16.png

200dp * 400dp


Snip20180629_14.png
LinearLayout 线性布局

同 FrameLayout 帧布局,只会2次测量

关于执行4次 view 的测量可以这样解释

某些 ViewGroup 子类会对 ViewGroup 自身执行2次测量,进而引起子 view 4次测量

在上面的打印日志中,可以看到 viewRootImpl 的身影,viewRootImpl 在第二次测量之后触发了一个大小改变的函数,然后接着是2次测量。可以猜测下是不同的 ViewGroup 子类有时候会触发 window 视图的变化,从而连带着造成 ViewGroup 的再次测量,ViewGroup 一次测量会对子 view 测量2次,那么 window 2次测量 ViewGroup 就会对子 view 测量4次


在多视图层级下,最内层的 view 执行的测量次数和父控件层数和类型的关系

那么继续深入,在多视图层级下,最内层的 view 会执行几次测量呢。

来个3层视图层级的例子,父视图都是 ConstraintLayout,view 是 match_parent 测量模式

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.bloodcrown.aaa02.MeasureActivity">

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <com.bloodcrown.aaa02.MyView2
                android:id="@+id/view_my"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

        </android.support.constraint.ConstraintLayout>
    </android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>

结果:


Snip20180629_17.png

view 执行了16 次测量,16次 = 内层 4次 * 第二层视图 2次 * 第一层视图 2次,我这里用的都是 match_parent,外层 ViewGroup 都会对自己执行2遍测量,比如视图第二层的 ViewGroup ,对自己测量一遍就是最内层的 ViewGroup 对 view 测量4遍,那么 视图第二层的 ViewGroup 对自己测量2遍,就是 最内层的 ViewGroup 对 view 测量8遍,以此类推,就是 16遍

我用 systrace 工具专门看了下这一帧的执行情况


Snip20180629_22.png

systrace 工具自动分析已经开始提示昂贵的测量,布局损耗了,仅仅只是 16 次测量而已,就花了 6 毫秒,大家看到没 view 的 测量很耗费 cpu 资源的,所以我们应该尽可能的减少 view 的测量,优化布局性能任重而道远

我们把第二层视图 ViewGroup 改成 200dp * 200dp ,猜测下 view 应该会执行 4次 * 1次 * 2次,一共 8次测量

看结果:的确是 8次


Snip20180629_20.png

那我们把多有的上层 ViewGroup 都改成 200dp * 200dp

看结果:


Snip20180629_18.png

view 执行2次,等于 ViewGroup 只执行了一次测量,当然在实际中 ViewGroup 不可能都能有个具体的宽高值,但是在我们能够明确知道布局的宽高时,写具体数据是能够大大减少布局中 view 的测量次数的

这个话题就写到这里了,还有其他想法的同学自己试着玩吧。另外我们有兴趣的同学可以用 systrace 全程观察下


最后我们来看下 view 整个 acitivty 页面中的方法调用

20161030164616308.png

在 acitivty resume 之后 布局view 才会加入到 window 中,然后开始整个布局的测量,布局,绘制

相关文章

网友评论

    本文标题:自定义 view - 3大核心方法补充

    本文链接:https://www.haomeiwen.com/subject/kwpvyftx.html