Android View的绘制流程

作者: ITRenj | 来源:发表于2021-06-15 14:45 被阅读0次

    Android View的绘制流程

    源码版本为 Android 10(Api 29),不同Android版本可能有一些差别

    View 的绘制从哪里开始

    《Activity常见问题》的 Activity 在 onResume 之后才显示的原因是什么? 部分中我们知道了View是在 onResume() 回调之后才显示出来的,显示过程主要是通过 WindowManagerImpl#addView() -> WindowManagerGlobal#addView() -> ViewRootImpl#setView() 这个过程,我们再次看一下 ViewRootImpl#setView() 的代码(核心代码):

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
         synchronized (this) {
             if (mView == null) {
                 mView = view;
                 // 调用 requestLayout() 方法,进行布局(包括measue、layout、draw)
                 requestLayout();
                 
                 mOrigWindowType = mWindowAttributes.type;
                 mAttachInfo.mRecomputeGlobalAttributes = true;
                 collectViewAttributes();
                 // 通过调用 Session 的 addToDisplay() 方法
                 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                         getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                         mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                         mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                         mTempInsets);
                 setFrame(mTmpFrame);
             }
         }
     }
    

    有这样一行 requestLayout() ,表示请求布局,我们界面的绘制也是从这一行代码开始的,接下来,我们就来看一下跟踪一下这段代码(ViewRootImpl#requestLayout()):

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    

    调用 ViewRootImpl#scheduleTraversals() 方法:

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    

    scheduleTraversals() 方法中通过 mChoreographer.postCallback() 方法发送一个要执行的实现了 RunnableTraversalRunnable 的对象 mTraversalRunnable

    1. mChoreographer.postCallback() 方法内部就是通过Handler机制

    2. TraversalRunnableViewRootImpl 的内部类

       final class TraversalRunnable implements Runnable {
           @Override
           public void run() {
               doTraversal();
           }
       }
      

    调用 ViewRootImpl#doTraversal() 方法

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
    
            performTraversals();
    
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
    

    调用 ViewRootImpl#performTraversals() 方法,该方法中关于绘制的代码

    private void performTraversals(){
        ……
        // 方法测量组件的大小
        performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
        ……
        // 方法用于子组件的定位(放在窗口的什么地方)
        performLayout(lp,desiredWindowWidth,desiredWindowHeight);
        ……
        // 绘制组件内容
        performDraw();
        ……
    }
    

    而在performMeasure()performLayout()performDraw()方法的调用过程可以用下面的图来表示:

    Android View绘制流程.png

    从图中可以看出系统的View类已经写好了measure()layout()draw()方法:

    1. 在系统View类中measure()方法用了final修饰,不能被重写(我觉得这应该是google不想让开发者更改measure()方法里面的逻辑而设计的,但是开发者有时又有需求需要自己测量,所以提供了onMeasure()方法可以重写);
      代码
    2. 在系统View类中的layout()方法在调用onLayout()方法前调用了setFrame()方法,这个方法作用是判断View的位置是否发生改变,如果没有发生改变,就不调用onLayout()方法,主要是为了提高性能,在View类中onLayout()方法是空实现,这是因为view没有子类,而当在自定义的控件如果是直接继承ViewGroup时就必须重写onLayout()方法;
      代码
    3. 在系统View类中的draw()方法,开发者一般不会重写,因为当我们如果重写draw()时,就需要按照系统定义好的步骤一步一步的画,否则会显示不出来,相对来说比较麻烦。而如果我们实现onDraw()方法,我们只要关注我们画的内容即可(画出来的内容就是显示到界面的内容);
      代码
    4. 当开发者在自定义控件时一般只需重写onMeasure()onLayout()onDraw()方法就可以了。

    测量 -- measure

    源码追踪

    ViewRootImpl中的performMeasure()方法:

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    

    调用了mViewmeasure()方法,mView就是 DecorView,是通过 ViewRootImpl#setView() 传入进来的,也就是调用了FrameLayout#measure()方法,FrameLayout继承ViewGroupViewGroup没有重写也不能重写measure()方法,所以最终调用的是View类中的measure()方法:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
        ...
    }
    

    在View类中的measure()中系统帮我们做了很多的处理并且不想让开发者重写measure的逻辑,所以使用了final修饰符进行修饰,并且调用了onMeasure()方法,所以在Activity中View树的测量过程中,最终是从FrameLayout#onMeasure()方法开始的,FrameLayoutonMeasure()方法如下:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        ......
        // 判断孩子控件的Visibility属性,如果为gone时,就跳过,因为gone属性不占用空间
        if (count > 1) {// 判断是否有孩子控件
            for (int i = 0; i < count; i++) {
                // 通过LayoutParams参数获取孩子控件的margin、padding值
                ...
                // 调用孩子控件的measure()方法测量自身大小
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
    

    FrameLayoutonMeasure()方法中,先是获取了孩子控件的个数,然后获取每一个孩子控件并判断visibility属性;最终调用孩子View#measure()方法测量自身大小。

    当开发者有需要重新测量控件时,只需要重写onMeasure()方法即可,系统在View的measure()方法中会回调onMeasure()方法,使测量值生效。

    下面是继承自View时重写的onMeasure()方法,我就只是简单的将宽和高都设置成500:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(500,500);
    }
    

    下面是继承ViewGroup时重写的onMeasure()方法,将所有孩子控件的宽和高的和计算出来作为父控件的宽和高,最终调用setMeasuredDimension()方法设置值:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMeasure = 0,heightMeasure = 0;
        // 直接调用系统方法测量每一个孩子控件的宽和高
        measureChildren(widthMeasureSpec,heightMeasureSpec);
        /**
         * 系统在调用measureChildren(widthMeasureSpec,heightMeasureSpec)的过程中,
         * 如果孩子控件依然是ViewGroup类型的,那么又会调用measureChildren()方法,否则会调用
         * child.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法测量每一个孩子控件
         * 的宽和高,直到所有的孩子控件都测量完成。
         * 这就可以说明measure的过程可以看成是一个递归的过程。
         */
     
        // 获取孩子控件的个数
        int childCount = getChildCount();
        // 循环测量每一个孩子控件的宽和高,得到的和就作为控件的宽和高
        for (int i = 0; i < childCount; i++) {
            View view = getChildAt(i);
            // 获取每一个孩子的宽和高
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
            // 把每一个孩子控件的宽和高加上
            widthMeasure += width;
            heightMeasure += height;
        }
     
        // 调用setMeasuredDimension()方法保存宽和高,表示measure的结束
        setMeasuredDimension(widthMeasure,heightMeasure);
    }
    

    另外在measure的过程中还可能会用到MeasureSpec类(View的内部类):

    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
     
        // 父控件不没有对子施加任何约束,子可以是任意大小(也就是未指定)
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
     
        // 父控件决定子的确切大小,表示width和height属性设置成match_parent或具体值
        public static final int EXACTLY     = 1 << MODE_SHIFT;
     
        // 子最大可以达到的指定大小,当设置为wrap_content时,模式为AT_MOST
        public static final int AT_MOST     = 2 << MODE_SHIFT;
        
        /*
         * 通过模式和大小创建一个测量规范
         * @param size the size of the measure specification
         * @param mode the mode of the measure specification
         * @return the measure specification based on size and mode
         */
        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
     
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }
     
        /*
         * 获取模式
         */
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
     
        /*
         * 获取大小
         */
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
     
        ...
    }
    

    MeasureSpecs使用了二进制去减少对象的分配,用最高的两位数来表示模式(Mode),剩下的30位表示大小(size)。

    Mode有三种:UNSPECIFIED(未指定,没有约束,可以任意大小)、EXACTLY(精确,表示match_parent或者具体的大小值)、AT_MOST(最大值,表示wrap_content)

    measure总结:

    1. View的measure()方法被final修饰,子类不可以重写,但可以通过重写onMeasure()方法来测量大小,当然也可以不重写onMeasure()方法使用系统默认测量大小;

    2. 如果想要让自己设置的值生效,就必须调用setMeasuredDimension()方法设置宽和高;

    3. 如果在ActivityonCreate()方法或onResume()方法里面直接调用getWidth()/getHeight()getMeasureWidth()/getMeasureHeight()获取控件的大小得到的结果很可能是0,因为在onCreate()onResume()的时候系统还没有调用measure()方法(getMeasureWidth()getMeasureHeight()的赋值在View的setMeasuredDimension()方法中,所以在调用完View的setMeasuredDimension()方法之后getMeasuredWidth()getMeasuredHeight()就已经有值了。而getWidth()getHeight()要在onLayout()方法完成之后才会被赋值),如果一定要在onCreate()方法或onResume()方法里面获取控件的大小,可以通过以下方法得到:

       view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
           @Override
           public void onGlobalLayout() {
               int width = view.getWidth();
               int height = view.getHeight();
               view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
           }
       });
      
    4. 通过setMeasuredDimension()方法设置的值并不一定就是控件的最终大小,组件真正的大小最终是由setFrame()方法决定的,该方法一般情况下会参考measure出来的尺寸值;

    5. 子视图View的大小是由父容器View和子视图View布局共同决定的;

    6. 如果控件是ViewGroup的子类,那就必须测量每一个孩子控件的大小,可以调用系统的measureChildren()方法测量,也可以自己测量;

    7. Android系统对控件的测量过程可以看做是一个递归的过程。

    摆放 -- layout

    源码追踪

    ViewRootImpl中的performLayout()方法:

     private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                int desiredWindowHeight) {
        ...
        final View host = mView;
        ...
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ...
        for (int i = 0; i < numValidRequests; ++i) {
            final View view = validLayoutRequesters.get(i);
            Log.w("View", "requestLayout() improperly called by " + view +
                    " during layout: running second layout pass");
            view.requestLayout();
        }
    }
    

    代码中的host是View树中的根视图(DecroView),也就是最外层容器,容器的位置安排在左上角(0,0),其大小默认会填满 mContentParent容器。该方法作用是确定孩子控件的位置,所以该方法只针对ViewGroup容器类,最终调用了Viewlayout()方法确定每一个孩子控件的位置:

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
     
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
     
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
     
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
     
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
     
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }
    

    layout()中确定位置之前会判断是否需要重新测量控件的大小,如果需要,就会调用onMeasure()方法重新测量控件,接下来执行 setOpticalFrame()setFrame()方法确定自身的位置与大小,这一步并不会绘制出来,只是将控件位子和大小值保存起来;接着调用onLayout()方法,在View中onLayout()方法是空实现。onLayout()方法的作用是当当前控件是容器控件时,那就必须重写onLayout()方法确定每一个孩子控件的位置,而当孩子控件还是ViewGroup的子类时,继续调用onLayout()方法,直到所有的孩子控件都有了确定的位置和大小,这个过程和measure一样,也可以看做是一个递归的过程。下面是FrameLayout#onLayout()方法源码:

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
     
    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();
        ...// 遍历所有的孩子控件
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            ... // 判断控件的visible属性书否为gone,如果为gone就不占用位置
            ... // 计算childLeft、childTop、childRight、childBottom,确定位置
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
    

    layout总结:

    1. View的布局逻辑是由父View,也就是ViewGroup容器布局来实现的。因此,我们如果自定义View一般都无需重写onLayout()方法,但是如果自定义一个ViewGroup容器的话,就必须实现onLayout()方法,因为该方法在ViewGroup类中是抽象的,ViewGroup的所有子类必须实现onLayout()方法(如果我们定义的容器控件是继承FrameLayout或其他已经继承了ViewGroup类的容器控件时,如果没有必要可以不用实现onLayout()方法,因为FrameLayout类中已经实现了);
    2. 如果view控件使用了gone属性时,在onLayout()方法遍历中就会跳过当前的View,因为gone属性表示不占用位置;
    3. 当layout()方法执行完成之后,调用getHeight()/getWidth()方法就能够得到控件的宽和高的值了;
    4. View的layout过程和measure过程类似,都可以看做是一个递归的过程;
    5. 在Activity中,layout的过程是从DecorView控件开始的。

    绘制 -- draw

    源码追踪

    ViewRootImpl中的performDraw()方法:

    private void performDraw() {  
        ...  
        final boolean fullRedrawNeeded = mFullRedrawNeeded;  
        mFullRedrawNeeded = false;  
        mIsDrawing = true;  
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");  
        try {  
            draw(fullRedrawNeeded);  
        } finally {  
            mIsDrawing = false;  
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);  
        }  
      
        ...  
    }  
      
    private void draw(boolean fullRedrawNeeded) {  
        ...  
        if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {  
            return;  
        }  
        ...  
    }  
      
    /** 
     * @return true if drawing was succesfull, false if an error occurred 
     */  
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,  
            boolean scalingRequired, Rect dirty) {  
        Canvas canvas;  
        ...  
        try {  
            ...  
            int left = dirty.left;  
            int top = dirty.top;  
            int right = dirty.right;  
            int bottom = dirty.bottom;  
            canvas = mSurface.lockCanvas(dirty);  
            if (!canvas.isOpaque() || yoff != 0) {  
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);  
            }  
            mView.draw(canvas);  
            ...  
        } finally {  
            surface.unlockCanvasAndPost(canvas);  
        }  
        ...  
        return true;  
    }
    

    canvas对象是从surface中获取到的,surface是中提供了一套双缓存机制,这样就提高了绘图的效率.通过代码可以看到最后调用了mViewdraw()方法,mViewecorView,也就是FrameLayout,在FrameLayoutViewGroup中都是没有重写draw()方法的,所以最终调用的是View中的draw()方法:

    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background   绘制视图View的背景
         *      2. If necessary, save the canvas' layers to prepare for fading 保存画布canvas的边框参数
         *      3. Draw view's content 绘制视图View的内容(调用了onDraw()方法)
         *      4. Draw children 绘制当前视图View的子视图(调用dispatchDraw()方法)
         *      5. If necessary, draw the fading edges and restore layers 绘制边框的渐变效果并重置画布
         *      6. Draw decorations (scrollbars for instance) 绘制前景、滚动条等修饰
         */
     
        // Step 1, draw the background, if needed 绘制视图View的背景
        int saveCount;
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
        // skip step 2 & 5 if possible (common case)
        ...
     
        // Step 2, save the canvas' layers 保存画布canvas的边框参数
        saveCount = canvas.getSaveCount();
     
        // Step 3, draw the content 绘制视图View的内容(调用了onDraw()方法)
        if (!dirtyOpaque) onDraw(canvas);
     
        // Step 4, draw the children 绘制当前视图View的子视图(调用dispatchDraw()方法)
        dispatchDraw(canvas);
     
        // Step 5, draw the fade effect and restore layers 绘制边框的渐变效果并重置画布
        ...
        canvas.restoreToCount(saveCount);
     
        // Step 6, draw decorations (foreground, scrollbars) 绘制前景、滚动条等修饰
        onDrawForeground(canvas);
    }
    

    由以上代码可以看得到,系统在View类中的draw()方法已经将背景、边框、修饰等都绘制出来了,而且在第三步和第四步的时候调用了onDraw()方法和dispatchDraw()方法,这样开发者在自定义控件的时候就只需要重写onDraw()方法或者dispatchDraw()方法,也就是只需要关注最终显示的内容就可以了,而不需要去绘制其他的修饰。
    View里面的onDraw()方法和dispatchDraw()方法都是空实现,也就是留给开发者去实现里面的具体逻辑。同时,在开发者实现onDraw()方法和dispatchDraw()方法时也可以不用去绘制其他的修饰了。需要说明一点,View中的draw()方法并不是和measure()方法一样被final修饰,draw()方法没有被final修饰,所以是可以重写的,但是当我们重写draw()方法时,必须和系统中Viewdraw()方法一样,一步一步的实现,否则就不能将控件绘制出来,所以在自定义控件的时候,一般都是重写onDraw()dispatchDraw()方法。

    如果自定义控件是继承至View时,就重写onDraw()方法,在onDraw()方法中绘制的结果就是最终显示的结果。

    以下代码就是在界面上以坐标(150,150)绘制了一个半径为35的红色实心圆:

    public class CircleView extends View {
        Paint paint;
        public CircleView(Context context) {
            this(context, null);
        }
     
        public CircleView(Context context, AttributeSet attrs) {
            super(context, attrs);
            paint = new Paint(Paint.ANTI_ALIAS_FLAG);
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.FILL);
        }
     
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawCircle(150,150,35,paint);
        }
    }
    
    View 绘制流程_circle.png

    如果自定义控件是继承至ViewGroup时,就重写dispatchDraw()方法,这里直接查看系统FrameLayoutdispatchDraw()方法:

    @Override
    protected void dispatchDraw(Canvas canvas) {
        final int childrenCount = mChildrenCount;
        ...
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
            }
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
       ...
    }
    

    在代码里面调用了drawChild(canvas, child, drawingTime)方法用来绘制孩子:

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }
    

    而在drawChild()方法中就是调用了viewdraw(Canvas canvas, ViewGroup parent, long drawingTime)方法(三个参数的方法)来绘制自己,如果孩子控件还是ViewGroup的子类,又会重新调用drawChild()方法递归处理,直到所有的孩子控件绘制完成,也就表示控件绘制完成了。其实这也和measure、layout的过程一样,可以看做是一个递归的过程。

    看一下view中三个参数的draw(Canvas canvas, ViewGroup parent, long drawingTime)方法:

    /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     * 这个方法是在ViewGroup的drawChild()方法中调用,用来绘制每一个孩子控件自身。
     * This draw() method is an implementation detail and is not intended to be overridden or
     * to be called from anywhere else other than ViewGroup.drawChild().
     * 这个方法除了在ViewGroup的drawChild()方法中被调用外,不应该在其它任何地方去复写或调用该方法,它属于ViewGroup。
     * 而这个方法最终也会调用View的draw(canvas)一个参数的方法来进行绘制。
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ...
        if (!hasDisplayList) {
            // 调用computeScroll()方法,这个方法是用来与Scroller类结合实现实现动画效果的
            computeScroll();
            sx = mScrollX;
            sy = mScrollY;
        }
        ...
         if (!hasDisplayList) {
            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                // 继续调用dispatchDraw()方法递归处理
                dispatchDraw(canvas);
            } else {
                // 调用View的draw(canvas)一个参数的方法
                draw(canvas);
            }
        } else {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            ((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags);
        }
        ...
        return more;
    }
    

    了解更多关于Scroller类的相关内容,可以查看《 Android中的Scroller类》这篇博客。

    draw总结:

    1. View绘制的画布canvas是从surface对象中获得,而最终也是绘制到surface中去。surface提供了一个双缓存机制,可以提高绘制的效率;
    2. 系统在View类中的draw()方法已经将背景、边框、修饰等都绘制出来了,如果在自定义View时直接继承View时是重写draw()方法,就必须和系统中Viewdraw()方法一样,一步一步的实现,否则就不能将控件展示出来;
    3. 因为自定义控件一般重写onDraw()方法,所以每一个控件都会绘制滚动条和其他的修饰;
    4. 自定义View如果直接继承制View时,需要重写onDraw()方法,在onDraw()中绘制的内容就是最终展示到界面的内容,自定义View如果是直接继承ViewGroup,那就重写dispatchDraw()方法,绘制ViewGroup的孩子控件;
    5. Android绘制的过程和measure、layout一样,可以看做是一个递归的过程。

    总体来说,View的绘制从开始到结束要经历几个过程:

    测量大小,回调 onMeasure()方法

    组件定位,回调 onLayout()方法

    组件绘制,回调 onDraw()方法

    相关文章

      网友评论

        本文标题:Android View的绘制流程

        本文链接:https://www.haomeiwen.com/subject/mtuzeltx.html