首先说下为什么分析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);
}