UI绘制流程(三)

UI绘制流程的起始点 ViewRootImpl#performTraversals()方法中:

此方法里分别调用了:
///测量
performMeasure()
// 摆放布局
performLayout()
// 绘制
performDraw()

这也是我们自定义UI布局时注意的过程 : 测量(Measure)—>布局(Layout)—>绘制(Draw)


Measure测量过程:

1. 通过getRootMeasureSpec(int windowSize, int rootDimension)方法传入父容器windowSize(具体数值) rootDimension (LayoutParams.(width|height))获取子view宽高测量模式;
具体代码:
 private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // 窗口不能调整大小。强制根视图为windowSize
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // 窗口可以调整大小。设置根视图的最大大小。
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // 窗口想成为一个确切的大小。强制根视图是那个大小。
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
MeasureSpec:

在Measure流程中,系统将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,在onMeasure中根据这个MeasureSpec来确定view的测量宽高

测量模式:
  • EXACTLY :父容器已经测量出所需要的精确大小,这也是childview的最终大小------match_parent,精确值
  • ATMOST : child view最终的大小不能超过父容器的给的------wrap_content
  • UNSPECIFIED: 不确定,源码内部使用-------一般在ScrollView,ListView
MeasureSpec里通过和一个数值M size 模式 与或非运算 得到一个数值(测量模式值) 而后通过M进行一些运算可以拿到父容器 size 模式
2. 调用performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) 两个参数是根据父容器的宽高测量模式 ,在方法中 调用 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)又在这个方法里调用了onMeasure(int widthMeasureSpec, int heightMeasureSpec) 而 mView 为DecorView 而,DecorView继承FrameLayout ,onMeasure方法在FrameLayout被重写了,所以最终调用的是FrameLayout onMeasure方法;
3. FrameLayout onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法:
容器view

1.获取子view数并遍历;
2.遍历过程 获取view child 判断 child.getVisibility() != GONE 时调用measureChildWithMargins()方法测量子view
遍历代码:

 for (int i = 0; i < count; i++) 

            final View child = getChildAt(i);//获取子view

            if (mMeasureAllChildren || child.getVisibility() != GONE) {//判断GONE 是GONE不用测量

                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);//测量child以及它的子view

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();//获取view LayoutParams 
                //获取所有子view中最大的宽或高
                maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

2.1.measureChildWithMargins()方法中传入了 child:子view, parentWidthMeasureSpec:父容器宽模式 parentHeightMeasureSpec:父容器高模式 等
2.2.measureChildWithMargins()具体执行为 代码:

    protected void measureChildWithMargins(View child,  int parentWidthMeasureSpec, int widthUsed,  int parentHeightMeasureSpec, int heightUsed) {
        //获取子view MarginLayoutParams
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
       //根据子view宽或高对应的  父容器模式  Padding Margin width 获取该控件的测量模式
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
         //继续测量子view 的子view 直到视图view为止
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
  1. 遍历完成之后获取了子view中最大宽 或 高 调用setMeasuredDimension()方法为该view设置宽高
setMeasuredDimension()后才可以获得view的宽高

小结:ViewGroup遍历测量Child三方法 自定义中使用:

  • measureChildWithMargins // 有Margin测量
  • measureChild// 测量这个view 没有Margin测量 自己遍历
  • measureChildren//遍历所有子view完成没有Margin测量
视图view

根据view设置内容 或呈现内容 来完成测量
setMeasuredDimension()调用完成测量

测量总结 自定义View,ViewGroup:

View:
  • 套路:根据父容器传来的测量模式确定view宽高 以及自己内容 最终调用setMeasuredDimession方法来保存自己的测量宽高
            final int specMode = MeasureSpec.getMode(measureSpec);
            final int specSize =  MeasureSpec.getSize(measureSpec);
            switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
             
                break;
            case MeasureSpec.AT_MOST:
                
                break;
            case MeasureSpec.EXACTLY:
            //完成宽高测量
                break;
        }
       setMeasuredDimension(width, height);
ViewGroup
  • 1、测量子view的规格大小 measureChildWithMargins measureChild measureChildren等方法
  • 2、通过子view的规格大小来确定自己的大小 setMeasuredDimession

Layout测量过程:

大概过程是Measure一样

ViewGroup

重写onLayout() 根据里要的样式计算每个view left, top, right, bottom 在调用子view layout(left, top, right, bottom)方法完成布局

你可能感兴趣的:(UI绘制流程(三))