在Android自定义控件(一)中主要讲述的时一些绘制的类以及其中的常见方法。自定义控件的步骤基本可以概括为:控件绘制,控件测量以及位置分配 和控件的交互。此文中我们将对控件测量中常用的类和方法进行介绍。
1,View的测量在onMeasure中进行。
MeasureSpec类
MeasureSpec中又三个Mode常量:
UNSPECIFIED表示未指定,爹不会对儿子作任何的束缚,儿子想要多大都可以;
EXACTLY表示完全的,意为儿子多大爹心里有数,爹早已算好了;
AT_MOST表示至多,爹已经为儿子设置好了一个最大限制,儿子你不能比这个值大
view的最终测量尺寸是由view本身何其父容器共同决定的,父容器所谓的“意图”其实就由上述三个常量值表现,
既然如此我们就该对这三个Mode常量做一个判断才行,不然怎么知道爹的意图呢
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 声明一个临时变量来存储计算出的测量值
int resultWidth = 0;
// 获取宽度测量规格中的mode
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
// 获取宽度测量规格中的size
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
/*
* 如果爹心里有数
*/
if (modeWidth == MeasureSpec.EXACTLY) {
// 那么儿子也不要让爹难做就取爹给的大小吧
resultWidth = sizeWidth;
}
/*
* 如果爹心里没数
*/
else {
// 那么儿子可要自己看看自己需要多大了
resultWidth = mBitmap.getWidth();
/*
* 如果爹给儿子的是一个限制值
*/
if (modeWidth == MeasureSpec.AT_MOST) {
// 那么儿子自己的需求就要跟爹的限制比比看谁小要谁
resultWidth = Math.min(resultWidth, sizeWidth);
}
}
int resultHeight = 0;
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
if (modeHeight == MeasureSpec.EXACTLY) {
resultHeight = sizeHeight;
} else {
resultHeight = mBitmap.getHeight();
if (modeHeight == MeasureSpec.AT_MOST) {
resultHeight = Math.min(resultHeight, sizeHeight);
}
}
// 设置测量尺寸
setMeasuredDimension(resultWidth, resultHeight);
}
如上代码所示我们从父容器传来的MeasureSpec中分离出了mode和size,size只是一个期望值我们需要根据mode来计算最终的size,如果父容器对子元素没有一个确切的大小那么我们就需要尝试去计算子元素也就是我们的自定义View的大小,
而这部分大小更多的是由我们也就是开发者去根据实际情况计算的,这里我们模拟的是一个显示图片的控件,那么控件的实际大小就应该跟我们的图片一致,但是虽然我们可以做出一定的决定也要考虑父容器的限制值,当mode为AT_MOST时size则是父容器给予我们的一个最大值,我们控件的大小就不应该超过这个值。
下面是运行效果:
上述代码中涉及的到常用方法:
- MeasureSpec.getMod(int );获取到尺寸的模式。根据所获取到的模式信息来得到父布局的意图。
- MeasureSpec.getSize(int); 获取到父意图中给定的测量大小。
2,ViewGroup中与测量相关的另外几个方法
ViewGroup对子元素测量师从measureChildren()方法开始的。
- measureChildren 对所有子元素进行测量。
measureChildren的逻辑很简单,通过父容器传入的父容器的widthMeasureSpec和heightMeasureSpec遍历子元素并调用measureChild方法去测量每一个子元素的宽高
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
- measureChildWithMargins
- measureChild 测量单个孩子的大小。此方法会调用getChildMeasureSpec来进行测量孩子的大小。
- getChildMeasureSpec:// 测量子元素并考虑外边距
一个View的大小由其父容器的测量规格MeasureSpec和View本身的布局参数LayoutParams共同决定 - ViewGroup.onLayout()方法ViewGroup中的onLayout()方法是一个抽象方法,这意味着你在继承时必须实现,onLayout的目的是为了确定子元素在父容器中的位置,那么这个步骤理应该由父容器来决定而不是子元素
-View.layout(l,t,r,b); 给字View分配位置。
-View.getPaddingXXX();获取到View的内边距。
- View.requestLayout方法的意义在于如果你的操作有可能会让控件的尺寸或位置发生改变那么就可以调用该方法请求布局,调用该方法后framework会尝试调用measure对控件重新测量:
public void setBitmap(Bitmap bitmap) {
this.mBitmap = bitmap;
requestLayout();
invalidate();
} ```
`requestLayout方法和invalidate方法并非都必需调用的,比如我们有一个更改字体颜色的方法,但是,如上我们所说,如果一旦尺寸大小或位置发生了变化,那么我们最好重新布局并迫使视图重绘,比如我们有个改变字体大小的方法`
public void setTextColor(int color) {
mPaint.setColor(color);
invalidate();
}
public void setTextSize(int size) {
mPaint.setTextSize(size);
requestLayout(); //需要调用重新测量
invalidate();
}
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/294186-0227ea1fce30c7b5.png)
-View.invalidate() 再次调用onDraw()进行渲染绘制。
补充:一个界面窗口的元素构成:
![一个界面窗口的元素构成.png](http://upload-images.jianshu.io/upload_images/294186-cf2aa15218fbfe27.png)