这段时间在学习android中view的工作原理与自定义View的相关内容,所以未来这这几篇博客都总结一下相关的知识吧。
首先我们要了解和熟悉两个概念,DecorView
与 MeasureSpec
.
DecorView
我们在设置Activity
的界面时,用的就是这句话 setContentView(R.layout.activity_main)
,那么大家有没有疑问呢,这个名字有点奇怪啊,为什么是setContentView
?难道不应该是setView
吗?这个问题就要从DecorView
来说起了。源码就不追踪了,直接说结论。
-
DecorView
就是我们的Activity
(或者整个Window
界面)的最*View。 -
DecorView extends FrameLayout
,它里面只有一个子元素为LinearLayout
,代表整个Window
界面。 -
LinearLayout
布局也包裹了两个FrameLayout
。- 上面的Framelayout是标题栏(
TitleBar
或ActionBar
)。具体形式和android版本及主题有关。它默认包括了一个TextView,当然我们也可以自定义标题栏。 - 下面的
FrameLayout
就是内容栏。它的id就是content
,我们通过setContentView
所设置的布局文件其实就是添加在内容栏当中,所以这个方法就是叫做setContentView
。
- 上面的Framelayout是标题栏(
因此现在我们可以体会到这个方法的命名的确很好。所以我们开发的命名也尽量做到望名知意,有理有据.
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
现在我们就知道所谓的DecorView
是怎么一回事了,google了一张图片可以比较清晰的表达这个问题。
其中图片的绿色部分,就是我们在布局中写的 布局文件了。
默认标题栏只有一个TextView,我们也是可以自定义的。
//@author www.yaoxiaowen.com 三句话的顺序不要颠倒。
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.custom_title);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title_layout);
那我们如何得到content
这个view呢。
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
那我们又如何得到我们添加的布局viewGroup呢?
//结合上一句代码
content.getChildAt(0);
MeasureSpec
在讨论MeasureSpec
之前,我们先回忆一下,在布局中,我们是怎么设置一个View的尺寸大小的。
//height类似,所以就不写了,仅拿 width举例子
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_width="100dp"
我们可以通过 LayoutParams
来设置view的尺寸,但是仅依靠我们设置的参数,能完全决定view的尺寸吗? 肯定是不能的。因为一个View大小还要受到父ViewGroup
的影响。
一个人的命运,既要靠自己奋斗, 也要考虑历史进程。所以一个view的尺寸大小,既要考虑自己所希望的大小,但是也要考虑父ViewGroup
对其所施加的影响。否则如果View自己就能完全决定自己尺寸的大小,那真是没王法了。
MeasureSpec
其实就是View当中的一个静态工具类,翻译过来就是 测量说明书。 代表了View在测试过程中所受到的约束。
在View
的测量过程中,系统将View自身的 LayoutParams
结合父容器所施加的规则转换成对应的MeasureSpec
,然后再根据这个MeasureSpec
来测量出View的尺寸宽高 。
MeasureSpec
代表了一个32位的int类型的值。高两位是SpecMode
(测量模式), 低30位是 SpecSize
(尺寸规格大小)。将SpecMode
和SpecSize
通过位操作封装成一个int值,可以减少内存分配,提升效率。 而MeasureSpec
提供了打包,解包,toString等方法。可以方便操作。
精简后的源码如下:
//View#MeasureSpec
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT; //0
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT; //1073741824
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT; //-2147483648
/**
* 根据尺寸和模式创建一个约束
*/
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
* Extracts the mode from the supplied measure specification.
* 从约束规范中获取模式
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
* 从约束规范中获取尺寸
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
SpecMode分为三类,具体的含义如下:
SpecMode | 对应的布局参数 | 输出值 | 说明 |
---|---|---|---|
UNSPECIFIED(未指明) | 无 | 0 | 父容器不对子控件做任何限制,要多大给多大,该情况一般用于系统内部,表示一种测量状态 |
EXACTLY(精确) | match_parent/具体值(dp,px) | 1073741824 | 已经检测出View所需要的大小,此时View的最终大小就是SpecSize的值,注意,如果是match_parent,就是说明子控件把父容器剩下的尺寸都要了 |
AT_MOST(至多) | wrap_content | -2147483648 | 父容器并不知道子控件到底需要多大,但是它指定了一个可用的尺寸SpecSize,子控件的大小不能超过该值,该情况下,不同view有不同的默认实现方式,比如TextView就默认包裹所有字符 |
MeasureSpec与LayoutParams的对应关系
我们在前面就说过,一个View的尺寸受到父容器和本身LayoutParams
双重影响。在进一步的讨论之前,我们先来看一个ViewGroup
中的一个重要方法。
// #ViewGroup#getChildMeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension)
看方法名字,就可以知道是测量子view的MeasureSpec
,并且该方法是在ViewGroup
中调用的。
它的三个参数意思如下:
-
int spec
:ViewGroup
自身的spec,HeightMeasureSpec
或WidthMeasureSpec
。(如果站在child的角度来看,就是父容器)。 -
int padding
: 如果是width,则是ViewGroup
左右Padding+子View左右Margin+widthUsed。
如果是height,则是ViewGroup
上下Padding+子View上下Margin+heightUsed。 -
int childDimension
: 就是 child的LayoutParmams
(lp.width
或lp.height
)。
该方法具体实现如下:
ViewGroup#getChildMeasureSpec
//此方法用来计算一个合适子视图的尺寸大小 (HeightMeasureSpec或者WidthMeasureSpec)
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//得到父容器的size和mode
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//当前ViewGroup剩余空间的大小。(站在子child的角度就是父容器剩余空间的大小).
//我们可以理解成 parentSize-padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//在ViewGroup的源码定义中, LayoutParams.MATCH_PARENT = -1
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
//在ViewGroup的源码定义中, LayoutParams.MATCH_PARENT = -2
} 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;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
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;
// Parent asked to see how big we want to be
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;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
该方法主要用来计算一个合适的子view的大小。里面是一系列的if else判断,其实它的内容,可以用一张表更加清晰的表现。
先记着这些内容,因为它在下面更近一步的讨论中有着非常重要的作用。
参考内容:
github:
https://github.com/yaowen369
欢迎对于本人的博客内容批评指点,如果问题,可评论或邮件(yaowen369@gmail.com)联系
<p >
欢迎转载,转载请注明出处.谢谢
</p>
<script type="text/javascript">
function Curgo()
{
window.open(window.location.href);
}
</script>