通过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); }
- 通过FrameLayout分析onMeasure
- onMeasure实例分析
- CoordinateLayout onMeasure流程分析
- 自定义控件---OnMeasure()方法分析
- RelativeLayout的onMeasure源码分析
- 通过查询资料理解onMeasure()方法
- FrameLayout
- FrameLayout
- FrameLayout
- FrameLayout
- Framelayout
- FrameLayout
- FrameLayout
- FrameLayout
- FrameLayout
- FrameLayout
- FrameLayout
- FrameLayout
- 88. Merge Sorted Array
- Hadoop installation Local (Standalone) Mode
- linux 用户、用户组的使用及ssh连接
- python之元组
- PowerDesigner逆向工程导入MYSQL数据库总结
- 通过FrameLayout分析onMeasure
- C++分解质因数
- MQTT协议学习心得
- java深拷贝浅拷贝
- MySql安装配置及使用入门
- 小Win,点一份APC(Apc机制详解)(一)
- 简述throw和throws的区别
- GC的简单介绍
- OpenCV的读取图像使用注意事项