自定义View(二) View的绘制原理

本文深入解析了Android中View的绘制流程,包括测量、布局和绘制三个关键步骤。详细介绍了MeasureSpec的作用,View如何根据MeasureSpec计算自己的尺寸,以及onMeasure、onLayout和onDraw方法的实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.View绘制流程

DecorView是View的顶级View,它需要通过ViewRootImpl建立DecorView和WindowManager的连接,通过ViewRootImpl的performTraversals开始向下测量,详细源码流程,请看

Android View源码解读:浅谈DecorView与ViewRootImpl

接着,底层View或者ViewGroup调用onMeasure,onLayout,onDraw去对View进行测量,布局,绘制,最终完成View的初始化,流程图如下

二.View的绘制流程

1.measure过程 

View的measure是通过父容器和其本身的LayoutParams算出的MeasureSpec,最终测量出View的宽高,了解MeasureSpec请看 MeasureSpec详解

默认View的onMeasure的源码是:

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
 public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

只要MeasureSpec的模式是AT_MOST或者EXACTLY,最终size都等于MeasureSpec.getSize(measureSpec),所以当View的layoutParams设置为Wrap_Content的时候,默认size会等于父容器的size,所以在自定义View的时候,要重写wrap_content的情况,一般格式如下:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
                measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec)
        );
    }
  private int measureWidth(int widthMeasureSpec) {
        int result = 0;
        int spaceMode = MeasureSpec.getMode(widthMeasureSpec);
        int spaceSize = MeasureSpec.getSize(widthMeasureSpec);

        if (spaceMode == MeasureSpec.EXACTLY){
            result = spaceSize;
        }else{
            result = 200;//设置宽度默认值
            if (spaceMode == MeasureSpec.AT_MOST){
                result = Math.min(result,spaceSize);
            }
        }
        return result;
    }

这样就完成了View的测量!!

2.layout布局

在View或者ViewGroup中,有

    protected void onLayout( int l, int t, int r, int b) {

方法,在其里面调用

    public void layout(int l, int t, int r, int b) 

完成View在父容器的位置布局

3.Draw绘制

在View中调用view方法开始绘制

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
}

主要步骤有:
drawBackground(canvas):绘制背景
onDraw(canvas):绘制自身
dispatchDraw(canvas):绘制子View
onDrawForeground(canvas):绘制滚动条等

View的更新主要有以下方法:
invalidate:在主线程调用
postInvalidate:是在非主线程调用
requestLayout():方法会调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值