文章目录
- 入口
- DecorView如何加载到Window中
- MeasureSpec
- Measure
- View的测量
- ViewGroup的测量
- Layout
- View的`layout()`
- Draw
- 1、绘制背景
- 3、绘制View内容
- 4、绘制子View
- 6、绘制装饰
入口
DecorView如何加载到Window中
MeasureSpec
该类是View的内部类,封装View的规格尺寸。
他就是一个32位的int值,高2为代表 specMode(测量模式),低30位代表specSize(测量大小)
specMode:UNSPECIFIED
AT_MOST
EXACTLY
对于每个View都有对应的
MeasureSpec
,在测量流程中,通过makeMeasureSpec()
来保存宽和高,通过
getMode()
和getSize()
得到模式和宽高
MeasureSpec
受自身的布局参数和父容器的测量规格共同影响
那么顶层View的DecorView没有父容器,怎么得到测量规格呢?
通过getRootMeasureSpec()
/**
* 根据窗口大小和根视图尺寸,获取根视图的MeasureSpec
*
* @param windowSize 窗口大小
* @param rootDimension 根视图尺寸
* @return 根视图的MeasureSpec
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// 如果根视图尺寸为MATCH_PARENT(即填充父窗口),窗口无法调整大小。
// 强制根视图尺寸为窗口大小,使用MeasureSpec.EXACTLY模式。
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 如果根视图尺寸为WRAP_CONTENT(即自适应内容),窗口可以调整大小。
// 设置根视图最大尺寸为窗口大小,使用MeasureSpec.AT_MOST模式。
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 如果根视图尺寸为具体的数值,窗口希望有确定的大小。
// 强制根视图尺寸为指定的大小,使用MeasureSpec.EXACTLY模式。
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
Measure
在某些极端情况下,系统可能需要多次measure才能确定最终的测量宽/高
View的测量
ViewGroup的测量
ViewGroup没有onMeasure()
,用measureChildren()
去递归调用子元素的测量方法measureChild()
Layout
View的layout()
Draw
官方文档阐述为:
- 如果需要,则绘制背景
- 保存当前canvas层(可以不执行)
- 绘制View的内容
- 绘制子View
- 如果需要,则绘制View的褪色边缘,类似于阴影效果(可以不执行)
- 绘制装饰,例如滚动条
- 如果有必要,绘制默认的焦点高亮显示(可以不执行)
1、绘制背景
调用View的drawBackground()
来执行
/**
* Draws the background onto the specified canvas.
*
* @param canvas Canvas on which to draw the background
*/
@UnsupportedAppUsage
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground; // 获取背景Drawable对象
if (background == null) { // 如果背景Drawable为null
return; // 直接返回,不进行绘制
}
setBackgroundBounds(); // 设置背景Drawable的边界矩形
...
final int scrollX = mScrollX; // 获取View的当前水平滚动偏移量
final int scrollY = mScrollY; // 获取View的当前垂直滚动偏移量
if ((scrollX | scrollY) == 0) { // 如果水平和垂直滚动偏移量都为0
background.draw(canvas); // 直接绘制背景Drawable在画布上
} else { // 如果有滚动偏移量
canvas.translate(scrollX, scrollY); // 将画布平移至滚动偏移量的位置
background.draw(canvas); // 绘制背景Drawable在平移后的画布上
canvas.translate(-scrollX, -scrollY); // 恢复画布的原始位置
}
}
3、绘制View内容
onDraw()
需要去自己进行重写实现
4、绘制子View
dispathchDraw()
需要去自己进行重写实现
ViewGroup进行了重写:
@Override
protected void dispatchDraw(Canvas canvas) {
...
for (int i = 0; i < childrenCount; i++) { // 遍历子View
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { // 如果当前索引为临时索引
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) { // 如果临时子View可见或者临时子View有动画
more |= drawChild(canvas, transientChild, drawingTime); // 在画布上绘制临时子View,并返回是否还有更多绘制
}
transientIndex++; // 增加临时索引
if (transientIndex >= transientCount) { // 如果临时索引超过了临时子View的数量
transientIndex = -1; // 重置临时索引
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); // 获取并验证预排序的子View索引
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); // 根据索引找到对应View
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { // 如果子View可见或者有动画
more |= drawChild(canvas, child, drawingTime); // 在画布上绘制子View,并返回是否还有更多绘制
}
}
...
}
最后调用了drawChild()
方法,而该方法其实返回的是child的draw()
方法,即View的draw()
:
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
if (!drawingWithDrawingCache) { // 1. 没有使用绘制缓存
if (drawingWithRenderNode) { // 使用RenderNode进行绘制
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((RecordingCanvas) canvas).drawRenderNode(renderNode);
} else {
// 对于没有背景的布局,快速路径
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { // 子View标记为不需要被绘制
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas); // 调用dispatchDraw()方法进行绘制
} else {
draw(canvas); // 调用draw()方法进行绘制
}
}
} else if (cache != null) { // 2. 存在绘制缓存
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
// 没有图层画笔,使用临时画笔绘制位图
...
} else {
// 使用图层画笔绘制位图,合并两个透明度,并恢复
...
}
}
...
return more; // 返回是否还有更多需要绘制的内容
}
6、绘制装饰
View的DrawForeground()
/**
* 绘制视图的前景内容。
*
* <p>前景内容可以包括滚动条、前景绘制或其他视图特定的装饰。前景绘制在主视图内容之上。</p>
*
* @param canvas 用于绘制的画布
*/
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas); // 调用绘制滚动指示器的方法
onDrawScrollBars(canvas); // 调用绘制滚动条的方法
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
// 如果存在前景就绘制
...
foreground.draw(canvas);
}
}