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);
}
相关推荐
传奇开心果编程1 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程2 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓3 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1233 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)4 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
五彩小白20 小时前
自回归和上下文
android
事圆则缓1 天前
Android 多渠道打包实战:Product Flavors、签名、AAB 与发包校验
android