2019年百度Android面试题-公共技术点之-View-绘制流程,涨薪7K

/**

  • 该方法是 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()进行复写。

绘制流程图
2019年百度Android面试题-公共技术点之-View-绘制流程,涨薪7K

- 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++) {
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISI

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

BLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}

SIBILITY_MASK) == VISI

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

BLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}

上一篇:使用C#快速生成二维码 | 真正跨平台方案


下一篇:canvas_11 requestAnimationFrame 与 小球