上一篇文章立下的flag,要读WheelView的源码,出一篇源码分析的文章,这就来了。
不过,在分析WheelView源码之前,先来重温下“View的工作流程”和“自定义View”的知识吧!
View的工作流程
View的工作流程主要就是指Measure(测量)、Layout(布局)、Draw(绘制)三个流程。
1、Measure——测量过程
(1)View的Measure
View的Measure最终是通过onMeasure方法实现的。
// 如果需要自定义测量,子类需重写这个方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
……
}
//如果View没有重写onMeasure方法,默认会直接调用getDefaultSize方法
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
可以看到,onMeasure方法实际上就是通过setMeasuredDimension方法实现的,而setMeasuredDimension方法的作用是通过传入的参数设置View的宽高。
我们来看下getDefaultSize方法中的三种specMode:
首先,UNSPECIFIED,一般用于系统内部的测量过程,应用开发中很少用到,我们暂不关心。
然后,AT_MOST 和 EXACTLY,这两种模式下,getDefaultSize方法返回的大小实际上就是measureSpec的specSize,specSize是View的测量后大小。
测量后,意味着是在layout过程后确定的大小。
先简单说明一下:
View的MeasureSpec,是由其父容器的MeasureSpec和其自身的LayoutParams共同确定的;
而其父容器的MeasureSpec又是由再上一级容器的MeasureSpec和其自身的LayoutParams共同确定的;
……;
一直到顶级DecorView,其MeasureSpec是由窗口的尺寸和其自身的LayoutParams共同确定的。
(2)ViewGroup的Measure
ViewGroup的Measure过程和View不同,它除了要完成自己的Measure过程以外,还会遍历去调用所有子元素的measure方法。
——先看一下ViewGroup自己的Measure过程:
ViewGroup是一个抽象类,没有实现View的onMeasure方法,都交给它的子类去具体实现了。原因在于,ViewGroup不同的子类(如LinearLayout、RelativeLayout等)的布局特性不同,自然有着不同的测量细节,ViewGroup是没有办法进行统一定义的。
不同子类是怎样实现onMeasure的,这里就不再赘述了,感兴趣的小伙伴可以去查看具体子类的源码。
——关于ViewGroup遍历调用所有子元素的measure方法,是在measureChildren方法中进行的:
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);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 调用每一个子元素的measure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,其中的getChildMeasureSpec方法可用来获取子元素的MeasureSpec,从其参数的构成不难理解,子元素MeasureSpec的创建于父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还与另一参数padding有关,这个padding是父容器的已占用空间大小。
通过这几个参数,最终实现了子元素的计算需求。
2、Layout——布局过程
Layout过程用来确定View在父容器中的布局位置,它是从ViewRootImpl 的 performLayout 方法开始的:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
上述方法中调用了View的layout方法确定View的位置。当View的位置确定后,会通过onLayout方法确定其所有子元素的位置,从来完成整个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);
……
onLayout(changed, l, t, r, b);
……
}
上述代码中的setFrame方法,是用来确定View四个顶点的位置的,一旦View的四个顶点位置确定,那么View在父容器中的位置也就确定了。
需要注意的是,onLayout与onMeasure类似,View和ViewGroup都没有具体的实现onLayout方法,而是由其不同的子类容器进行具体实现的。
原因也是类似的:不同的子类容器(如LinearLayout、RelativeLayout等)的布局特性不同,怎样确定其子元素的位置的细节自然不同,View或ViewGroup是没有办法进行统一定义的。
3、Draw——绘制过程
Draw过程用来将控件绘制出来,它是从 ViewRootImpl 的 performDraw方法开始的:
private void performDraw() {
boolean canUseAsync = draw(fullRedrawNeeded);
}
private boolean draw(boolean fullRedrawNeeded) {
……
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
...
mView.draw(canvas);
...
}
最终调用View的draw方法进行绘制每一个View,具体可分为 6 步:
public void draw(Canvas canvas) {
...
// 1. Draw the background
if (!dirtyOpaque) {
drawBackground(canvas);
}
...
// 2. If necessary, save the canvas' layers to prepare for fading
saveCount = canvas.getSaveCount();
...
// 3. Draw view's content
if (!dirtyOpaque) onDraw(canvas);
// 4. Draw children
dispatchDraw(canvas);
// 5. If necessary, draw the fading edges and restore layers
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// 6. Draw decorations (scrollbars for instance)
onDrawScrollbars(canvas); // onDrawForeground(canvas);
}
View 绘制过程的传递,是通过 dispatchDraw 方法实现的,dispatchDraw 会遍历调用所有子元素的 draw 方法。
dispatchDraw 方法是在ViewGroup中实现的:
protected void dispatchDraw(Canvas canvas) {
……
drawChild(canvas, transientChild, drawingTime);
……
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
至此,View 的工作流程,算是简单的分析完了”。
感谢:
任玉刚 · 《Android开发艺术探索》
https://www.jianshu.com/p/c151efe22d0d