final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 获取 ChildView 的 widthMeasureSpec
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 获取 ChildView 的 heightMeasureSpec
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
- 该方法是 measureChildren 中最繁重的部分,为每一个 ChildView 计算出自己的 MeasureSpec。
- 目标是将 ChildView 的 MeasureSpec 和 LayoutParams 结合起来去得到一个最合适的结果。
- @param spec 对该 View 的测绘要求
- @param padding 当前 View 在当前唯独上的 paddingand,也有可能含有 margins
- @param childDimension 在当前维度上(height 或 width)的具体指
- @return 子视图的 MeasureSpec
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
…
// 根据获取到的子视图的测量要求和大小创建子视图的 MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
/**
*
- 用于获取 View 最终的大小,父视图提供了宽、高的约束信息
- 一个 View 的真正的测量工作是在 onMeasure(int, int) 中,由该方法调用。
- 因此,只有 onMeasure(int, int) 可以而且必须被子类复写
- @param widthMeasureSpec 在水平方向上,父视图指定的的 Measure 要求
- @param heightMeasureSpec 在竖直方向上,控件上父视图指定的 Measure 要求
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
…
onMeasure(widthMeasureSpec, heightMeasureSpec);
…
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
4. layout 相关概念及核心方法
首先要明确的是,子视图的具体位置都是相对于父视图而言的。View 的 onLayout 方法为空实现,而 ViewGroup 的 onLayout 为 abstract 的,因此,如果自定义的 View 要继承 ViewGroup 时,必须实现 onLayout 函数。
在 layout 过程中,子视图会调用getMeasuredWidth()和getMeasuredHeight()方法获取到 measure 过程得到的 mMeasuredWidth 和 mMeasuredHeight,作为自己的 width 和 height。然后调用每一个子视图的layout(l, t, r, b)函数,来确定每个子视图在父视图中的位置。
LinearLayout 的 onLayout 源码分析
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
/**
- 遍历所有的子 View,为其设置相对父视图的坐标
*/
void layoutVertical(int left, int top, int right, int bottom) {
for
(int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {//不需要立即展示的 View 设置为 GONE 可加快绘制
final int childWidth = child.getMeasuredWidth();//measure 过程确定的 Width
final int childHeight = child.getMeasuredHeight();//measure 过程确定的 height
…确定 childLeft、childTop 的值
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
View.java
public void layout(int l, int t, int r, int b) {
…
setFrame(l, t, r, b)
}
/**
- 为该子 View 设置相对其父视图上的坐标
*/
protected boolean setFrame(int left, int top, int right, int bottom) {
…
}
5. 绘制流程相关概念及核心方法
先来看下与 draw 过程相关的函数:
View.draw(Canvas canvas): 由于 ViewGroup 并没有复写此方法,因此,所有的视图最终都是调用 View 的 draw 方法进行绘制的。在自定义的视图中,也不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制,如果自定义的视图确实要复写该方法,那么请先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制。
View.onDraw():
View 的onDraw(Canvas)默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容。
dispatchDraw() 发起对子视图的绘制。View 中默认是空实现,ViewGroup 复写了dispatchDraw()来对其子视图进行绘制。该方法我们不用去管,自定义的 ViewGroup 不应该对dispatchDraw()进行复写。
绘制流程图
- View.draw(Canvas) 源码分析
/**
- Manually render this view (and all of its children) to the given Canvas.
- The view must have already done a full layout before this function is
- called. When implementing a view, implement
- {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
- If you do need to override this method, call the superclass version.
- @param canvas The Canvas to which the View is rendered.
- 根据给定的 Canvas 自动渲染 View(包括其所有子 View)。在调用该方法之前必须要完成 layout。当你自定义 view 的时候,
- 应该去是实现 onDraw(Canvas) 方法,而不是 draw(canvas) 方法。如果你确实需要复写该方法,请记得先调用父类的方法。
*/
public void draw(Canvas canvas) {
/ * Draw traversal performs several drawing steps which must be executed
- in the appropriate order:
-
1. Draw the background if need
-
2. If necessary, save the canvas' layers to prepare for fading
-
3. Draw view's content
-
4. Draw children (dispatchDraw)
-
5. If necessary, draw the fading edges and restore layers
-
6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we’re done…
return;
}
// Step 2, save the canvas’ layers
…
// Step 3, draw the content
if (!dirtyOpaque)
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
}
由上面的处理过程,我们也可以得出一些优化的小技巧:当不需要绘制 Layer 的时候第二步和第五步会跳过。因此在绘制的时候,能省的 layer 尽可省,可以提高绘制效率
ViewGroup.dispatchDraw() 源码分析
dispatchDraw(Canvas canvas){
…
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {//处理 ChildView 的动画
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {//只绘制 Visible 状态的布局,因此可以通过延时加载来提高效率
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);// 添加布局变化的动画
bindLayoutAnimation(child);//为 Child 绑定动画
if (cache) {
child.setDrawingCacheEnabled(true);
if (buildCache) {
child.buildDrawingCache(true);
}
}
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();// 启动 View 的动画
}
// 绘制 ChildView
for (int i = 0; i < childrenCount; i++) {
r controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();// 启动 View 的动画
}
// 绘制 ChildView
for (int i = 0; i < childrenCount; i++) {