自定义View的最基本的三个方法分别是: onMeasure()、onLayout()、onDraw(); View在Activity中显示出来,要经 历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。
测量:onMeasure()决定View的大小;
布局:onLayout()决定View在ViewGroup中的位置;
绘制:onDraw()决定绘制这个View。
自定义View: 只需重写onMeasure()和onDraw()
自定义ViewGroup:只需重写onMeasure()和onLayout()
自定义View,首先要测量宽高尺寸。在xml布局中定义了尺寸,我们的layout_width和layout_height参数可以不用写具体的尺寸,而是wrap_content或者是match_parent。
函数原型:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)参数widthMeasureSpec和HeightMeasureSpec为 MeasureSpec类型。
MeasureSpec是View中的内部类,基本都是二进制运算。由于int是32位的,用高两位表示mode,低30位表示size,MODE_SHIFT = 30的作用是移位
MODE_MASK = 0x3 << MODE_SHIFT,其中0x3是十六进制,它的二进制是11,向左移位30,结果是11000.....0000(一共30个0),1与任何数“&运算”均为任何数,0与任何数“&运算”均为0,故它的作用是获取measureSpec的高两位的mode,或者低30位的size(MODE_MASK取返,00111...111)
UNSPECIFIED = 0 << MODE_SHIFT,十进制0的二进制是00,左移30位是00000...000(00+30个0)
EXACTLY = 1 << MODE_SHIFT,十进制1的二进制是01,左移30位是00000...000(01+30个0)
AT_MOST = 2 << MODE_SHIFT,十进制2的二进制是10,左移30位是00000...000(10+30个0)
测量模式表示意思UNSPECIFIED父容器没有对当前View有任何限制,当前View可以任意取尺寸EXACTLY当前的尺寸就是当前View应该取的尺寸AT_MOST当前尺寸是当前View能取的最大尺寸 普通View的MeasureSpec创建规则标题 ViewGroup类public static int getChildMeasureSpec(int spec, int padding, int childDimension)源码 /** * 目标是将父控件的测量规格和child view的布局参数LayoutParams相结合,得到一个 * 最可能符合条件的child view的测量规格。 * * @param spec 父控件的测量规格 * @param padding 父控件里已经占用的大小 * @param childDimension child view布局LayoutParams里的尺寸 * @return child view 的测量规格 */ public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //父控件的测量模式 int specMode = MeasureSpec.getMode(spec); //父控件的测量大小 int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // 当父控件的测量模式 是 精确模式,也就是有精确的尺寸了 case MeasureSpec.EXACTLY: //如果child的布局参数有固定值,比如"layout_width" = "100dp" //那么显然child的测量规格也可以确定下来了,测量大小就是100dp,测量模式也是EXACTLY if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } //如果child的布局参数是"match_parent",也就是想要占满父控件 //而此时父控件是精确模式,也就是能确定自己的尺寸了,那child也能确定自己大小了 else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.EXACTLY; } //如果child的布局参数是"wrap_content",也就是想要根据自己的逻辑决定自己大小, //比如TextView根据设置的字符串大小来决定自己的大小 //那就自己决定呗,不过你的大小肯定不能大于父控件的大小嘛 //所以测量模式就是AT_MOST,测量大小就是父控件的size else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 当父控件的测量模式 是 最大模式,也就是说父控件自己还不知道自己的尺寸,但是大小不能超过size case MeasureSpec.AT_MOST: //同样的,既然child能确定自己大小,尽管父控件自己还不知道自己大小,也优先满足孩子的需求?? if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } //child想要和父控件一样大,但父控件自己也不确定自己大小,所以child也无法确定自己大小 //但同样的,child的尺寸上限也是父控件的尺寸上限size else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } //child想要根据自己逻辑决定大小,那就自己决定呗 else if (childDimension == LayoutParams.WRAP_CONTENT) { 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 = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }代码示例:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int lineCount = allLines.size(); int curL = getPaddingLeft(); int curT = getPaddingTop(); for (int i = 0; i < lineCount; i++) { List<View> lineViews = allLines.get(i); int lineHeight = lineHeights.get(i); for (int j = 0; j < lineViews.size(); j++) { View view = lineViews.get(j); int left = curL; int top = curT; int right = left + view.getMeasuredWidth(); int bottom = top + view.getMeasuredHeight(); view.layout(left, top, right, bottom); curL = right + mHorizontalSpacing; } curT = curT + lineHeight + mVerticalSpacing; curL = getPaddingLeft(); } }计算ViewGroup子View的 left 、top、right、bottom坐标,调用View.layout方法在ViewGroup上布局View
View.Layout()函数:public void layout(int l, int t, int r, int b)
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
