我们继承重写ViewGroup的目的是要做自定义控件,所以我们有必要先看一下安卓View的绘制过程:
首先当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点。
绘制过程从布局的根节点开始,从根节点开始测量和绘制整个layout tree,绘画通过遍历整个树来完成,不可见的区域的View被放弃。
每一个ViewGroup 负责要求它的每一个孩子被绘制,每一个View负责绘制自己。
因为整个树是按顺序遍历的,所以父节点会先被绘制,而兄弟节点会按照它们在树中出现的顺序被绘制。
绘制是两个过程:一个measure过程和一个layout过程。
1.测量过程
是在measure(int, int)中实现的,是从树的顶端由上到下进行的。
在这个递归过程中,每一个View会把自己的dimension specifications传递下去。
在measure过程的最后,每一个View都存储好了自己的measurements,即测量结果。
2.布局过程
发生在 layout(int, int, int, int)中,仍然是从上到下进行。
在这一遍中,每一个parent都会负责用测量过程中得到的尺寸,把自己的所有孩子放在正确的地方。
所以在继承ViewGroup类时,需要重写两个方法,分别是onMeasure和onLayout。重写ViewGroup的过程大致是两个:
1)测量过程>>>onMeasure(int widthMeasureSpec, int heightMeasureSpec)
传入的参数是本View的可见长和宽,通过这个方法循环测量所有View的尺寸并且存储在View里面;
2)布局过程>>>onLayout(boolean changed, int l, int t, int r, int b)
传入的参数是View可见区域的上下左右四边的位置,在这个方法里面可以通过layout来放置子View;
我们先来看一下测量的过程,也就是该如何重写onMeasure方法,重写之前我们先要了解这个方法:
onMeasure方法
onMeasure方法是测量view和它的内容,决定measured width和measured height的,子类可以覆写onMeasure来提供更加准确和有效的测量。
注意:在覆写onMeasure方法的时候,必须调用 setMeasuredDimension(int,int)
来存储这个View经过测量得到的measured width and height。
如果没有这么做,将会由measure(int, int)方法抛出一个IllegalStateException。
并且覆写onMeasure方法的时候,子类有责任确保measured height and width至少为这个View的最小height和width。getSuggestedMinimumHeight()
and getSuggestedMinimumWidth()
onMeasure方法如下:
- protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
其中两个参数如下:
widthMeasureSpec
heightMeasureSpec
传入的参数是两个int分别是parent提出的水平和垂直的空间要求。
这两个要求是按照View.MeasureSpec类来进行编码的。参见View.MeasureSpec这个类的说明:
两个参数分别代表宽度和高度的MeasureSpec,android2.2文档中对于MeasureSpec中的说明是:
一个MeasureSpec封装了从父容器传递给子容器的布局需求.每一个MeasureSpec代表了一个宽度,或者高度的说明.一个MeasureSpec是一个大小跟模式的组合值.
这个类包装了从parent传递下来的布局要求,传递给这个child。
简单地说就是每一个MeasureSpec代表了对宽度或者高度的一个要求。
每一个MeasureSpec有一个尺寸(size)和一个模式(mode)构成。
MeasureSpecs这个类提供了把一个<size, mode>的元组包装进一个int型的方法,从而减少对象分配。当然也提供了逆向的解析方法,从int值中解出size和mode。我们先看三种模式:
有三种模式:
UNSPECIFIED
这说明parent没有对child强加任何限制,child可以是它想要的任何尺寸,子容器想要多大就多大。
EXACTLY
Parent为child决定了一个绝对尺寸,child将会被赋予这些边界限制,不管child自己想要多大,子容器应当服从这些边界。
AT_MOST
Child可以是自己任意的大小,但是有个绝对尺寸的上限,即子容器可以是声明大小内的任意大小。
当我们设置width或height为fill_parent时,容器在布局时调用子view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。而当设置为wrap_content时,容器传进去的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY, 而MeasureSpec的UNSPECIFIED模式目前还没有发现在什么情况下使用。
View的onMeasure方法默认行为是当模式为UNSPECIFIED时,设置尺寸为mMinWidth(通常为0)或者背景drawable的最小尺寸,当模式为EXACTLY或者AT_MOST时,尺寸设置为传入的MeasureSpec的大小。
具体取出模式或者值的方法:
根据提供的测量值(格式)提取模式(上述三个模式之一)
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
而合成则可以使用下面的方法:
根据提供的大小值和模式创建一个测量值(格式)
MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
我们回忆一下之前一开始讲过的View绘制过程
当一个View的measure()方法返回的时候,它的getMeasuredWidth和getMeasuredHeight方法的值一定是被设置好的。它所有的子节点同样被设置好。一个View的测量宽和测量高一定要遵循父View的约束,这保证了在测量过程结束的时候,所有的父View可以接受子View的测量值。一个父View或许会多次调用子View的measure()方法。举个例子,父View会使用不明确的尺寸去丈量看看子View到底需要多大,当子View总的尺寸太大或者太小的时候会再次使用实际的尺寸去调用onMeasure().
下面我们来看看具体代码:
我们来看View类中measure和onMeasure函数的源码:
- public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
- if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
- widthMeasureSpec != mOldWidthMeasureSpec ||
- heightMeasureSpec != mOldHeightMeasureSpec) {
-
-
- mPrivateFlags &= ~MEASURED_DIMENSION_SET;
-
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
- }
-
-
- onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-
-
- if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
- throw new IllegalStateException("onMeasure() did not set the"
- + " measured dimension by calling"
- + " setMeasuredDimension()");
- }
-
- mPrivateFlags |= LAYOUT_REQUIRED;
- }
-
- mOldWidthMeasureSpec = widthMeasureSpec;
- mOldHeightMeasureSpec = heightMeasureSpec;
- }
measure的过程是固定的,而measure中调用了onMeasure函数,因此真正有变数的是onMeasure函数,onMeasure的默认实现很简单,源码如下:
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
onMeasure默认的实现仅仅调用了setMeasuredDimension,setMeasuredDimension函数是一个很关键的函数,它对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值,而measure的主要目的就是对View树中的每个View的mMeasuredWidth和mMeasuredHeight进行赋值。一旦这两个变量被赋值,则意味着该View的测量工作结束。
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
-
- mPrivateFlags |= MEASURED_DIMENSION_SET;
- }
对于非ViewGroup的View而言,通过调用上面默认的measure——>onMeasure,即可完成View的测量,当然你也可以重载onMeasure,并调用setMeasuredDimension来设置任意大小的布局,但一般不这么做。
对于ViewGroup的子类而言,往往会重载onMeasure函数负责其children的measure工作,重载时不要忘记调用setMeasuredDimension来设置自身的mMeasuredWidth和mMeasuredHeight。如果我们在layout的时候不需要依赖子视图的大小,那么不重载onMeasure也可以,但是必须重载onLayout来安排子视图的位置。
ViewGroup中定义了measureChildren, measureChild, measureChildWithMargins来对子视图进行测量,measureChildren内部只是循环调用measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也作为子视图的大小。
getChildMeasureSpec的总体思路就是通过其父视图提供的MeasureSpec参数得到specMode和specSize,并根据计算出来的specMode以及子视图的childDimension(layout_width和layout_height中定义的)来计算自身的measureSpec,如果其本身包含子视图,则计算出来的measureSpec将作为调用其子视图measure函数的参数,同时也作为自身调用setMeasuredDimension的参数,如果其不包含子视图则默认情况下最终会调用onMeasure的默认实现,并最终调用到setMeasuredDimension,而该函数的参数正是这里计算出来的。
总结:从上面的描述看出,决定权最大的就是View的设计者,因为设计者可以通过调用setMeasuredDimension决定视图的最终大小,例如调用setMeasuredDimension(100, 100)将视图的mMeasuredWidth和mMeasuredHeight设置为100,100,那么父视图提供的大小以及程序员在xml中设置的layout_width和layout_height将完全不起作用,当然良好的设计一般会根据子视图的measureSpec来设置mMeasuredWidth和mMeasuredHeight的大小,以尊重程序员的意图。
下面我们看一下具体的重写代码:
-
-
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int measureWidth = measureWidth(0, widthMeasureSpec);
- int measureHeight = measureHeight(0, heightMeasureSpec);
-
-
-
-
-
-
-
-
-
- for (int i = 0; i < getChildCount(); i++) {
- View v = getChildAt(i);
-
- int widthSpec = 0;
- int heightSpec = 0;
- LayoutParams params = v.getLayoutParams();
- if (params.width > 0) {
- widthSpec = MeasureSpec.makeMeasureSpec(params.width,
- MeasureSpec.EXACTLY);
- } else if (params.width == -1) {
- widthSpec = MeasureSpec.makeMeasureSpec(measureWidth,
- MeasureSpec.EXACTLY);
- } else if (params.width == -2) {
- widthSpec = MeasureSpec.makeMeasureSpec(measureWidth,
- MeasureSpec.AT_MOST);
- }
-
- if (params.height > 0) {
- heightSpec = MeasureSpec.makeMeasureSpec(params.height,
- MeasureSpec.EXACTLY);
- } else if (params.height == -1) {
- heightSpec = MeasureSpec.makeMeasureSpec(measureHeight,
- MeasureSpec.EXACTLY);
- } else if (params.height == -2) {
- heightSpec = MeasureSpec.makeMeasureSpec(measureWidth,
- MeasureSpec.AT_MOST);
- }
- v.measure(widthSpec, heightSpec);
-
- }
-
- setMeasuredDimension(measureWidth, measureHeight);
- }
-
- private int measureWidth(int size, int pWidthMeasureSpec) {
- int result = size;
- int widthMode = MeasureSpec.getMode(pWidthMeasureSpec);
- int widthSize = MeasureSpec.getSize(pWidthMeasureSpec);
-
- switch (widthMode) {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = widthSize;
- break;
- }
- return result;
- }
-
- private int measureHeight(int size, int pHeightMeasureSpec) {
- int result = size;
-
- int heightMode = MeasureSpec.getMode(pHeightMeasureSpec);
- int heightSize = MeasureSpec.getSize(pHeightMeasureSpec);
-
- switch (heightMode) {
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = heightSize;
- break;
- }
- return result;
- }
这是一个重写的简单例子,已经经过测试了。我再贴一下这个类的代码吧:
- package com.example.component;
-
- import android.content.Context;
- import android.util.AttributeSet;
- import android.view.View;
- import android.view.ViewGroup;
-
- public class MyLayout extends ViewGroup {
-
-
- <span style="white-space:pre"> </span>public MyLayout(Context context) {
- <span style="white-space:pre"> </span>super(context);
- <span style="white-space:pre"> </span>}
-
- public MyLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public MyLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
-
-
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int measureWidth = measureWidth(0, widthMeasureSpec);
- int measureHeight = measureHeight(0, heightMeasureSpec);
-
-
-
-
-
-
-
-
-
- for (int i = 0; i < getChildCount(); i++) {
- View v = getChildAt(i);
-
- int widthSpec = 0;
- int heightSpec = 0;
- LayoutParams params = v.getLayoutParams();
- if (params.width > 0) {
- widthSpec = MeasureSpec.makeMeasureSpec(params.width,
- MeasureSpec.EXACTLY);
- } else if (params.width == -1) {
- widthSpec = MeasureSpec.makeMeasureSpec(measureWidth,
- MeasureSpec.EXACTLY);
- } else if (params.width == -2) {
- widthSpec = MeasureSpec.makeMeasureSpec(measureWidth,
- MeasureSpec.AT_MOST);
- }
-
- if (params.height > 0) {
- heightSpec = MeasureSpec.makeMeasureSpec(params.height,
- MeasureSpec.EXACTLY);
- } else if (params.height == -1) {
- heightSpec = MeasureSpec.makeMeasureSpec(measureHeight,
- MeasureSpec.EXACTLY);
- } else if (params.height == -2) {
- heightSpec = MeasureSpec.makeMeasureSpec(measureWidth,
- MeasureSpec.AT_MOST);
- }
- v.measure(widthSpec, heightSpec);
-
- }
-
- setMeasuredDimension(measureWidth, measureHeight);
- }
-
- private int measureWidth(int size, int pWidthMeasureSpec) {
- int result = size;
- int widthMode = MeasureSpec.getMode(pWidthMeasureSpec);
- int widthSize = MeasureSpec.getSize(pWidthMeasureSpec);
-
- switch (widthMode) {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = widthSize;
- break;
- }
- return result;
- }
-
- private int measureHeight(int size, int pHeightMeasureSpec) {
- int result = size;
-
- int heightMode = MeasureSpec.getMode(pHeightMeasureSpec);
- int heightSize = MeasureSpec.getSize(pHeightMeasureSpec);
-
- switch (heightMode) {
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = heightSize;
- break;
- }
- return result;
- }
-
-
-
-
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
-
- int mTotalHeight = 0;
-
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View childView = getChildAt(i);
-
-
- int measureHeight = childView.getMeasuredHeight();
- int measuredWidth = childView.getMeasuredWidth();
-
- childView.layout(l, mTotalHeight, measuredWidth, mTotalHeight
- + measureHeight);
-
- mTotalHeight += measureHeight;
-
- }
- }
- }
希望大家能有所收获。所用到的知识上面已经讲过了,我对这部分知识目前理解的也还是不透彻,最近需要用到,从网上看了很多大神的文章边学边写的,等到我继续深入之后还会再给大家补充。
本文转载自:http://blog.youkuaiyun.com/sunmc1204953974/article/details/38454267