Android 视图模块

Android 自定义控件 layout

2021-02-04  本文已影响0人  科技猿人

Read The Fucking Source Code

引言

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

源码版本(Android Q — API 29)

本文涉及Android绘制流程

Android 绘制流程

1. 顶层视角预览 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 过程
2.2.2 ViewGroup的 layout 过程

2.3 layout自顶向下

2.3.1 最顶层(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(举例: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分发的layout

我们先来看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)

上一篇 下一篇

猜你喜欢

热点阅读