Android 自定义控件 layout

Read The Fucking Source Code

引言

Android自定义控件涉及View的绘制分发流程

源码版本(Android Q — API 29)

前置依赖 【Android 绘制流程】

1. 预览

View绘制流程分发-layout

2. layout

2.1 View 和 ViewGroup 的区别

  • View:View主要执行layout方法,使用 serFrame 方法来设置本身 View 的四个顶点的位置,确定View本身的位置。
  • ViewGroup:ViewGroup主要执行onLayout方法,递归遍历所有子View,确定子View的位置。

2.2 View 和 ViewGroup 的汇总

2.2.1 View layout

View布局流程

2.2.2 ViewGroup layout

ViewGroup布局流程

2.3 layout 自顶向下分发

2.3.1 DecorView 分发

  • 最顶层(DecorView)分发的 layout
  • 我们来看ViewRootImpl中的 performLayout() 方法
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
  //代码省略……

  //mView即DecorView
  final View host = mView;

  //代码省略……

  //进行布局分发
  host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

  //代码省略……
}
复制代码

 看到这里,那 host.getMeasuredWidth() / host.getMeasuredHeight() 是什么?它是直接调用 View 中的方法,其实就是经过 measure 后的 DecorView 的测量宽度和高度。在 【Android 自定义控件 measure】 中有说明。

2.3.2 ViewGroup 分发

ViewGroup(举例:LinearLayout)分发的 layout

2.3.2.1 我们先来看 ViewGroup 中的 layout() 方法

@Override
    public final void layout(int l, int t, int r, int b) {
        //ViewGoup的LayoutTransition动画,动画相关,可忽略
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            //调用父类(View)的layout方法
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }
复制代码

 ViewGroup 里面的 layout 最终会调入到父类 View 中的 layout ,View 的 layout 后面讲解。这里可以先告诉大家,最终会调用 View 的 onLayout 方法,而 ViewGroup 的 onLayout 是抽象方法,所以它的子类 LinearLayout 必须要实现。

2.3.2.2 我们再来看 LinearLayout 中的 onLayout() 方法。

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            //纵向布局,对应xml中的:android:orientation="vertical"
            layoutVertical(l, t, r, b);
        } else {
            //横向布局,对应xml中的:android:orientation="horizontal"
            layoutHorizontal(l, t, r, b);
        }
    }
复制代码

2.3.2.3 挑一个纵向的吧,我们再来看 LinearLayout 中的 layoutVertical() 方法。

void layoutVertical(int left, int top, int right, int bottom) {
         //代码省略……

        //每个子View经过测量,都确定了尺寸,即宽和高,那么布局的确定只需要 left和top的位置。
        int childTop;
        int childLeft;
        
        //代码省略……
        
        //遍历子View
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                 //计算子View的测量宽 / 高值
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();

                //代码省略……
            
                //确定自身子View的位置(递归调用子View的setChildFrame(),实际上是调用了子View的layout)
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                
                //代码省略……
            }
        }
    }
复制代码

2.3.2.4 我们再来看 LinearLayout 中的 setChildFrame() 方法。

private void setChildFrame(View child, int left, int top, int width, int height) {
        //开始递归调用View的layout方法
        child.layout(left, top, left + width, top + height);
    }
复制代码

 又一次回到了 View 的 layout 方法,接下来就看 View 分发的 layout。

2.3.3 View 分发

 我们先来看 View 中的 layout() 方法。

public void layout(int l, int t, int r, int b) {
        //代码省略……

        // 当前视图的四个顶点
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        
        // 初始化四个顶点的值、判断当前View大小和位置是否发生了变化
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        
        //若视图的大小 & 位置发生变化。会重新确定该View所有的子View在父容器的位置:onLayout()
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //重新确定该View所有的子View在父容器的位置
            onLayout(changed, l, t, r, b);

            //代码省略……
        }
    }
复制代码

 我们先来看 View 中的 onLayout() 方法。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }
复制代码

  空空如也,其实 View 的布局由父容器决定,所以空实现是正常的,当然也可以在自定义 View 中进行更改。

小编的扩展链接

《Android 视图模块 全家桶》

优秀博客推荐

Android开发之自定义控件(二)—onLayout详解
自定义View Layout过程 – 最易懂的自定义View原理系列(3)

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享