简单的场景分析LinearLayout 源码

时间:2022-07-24
本文章向大家介绍简单的场景分析LinearLayout 源码,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
场景

一个 LinearLayout 垂直方向上包含两个 TextView 和一个 RelativeLayout

RelativeLayout 又包含两个TextView

根据这个场景,分析一下 LinearLayout 的 measureVertical()做了哪些事情

如下图:

xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_orange_dark"
        android:gravity="center"
        android:text="TextView1"
        android:textColor="@android:color/white"
        android:textSize="24sp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:background="@android:color/holo_green_light"
        android:gravity="center"
        android:text="TextView2"
        android:textColor="@android:color/white"
        android:textSize="24sp" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_orange_light">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="100dp"
            android:layout_alignParentLeft="true"
            android:gravity="center"
            android:text="TextView"
            android:textColor="@android:color/white"
            android:textSize="24sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="100dp"
            android:layout_alignParentRight="true"
            android:gravity="center"
            android:text="TextView"
            android:textColor="@android:color/white"
            android:textSize="24sp" />
    </RelativeLayout>

</LinearLayout>

上面是 xml 中的定义

onMeasure()

1.垂直方向上获取同一层级的 view 的数量

2.计算高度

3.计算宽度

依次如下:

1. 垂直方向上获取同一层级的 view 的数量

垂直方向上获取 child 的个数,当前是 3 个,虽然RelativeLayout 也包含两个TextView

但是不是同一级的.

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
 //
 final int count = getVirtualChildCount();
}
2. 计算高度

大体流程如下:

2.1 用for 循环所有的child 控件,分别计算出每个 child高度和 child 的 margin, 累加记为mTotalLength

2.2 mTotalLength 加上 padding,记为heightSize

2.3 如设置背景heightSize和背景比较一下高度取大值,记为heightSize

2.4 heightSize 和父类传入的 heightMeasureSpec参数 比较得出最终LinearLayout的高度.

 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
 
         for (int i = 0; i < count; ++i) {
             final View child = getVirtualChildAt(i);
 
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 
 
             final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
             if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
              //...
             } else {
                 // 计算当前 index 对应的 child 的childHeight
                 final int childHeight = child.getMeasuredHeight();
 
                 // mTotalLength加上margin,
                 mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                        lp.bottomMargin + getNextLocationOffset(child));
             }
         }
          //mTotalLength 加上padding
         mTotalLength += mPaddingTop + mPaddingBottom;
        
         // heightSize 作为计算的高度
         int heightSize = mTotalLength;
 
         //若设置背景的话heightSize 和背景比较一下取大值,
         heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
         
         //结合父类传入的heightMeasureSpec计算最终的高度
         int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); 
 }
3. 计算宽度

计算宽度的过程和高度差不多,这里不再重复,下方源码也有注释,可以对着参考.

最终调用方法

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);

结束onMeasure()流程

源码:

public class LinearLayout extends ViewGroup {
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            // 分析这个
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }    
 
     void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
         mTotalLength = 0;
         int maxWidth = 0;
         int childState = 0;
         int alternativeMaxWidth = 0;
         boolean allFillParent = true;
         final int count = getVirtualChildCount();
         
         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 
         for (int i = 0; i < count; ++i) {
             final View child = getVirtualChildAt(i);
 
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 
 
             final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
             if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                 //...
             } else {
                 //没有设置totalWeight =0, 因此mTotalLength =0
                 final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                 //测量一下child
                 measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                         heightMeasureSpec, usedHeight);
 
                 // 上面测量了 因此这个可以拿到当前 index 的 child 的childHeight
                 final int childHeight = child.getMeasuredHeight();
 
                 final int totalLength = mTotalLength;
                 // 测量第一个 child 时候,mTotalLength为 :第一个child 的childHeight
                 mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                        lp.bottomMargin + getNextLocationOffset(child));
             }
 
             boolean matchWidthLocally = false;
 
             final int margin = lp.leftMargin + lp.rightMargin;
             // 测量的宽度= child 的测量宽度+margin
             final int measuredWidth = child.getMeasuredWidth() + margin;
             //maxWidth 初始值是 0, maxWidth和measuredWidth比较取大的
             maxWidth = Math.max(maxWidth, measuredWidth);
             childState = combineMeasuredStates(childState, child.getMeasuredState());
 
             allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
             if (lp.weight > 0) {
                 //...
             } else {
                 // 结果和matchWidthLocally有关系,matchWidthLocally默认为 false 和上面有关系
                 alternativeMaxWidth = Math.max(alternativeMaxWidth,
                         matchWidthLocally ? margin : measuredWidth);
             }
 
             i += getChildrenSkipCount(child, i);
         }
         // 至此上面的 for 循环结束
         //mTotalLength 为 child 的childHeight之和
 
 
          // 总高度 在这加上 padding ,没有设置padding 都是 0, mTotalLength不变
         mTotalLength += mPaddingTop + mPaddingBottom;
        
         // mTotalLength 赋值给
         int heightSize = mTotalLength;
 
         //没有背景为heightSize
         //getSuggestedMinimumHeight() 是跟设置背景有关,getSuggestedMinimumHeight()无背景=0
         heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
         
         //结合heightMeasureSpec调整计算大小
         //但是 heightMeasureSpec 是 match_parent 因此大小为match_parent 屏幕的高
         int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
         heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
         
         //heightSize 是屏幕高度
         //之差就是 剩余的空间
         int remainingExcess = heightSize - mTotalLength
                 + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
         //totalWeight 是 0
         if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
           //...
         } else {
             // 计算宽度
             alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                            weightedMaxWidth);
         }
         // padding 都是 0
         maxWidth += mPaddingLeft + mPaddingRight;

         // 和getSuggestedMinimumWidth()比较,也是和背景有关
         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
         
         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                 heightSizeAndState);
     } 
}

onLayout()

1.垂直方向上获取同一层级的 view 的数量

2.计算初始摆放的位置

3.依次摆放子view 的位置

依次如下:

1. 垂直方向上获取同一层级的子 view 的数量
 final int count = getVirtualChildCount();
2. 计算初始摆放的位置
 childTop = mPaddingTop;
 //...
 childTop += lp.topMargin;
3. 一次摆放子 view 的位置

setChildFrame()方法确定子 view 的位置

setChildFrame()
//
child.layout(left, top, left + width, top + height);

子view 是 TextView,是 view,会直接调用 setFrame()方法确定 view 的位置,将位置信息保存成员变量

子view 是 RelativeLayout,是 ViewGroup,layout()到onLayout()方法再次递归,最终确定所有view 的位置

onLayout() 部分源码如下:
public class LinearLayout extends ViewGroup {
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            //分析这个
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }
    
    void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;
        
        // 获取宽度
        final int width = right - left;
        int childRight = width - mPaddingRight;
        
        int childSpace = width - paddingLeft - mPaddingRight;
        
        //获取 child 数量
        final int count = getVirtualChildCount();

        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    
        // 根据mGravity 计算childTop
        switch (majorGravity) {
           //...
           default:
               childTop = mPaddingTop;
               break;
        }

        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
               //...
            } else if (child.getVisibility() != GONE) {
                // 获取当前 index 的 child 的 宽度
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();
                
                // 获取gravity
                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                //
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    //...
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }


                childTop += lp.topMargin;
                //child.layout()方法
                // child 是 TextView这里layout()方法进入 View 的layout()方法,会回调onLayoutChange()方法
                // View 的layout()方法 内部调用onLayout()方法,最终执行 TextView的onLayout()
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    } 
    private void setChildFrame(View child, int left, int top, int width, int height) {        
        child.layout(left, top, left + width, top + height);
    }    
    
}

至此,LinearLayout 简单的加载这三个垂直控件的流程分析完毕

至于带 weight 属性的情况,我们后面分析.

更多内容 欢迎关注公众号