Android中View的工作流程之layout过程

文章收藏的好句子:面对困难,不要怕,也不要急,慢慢来,一点点解决,局面会逐渐打开的。

ps:文章是基于 Android Api 31来分析源码的。

目录

1、View 的 layout 过程

     1、1 View(它不是ViewGroup) 的 layout 过程

          1、1、1 原始 View 的 layout 过程

          1、1、2 具体 View 的 layout 过程

     1、2 ViewGroup 的 layout 过程

1、View 的 layout 过程

layout 的过程是 ViewGroup 用来确定子元素的位置,当 ViewGroup 的位置被确定后,如果这个 View 是 ViewGroup 并且有子元素的话,它在onLayout中 会遍历所有的子元素并调用其 layout 方法,在 layout 方法中onLayout方法又会被调用;下面我们先说一下非 ViewGroup 的 layout 过程。

1、1 View(它不是ViewGroup)的 layout 过程

1、1、2 原始 View 的 layout 过程

我们先看一下 View 的 layout 方法;

973b8c9f3a33dae28b6b13f3cf8481b1.png

先看注释2,onLayout 方法是确定当前 View 的所有子元素的位置,我们看看 View 的 onLayout 方法;

2c063af1998c5aed89c3e879b9fedfd2.png

看到没,View 的 onLayout 方法是一个空实现,所以直接用 View 这个类作为 xml 文件中的标签的话是没有子元素的。

好,我们现在看一下注释1中的 setFrame 方法,这个方法位于 View 这个类中;

bf6e099619bf2eb3b6027a2f8896c0de.png

看注释3这4个变量的赋值,left、top、right 和 bottom 就是当前这个 View 的4个顶点的位置,所以说 setFrame 方法就是确定当前 View 的在父容器布局中的位置。

1、1、2 具体 View 的 layout 过程

我这里介绍的具体的 View ,它不是 ViewGroup 哦,它是继承于 View 的子类或者是 View 的子类的子类等等,例如 TextView、ImageView、Button 等;好,我们这里就用 TextView 的 layout 过程来分析,TextView 并没有重写 layout 方法,只是重写了 onLayout 方法;

99866d46bd2a768d191f1697cf9276f7.png

看 TextView 中的 onLayout 方法,它调用了 super.onLayout 方法,而 super 就是 View,所以 super.onLayout 是空实现;看注释4的方法,它只是自动计算和设置文本大小,所以 TextView 的位置设置是在 View 的 setFrame 方法中,同时证明 TextView 它是不可能作为其他 View 的父容器。

1、2 ViewGroup 的 layout 过程

我们知道,Android 中系统自带的 ViewGroup 实现类有 RelativeLayout、LinearLayout、GridLayout、TableLayout、FrameLayout 和 Constraint-Layout,由于 ViewGroup 子类的布局特性不同,所以 ViewGroup 子类对子元素的位置设置也就不同了;这里我们用 FrameLayout 的 layout 过程进行分析,同样 FrameLayout 没有重写 layout 方法,它的父类 ViewGroup 也没有重写 layout 方法,我们只需看它的 onLayout 方法就好了;

f36649d630a3f28d77a080e1a2e0b155.png

FrameLayout 的 onLayout 方法调用了 FrameLayout 的 layoutChildren 方法,我们往下看 layoutChildren 方法;

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {


        //5、
        final int count = getChildCount();


        //6、
        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();


        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();


        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                ......
                //7、
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;


                //8、
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                                lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }


                //9、
                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                                lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }


                //10、
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
  }

看注释5,它是获取子元素的个数;看注释6中的 parentLeft、parentRight、parentTop 和 parentBottom,是获取当前的 FrameLayout 左右上下这四个方向的 padding;看注释7,根据 View 中 LayoutParams 的 Gravity 来计算 FrameLayout 子 View 的四个顶点位置,看是左到右还是右到左;看注释8,根据横坐标来计算子 View 的 left 位置;看注释9,根据纵坐标来计算子 View 的 top 位置;看注释10,知道了子 View 的 left、top、width 和 height 后,递归调用子 View 的 layout 过程并顺便对子 View 的位置进行保存。

Android中View的工作流程之measure过程这篇文章我们有提到过,一般情况下测量的宽高等于最终的宽高对不对?只不过测量宽高形成于 View 的 measure 过程,而最终宽高形成于 View 的 layout 过程,只是这两者的赋值时机不同而已,最终宽高的赋值时机稍微晚一些,所以,在平时开发中,我们可以认为 View 的测量宽高等于最终宽高;但是一些特殊情况下测量宽高和最终宽高不一样,下面我举个例子;

7fe70f828dfd41256a2f05edc34b8df0.png

注释11的代码会导致在任何情况下 View 的最终宽高比测量宽高大40 px,

这样会导致 View 显示不正常,这实了测量宽高真的可以不等于最终宽高,但实际开发中,我们不推荐这样做;有时候,View 需要多次 measure 过程才能确定自己的测量宽高,在前几次的测量过程中,其得出的测量宽高很有可能和最终宽高不一样,到最后,测量宽高和最终宽高是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值