Android View 的绘制 的基本过程是:
1. Measure 测量, 即确定View 的大小
2. Layout 布局,即确定View 的摆放位置
3. Draw, 画View
首先看Measure
关于Measure 放方法有:
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
final方法 , 不能重写
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
可以重写, 若想影响测量 课在次方法中做处理
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
最终决定 View 大小的方法, 该不放不能重写, 且必须在 onMeasure 调用
在ViewGroup 中还有一些 关于Measure的方法:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)
循环遍历 调用 measureChild 方法
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
在这个方法中 会调用子View 的 measure 方法去测量子类
ok 上面说了一些 Measure的相关方法, 用到最多的一班还只是 onMeasure 方法,自己改变View 的测量的大小
需要有一点需要注意的是 在onMeasure 中必须要调用 setMeasuredDimension 方法不然会报错
下面来看看 View 默认的onMeasure 方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
只是的简单的调用了 setMeasuredDimension 方法, 我在onMeasure 方法中需要计算好大小, 再调用 setMeasuredDimension 方法
我们看到上面 哪些关于 Measure方法参数 都是int 行, 但是他们 不是一个 简单的 宽度高低, 还有有些其他的东西再里面:
例如:
widthMeasureSpec 这里有两个数据, 前3位代表, 测量模式, 后面数据代表宽度:
测量模式有:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;// 移动的位数
private static final int MODE_MASK = 0x3 << MODE_SHIFT;// mask 0011 向右移动30位
public static final int UNSPECIFIED = 0 << MODE_SHIFT;// 0000 向右移动30位
public static final int EXACTLY = 1 << MODE_SHIFT;// 0001向右移动30位
public static final int AT_MOST = 2 << MODE_SHIFT;// 0010向右移动30位
// 向要知道 是什么模式可以调用:
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
// 获取具体尺寸
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
// 自己 做一个这个int 数据, 传入 尺寸 和 模式就好了
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
}
具体的绘制过程只能很笼统的说一下:
在RootView 中, 会去调用 ViewGroup的measure方法:
然后在View Group 在onMeasure 中 又会依次调用子View 的measure 方法
ViewGroup遍历完后, 也会计算处自己的大小最后调用 setMeasuredDimension 方法
最后子View 调用 onMeasure setMeasuredDimension 方法
View 的大小 是子View 和父View共同决定, 我们在xml 中给出的width height 知识期望值, 在别的博客上看到一张表表示的还不错:
:
父视图能力尺寸 | 子视图期望尺寸 | 子视图最终允许尺寸 |
EXACTLY + Size1
|
EXACTLY + Size2
|
EXACTLY + Size2
|
EXACTLY + Size1
|
fill_parent/match_parent
|
EXACTLY
+Size1
|
EXACTLY + Size1
|
wrap_content
|
AT_MOST
+Size1
|
AT_MOST
+Size1
|
EXACTLY + Size2
|
EXACTLY
+Size2
|
AT_MOST
+Size1
|
fill_parent/match_parent
|
AT_MOST
+Size1
|
AT_MOST
+Size1
|
wrap_content
|
AT_MOST
+Size1
|
UNSPECIFIED+Size1
|
EXACTLY + Size2
|
EXACTLY + Size2
|
UNSPECIFIED+Size1
|
fill_parent/match_parent
|
UNSPECIFIED+0
|
UNSPECIFIED+Size1
|
wrap_content
|
UNSPECIFIED+0
|
ok 下面说说layout 方法:
关于Layout的方法有:
public void layout(int l, int t, int r, int b)
这个方法是View 的方法
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
ViewGroup的方法且 ViewGroup 必须实现该方法
public void layout(int l, int t, int r, int b)中得
l、t、r、b是指view的左left、上top、右right、底bottom的位置
View 的具体位置是有 ViewGroup中得 onLayout 方法决定
在ViewGroup中的onLayout方法需要依次的遍历子View 调用他们的Layout方法
下面可以看看 LinearLayout 的onLayout方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical();
} else {
layoutHorizontal();
}
}
void layoutVertical() {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = mRight - mLeft;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + mBottom - mTop - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
在Layout中做的就是 计算好位置, 然后调用子ViewLayout方法
下面在来看看Draw 的过程,
当测量完了, 宽高弄好了, 位置也确定了, 最后就是 画View 了
draw 相关的方法有;
public void draw(Canvas canvas)
protected void onDraw(Canvas canvas)
View 的Draw 方法中有6个步骤
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
在第三步 Draw view's content 调用onDraw方法,子类中实现onDraw方法。
在第四步,Draw children步骤使用的dispatchDraw方法,这个方法在ViewGroup中有实现。
View或ViewGroup的子类不用再重载ViewGroup中该方法,因为它已经有了默认而且标准的view系统流程。dispatchDraw()内部for循环调用drawChild()分别绘制每一个子视图,而drawChild()内部又会调用draw()函数完成子视图的内部绘制工作
ok 上面基本就是 绘制View 的一个大概的粗擦的流程
写的不是很好, 但是谢了总归会加深写印象, 一个对源码有更深的探究时, 还有在重新写一遍
产考一下文章: