Android 绘制流程源码分析

文章目录

Android 绘制流程源码分析

概述

View 的绘制流程是 UI 渲染的核心机制,主要包括三个阶段:

  1. measure 测量:确定 View 以及子 View 的尺寸。
  2. layout 布局:确定 View 以及子 View 在父容器中的坐标。
  3. draw 绘制:将 View 绘制到屏幕上。

由 ViewRootImpl 驱动整个流程。

requestLayout() 和 invalidate() 最终也会通过 ViewRootImpl 触发完整或部分流程。

流程

复制代码
ViewRootImpl.performTraversals()
			↓
			performMeasure() → View.measure() → onMeasure()
			↓
			performLayout() → View.layout() → onLayout()
			↓
			performDraw() → View.draw() → onDraw()

源码分析

ViewRootImpl.performTraversals()

java 复制代码
private void performTraversals() {    
    // ViewRootImpl的测量规格
    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height, lp.privateFlags);
    
    // 测量阶段
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    if (didLayout) {
        // 布局阶段
        performLayout(lp, mWidth, mHeight);
    }

    // 绘制阶段
    performDraw(mActiveSurfaceSyncGroup)
}

ViewRootImpl.performMeasure()

java 复制代码
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mMeasuredWidth = mView.getMeasuredWidth();
    mMeasuredHeight = mView.getMeasuredHeight();
    mViewMeasureDeferred = false;
}

View.measure()

java 复制代码
// final方法,不能被重写
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
	// 实际测量逻辑
    onMeasure(widthMeasureSpec, heightMeasureSpec);
}

FrameLayout.onMeasure()

java 复制代码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 获取子View数量
    int count = getChildCount();

    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;

    // 遍历子View
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            // 测量子View
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            // ...
        }
    }

    // 根据子View尺寸和自身规则,计算资金的尺寸
    setMeasuredDimension(
        resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
        resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)
    );     
}

ViewRootImpl.performLayout()

java 复制代码
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                           int desiredWindowHeight) {
	// host既DecorView
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

View.layout()

java 复制代码
public void layout(int l, int t, int r, int b) {
    // 设置自身位置
    boolean changed = setFrame(l, t, r, b);
	// 位置/尺寸发生变化,重写调用onLayout
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
     	// 由ViewGroup处理,实际布局逻辑
        onLayout(changed, l, t, r, b);         
    }
}

LinearLayout.onLayout()

java 复制代码
@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);
    }
}
java 复制代码
void layoutVertical(int left, int top, int right, int bottom) {
    // 遍历子View
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            // 测量子View宽高
            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;
            // 计算子View坐标,调用layout()
            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);
}

ViewRootImpl.performDraw()

java 复制代码
private boolean performDraw(@Nullable SurfaceSyncGroup surfaceSyncGroup) {
    
    // 最终调用DecorView.draw()
    mView.draw(canvas)
}

View.draw()

java 复制代码
public void draw(@NonNull Canvas canvas) {    
    // 绘制背景
    drawBackground(canvas);
 	// 实际绘制逻辑
    onDraw(canvas);
    // 绘制子View
    dispatchDraw(canvas);
	
    drawAutofilledHighlight(canvas);
	// 绘制前景
    onDrawForeground(canvas);
    // 默认焦点高亮
    drawDefaultFocusHighlight(canvas);
}
相关推荐
进击的cc2 小时前
Android Kotlin:高阶函数与Lambda简化回调地狱
android·kotlin
1172 小时前
Android资源类型与常用的四种布局资源
android
常利兵2 小时前
Android 集合探秘:ArrayMap 与 SparseArray 的奇妙之旅
android·算法·哈希算法
氦客2 小时前
Android Compose 屏幕适配实战:区分手机 / 平板
android·智能手机·电脑
安卓机器2 小时前
安卓玩机工具推荐------电脑端 开源的安卓设备联机玩机辅助工具 强烈推荐
android·电脑
always_TT2 小时前
整数溢出与未定义行为
android
Digitally3 小时前
6 种实用方法:无需 USB 线将电脑文件传输至安卓手机
android·智能手机·电脑
秋93 小时前
Pentaho Kettle 9.4 实战:SQL Server 数据同步到 MySQL详细手册,附详细手册
android·adb·数据库同步
PHP代码3 小时前
windows mysql 双板本 兼容性
android·adb