通过FrameLayout分析onMeasure

来源:互联网 发布:大数据时代论文3000字 编辑:程序博客网 时间:2024/05/16 18:17

首先说下为什么分析FrameLayout .
……
因为他简单啊!! 哪像相对布局和线性布局 , 源码太长了…

大家也知道FrameLayout 的摆放规则 , 大部分使用是根据layout_gravity属性来控制摆放位置 , 如果你没有使用这个属性 , 默认就是放在左上角 , 并且最后添加的子View会显示在最上层 .

那么这篇文章从DecorView开始分析它的padding , margin等 , 这些值是怎么算到onMeasure()和onLayout()的 , 以及简单讲一下addView()方法和LayoutParam .

那么开始分析下

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:padding="16dp">    <TextView        android:text="123"        android:layout_width="match_parent"        android:layout_height="wrap_content" /></FrameLayout>

上篇文章中讲到 , Activity的setContentView()方法 , 执行完setContentView()方法后 , 这个布局已经添加到了DecorView上面了. 其实setContentView()方法里面也是调用了addView()方法将子View添加到父View上去的 , 再看addView()方法 , 他调用了requestLayout()方法 , 但是那么根据上篇文章讲到的requestLayout()方法会执行ViewRootImpl的requestLayout()接着调用performTraversal()方法来执行测量 , 摆放 , 绘制 .
但是!
在setContentView()的时候虽然调用了addView()方法 , 但是这时ViewRootImpl还没有实例化 , 它的实例化其实是在activity.attach()方法中 new 出来的 , 所以第一次的performTraversal()方法是在 handleResume()调用的 .
那么再重新屡一下DecorView在添加到Window之后他的第一次测量 、摆放和绘制 .
1. ActivityThread的handleResume()
2. 在handleResume()方法中调用了ViewRootImpl的setView()
3. ViewRootImpl的setView()方法中把decorView添加到了PhoneWindow
4. 接着调用了ViewRootImpl的requestLayout()
5. 最终他会在requestLayout()中调用performTraversals()之后 , 会使每个View执行他们的测量等方法

那么从DecorView开始看一下DecorView的 lp 是什么时候设置进去的并且值是什么

    DecorView(Context context, int featureId, PhoneWindow window,            WindowManager.LayoutParams params) {        ......    }    protected DecorView generateDecor(int featureId) {        ......        return new DecorView(context, featureId, this, getAttributes());    }

可以看到DecorView构造函数的最后一个参数就是DecorView的LayoutParams

    private final WindowManager.LayoutParams mWindowAttributes =        new WindowManager.LayoutParams();    public final WindowManager.LayoutParams getAttributes() {        return mWindowAttributes;    }

getAttributes()获得的其实就是mWindowAttributes

        public LayoutParams() {            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);            type = TYPE_APPLICATION;            format = PixelFormat.OPAQUE;        }

答案在WindowManager的LayoutParams的无参构造方法当中 , 也就是DecorView的lp宽高都是MATCH_PARENT .

接着看performTraversals()中的performMeasure()方法

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    private static int getRootMeasureSpec(int windowSize, int rootDimension) {        int measureSpec;        switch (rootDimension) {        case ViewGroup.LayoutParams.MATCH_PARENT:            // Window can't resize. Force root view to be windowSize.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);            break;        case ViewGroup.LayoutParams.WRAP_CONTENT:            // Window can resize. Set max size for root view.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);            break;        default:            // Window wants to be an exact size. Force root view to be that size.            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);            break;        }        return measureSpec;    }

调用getRootMeasureSpec()方法获取了宽和高的规格 , mWidth和mHeight是屏幕的宽高 , lp.width和lp.height根据上面的代码都是MATCH_PARENT , 那么performMeasure()方法的两个规格: 模式 : EXACTLY , 大小 : 屏幕大小 .

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");        try {            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);        } finally {            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }    }

接着调用了DecorView的measure()方法 , 传过去了从上一层获取的两个规格 , measure是final方法 , 子类不能重写 , 所以DecorView的measure()就是View的measure()方法 , 接着调用了咱们常见的onMeasure()方法还是传过去了上层传过来的两个规格 .
接着就是DecorView的onMeasure()方法了. 它里面的逻辑是一个相当于递归的过程
//如果是View , 那么直接测量自身 , 如果是ViewGroup , 那么根据自己的测量规格 , 根据每个子View的lp给子View设置规格 , 这么一层一层传递下去 , 直到所有的View测量完毕 .

现在我们是将上面写简单的布局id以setContentView()方式传过去了 , 那么再屡一下布局的层级结构
从父View到子View
DecorView -> LinearLayout -> FrameLayout -> FrameLayout(上面写的布局中的FrameLayout)
在上面分析完了DecorView的两个规格 , 那么跳过中间这些层 , 直接看自己写的FrameLayout

    //这里的两个规格模式都是EXACTLY , 大小是根据主题等定的(例如你是有ActionBar , 那么size是屏幕大小减去ActionBar的高度 , 这里可以看成是屏幕大小)    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        ...        for (int i = 0; i < count; i++) {            final View child = getChildAt(i);            if (mMeasureAllChildren || child.getVisibility() != GONE) {                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);        }    }

我减少了onMeasure的代码 , 重点看一下他是如何根据自身的规格以及子View的lp生成规格传递给子View的
这里遍历每个子View后调用了ViewGroup中帮我们封装好的measureChildWithMargins()方法

    protected void measureChildWithMargins(View child,            int parentWidthMeasureSpec, int widthUsed,            int parentHeightMeasureSpec, int heightUsed) {        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();        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);        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

这里child.getLayoutParams()在强转成了MarginLayoutParams , child的lp是在inflate布局的时候会调用addView(View child, LayoutParams params)的重载方法 , 而这个params就是lp

final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);

调用了generateLayoutParams()方法获取的lp.
在看一下FrameLayout的generateLayoutParams()

    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        return new FrameLayout.LayoutParams(getContext(), attrs);    }

返回了一个FrameLayout的内部类 , 而这个类继承了MarginLayoutParams , 所以上面child就可以强转了

这里在提一下 , 如果你是用的是addView(View child)一个参数的方法 , 并且没有调用这个要添加的View的setLayoutParams方法 , 那么他就会调用generateDefaultLayoutParams()方法

        LayoutParams params = child.getLayoutParams();        if (params == null) {            params = generateDefaultLayoutParams();            if (params == null) {                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");            }        }

那么知道了child的lp是margin了那么 接着往下看代码

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin                        + widthUsed, lp.width);

测量宽度规格和高度规格是一样的 , 这里就看一下测量宽度的规格 .
这里调用了getChildMeasureSpec()方法获取了宽度的规格 .
第一个参数 : 父View的宽度规格
第二个参数 : 父View的左右padding以及 , 子View的左右margin + widthUsed(上面传的是0 , 可以忽略)
第三个参数 : 子View的宽度(有三种可能WRAP_CONTENT , MATCH_PARENT , 固定大小)

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        //获取父View的测量模式        int specMode = MeasureSpec.getMode(spec);        //获取父View的测量大小        int specSize = MeasureSpec.getSize(spec);        //父View的大小减去第二个参数 , 就是子View的有效大小 , 不能小于0        int size = Math.max(0, specSize - padding);        //最终测量规格的大小        int resultSize = 0;        //最终测量规格的模式        int resultMode = 0;        switch (specMode) {        //父View 的模式为EXACTLY时        case MeasureSpec.EXACTLY:            //第三个参数刚才列出了三种情况 , >=0 就是固定大小的情况            //因为WRAP_CONTENT为-2 , MATCH_PARENT为-1            if (childDimension >= 0) {                //当子View的宽度为固定大小时 , 测量大小设置其固定大小, 模式为EXACTLY                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                //当子View的宽度为MATCH_PARENT时 , 测量大小为上面算出来的子View的有效大小 , 并且模式为EXACTLY                resultSize = size;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                //当子View的宽度为WRAP_CONTENT时 , 测量大小为子View的有效大小 , 并且模式为AT_MOST(AT_MOST可以认为是不确定大小 , 但是给你一个范围就是不能大于子View的有效大小 , 而EXACTLY就是固定大小了)                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        //当父View大小不确定的情况:        case MeasureSpec.AT_MOST:            //子View大小固定时还是跟上面一样的情况 , 不考虑父View的情况            if (childDimension >= 0) {                // Child wants a specific size... so be it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                //子View的宽度是MATCH_PARENT还是WRAP_CONTENT都一样 ,                 //模式为AT_MOST , 不能超过子View有效大小                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // 这个情况不讨论.        case MeasureSpec.UNSPECIFIED:            if (childDimension >= 0) {                // Child wants a specific size... let him have it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size... find out how big it should                // be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            }            break;        }        //最后根据的出来的模式和大小 , 返回一个规格        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

测量完所有子View之后就可以调用child.getMeasuredWidth()方法获取子View的大小了 , 那么获取大小之后父View也可以测量自己的大小了 , 这里FrameLayout会判断 , 背景图片 , maxHeight , minHeight和子View的宽高等元素 , 来获取最终宽高 , 调用setMeasuredDimension()传过去宽高之后 , View的mMeasuredWidth 以及 mMeasuredHeight赋值 , 所以调用getMeasuredWidth()等方法就会有值了 .

补充一点 , ViewGroup帮我们封装了measureChildren()方法和measureChildrenWithMargin()方法 , 三大布局 , 线性布局和帧布局都调用了measureChildrenWithMargin()方法 , 相对布局虽然调用了measureChild()方法 , 但是它重写了measureChild()方法并且把margin算进去了 , 所以大家在自定义ViewGroup的时候如果要算margin的话最好都调用measureChildrenWithMargin()方法就可以了 , 别忘了也重写generateLayoutParams()方法 , 要不然会出现强转错误的

这里讲一下MeasureSepc是如何保存模式和大小的
主要看makeMeasureSpec() , getMode() 和getSize()这三个方法

        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,                                          @MeasureSpecMode int mode) {            if (sUseBrokenMakeMeasureSpec) {                return size + mode;            } else {                //看这个分支就可以了                return (size & ~MODE_MASK) | (mode & MODE_MASK);            }        }

int为32字节 , 可以由32个1和0表示 , 他的前两位保存了模式 , 后三十位保存了大小 .
那么看一下MODE_MASK

        private static final int MODE_MASK  = 0x3 << 30;

0x表示16进制 , 0x3转换为10进制还是3 转换为二进制位11 .
可以表示为 0000 …. 0011 , 前面有30个0

像左移30位的话那么 MODE_MASK可以表示为
1100 …. 0000 前二位为2 , 后面30位都是0

(size & ~MODE_MASK)

先看前面部分 这里包含了 &(与运算) 和 ~(取反运算) 两个符号
~ 这个运算符是将一个二级制全部颠倒过来 , 把1改为0 , 把0改为1
也就是说MODE_MASK 值为 1100 …. 0000 取反后就是 , 0011 …. 1111

& 运算的规则是 两位同时为“1”,结果才为“1”,否则为0
例如:3&5即 0000 0011 & 0000 0101 = 0000 0001因此,3&5的值得1。
那么我们假设 size 是 5 , 和取反后的MODE_MASK 进行与预算
0000 …. 0101 & 0011 …. 1111 = 0000 …. 0101

& 运算的规则既然是所有都位1才可以为1 , 那么MODE_MASK将前两位置为0了 , 也就是说不管你size的值前两位是什么 , 也不可能为1 .

        public static final int UNSPECIFIED = 0 << MODE_SHIFT;        public static final int AT_MOST     = 2 << MODE_SHIFT;        public static final int EXACTLY     = 1 << 30;        (mode & MODE_MASK)

记录模式也和前面是一样的 , 假设mode为EXACTLY , EXACTLY的值为 0100 …. 0000
0100 …. 0000 & 1100 …. 0000 = 0100 …. 0000

这两模式也记录在了前两位 .
最后执行了 |(或运算) 这个运算符的规则是 只要有一个为1 就为1 .
那么将刚才的大小以及模式进行或运算
0000 …. 0101 | 0100 …. 0000 = 0100 …. 0101
这样看来就是前两位存储了模式 后三十位存储了大小 , 两者互相不干扰
那么在看getSize()和getMode()方法就简单了

        public static int getSize(int measureSpec) {            return (measureSpec & ~MODE_MASK);        }        public static int getMode(int measureSpec) {            //noinspection ResourceType            return (measureSpec & MODE_MASK);        }
1 0
原创粉丝点击