View的绘制流程

作者: 帝王鲨kingcp | 来源:发表于2017-12-20 19:24 被阅读5次

    认识一个Activity界面结构

    每个Activity都会有一个Window,Window是抽象类具体由PhoneWindow实现。PhoneWindow中有一个顶级View即DecorView,DecorView是一个FrameLayout,有唯一的子view垂直布局的LinearLayout,包含两个子元素,一个是TitleView(ActionBar的容器),另一个是ContentView(窗口内容的容器)。关于ContentView,它是一个FrameLayout(android.R.id.content),我们平常用的setContentView就是设置它的子View。

    Activity界面结构.png

    RootView

    View的测量(measure),布局(layout),绘制(draw)的三大流程都由RootView完成,RootViewImpl是RootView的具体实现类。在Activity被创建的完成后,会将DecorView添加到Window中,同时创建RootViewImpl对象,并将RootViewImpl与DecorView建立关联。View真正的绘制流程是从performTraversals开始的。

    View的绘制流程

    View的绘制流程.png

    Measure测量过程

    在measure测量阶段:起点是ViewRootImpl的measureHierarchy()方法---->在measureHierarchy中会执行到performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),传入performMeasure()方法的MeasureSpec的SpecMode为EXACTLY,SpecSize为窗口尺寸---->在performMeasure方法中调用mView.measure(childWidthMeasureSpec, childHeightMeasureSpec),mView就是DecorView---->对于decorView来说,实际执行测量工作的是FrameLayout的onMeasure()方法。在onMeasure中调用measureChildWithMargins()方法对所有子View进行了一遍测量,并计算出所有子View的最大宽度和最大高度。

    对于ViewGroup来说,它会调用child.measure()来完成子View的测量。传入ViewGroup的MeasureSpec是它的父View用于约束其测量的,那么ViewGroup本身也需要生成一个childMeasureSpec来限制它的子View的测量工作。这个childMeasureSpec就由getChildMeasureSpec()方法生成,childMeasureSpec的形成规律如下:


    childMeasureSpec的形成.png

    View的measure过程由measure方法来完成,measure方法是一个final类型的方法,子类不能重写此方法。在View的measure方法中会去调用view的onMeasure方法。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    

    setMeasuredDimension会设置view测量的宽高。注意这里是测量的宽高,并非最终的宽高,虽然绝大数情况下测量宽高和最终宽高一样,但是最终的宽高还是由layout来决定。getMeasuredWidth(),getMeasuredHeight()方法用来获得测量的宽高,通过这两个方法在onMeasure中不一定能获得准确的测量宽高,因为有时候或多次调用onMeasure,所以在onLayout中去获得测量宽高是最保险的方式。

    Layout布局过程

    layout确定View本身的位置,onLayout确定子元素的位置

    下面是view中layout的源码:先是通过setFrame的方法来确定view自己的位置,然后再onLayout的方法来确定子view的布局,onLayout没有统一的实现,不同的viewGroup由不同的实现。

        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;
        }
    
    getMeasureHeight()和getHeight(),测量宽高和最终宽高的区别

    getMeasureHeight()是在measure之后获得,getHeight()是在layout之后获得的,两个绝大多数情况下是一样的,只有重写layout方法之后会不同。在下面这种情况下,getHeight比getMesaureHeight大100

        public void layout(int l, int t, int r, int b) {
             super.layout(l, t, r+100, b+100);
        }
    

    draw绘制流程

    实际上,View类的onDraw()方法为空,因为每个View绘制自身的方式都不尽相同,对于decorView来说,由于它是容器View,所以它本身并没有什么要绘制的。dispatchDraw()方法用于绘制子View,显然普通View(非ViewGroup)并不能包含子View,所以View类中这个方法的实现为空。ViewGroup类的dispatchDraw()方法中会依次调用drawChild()方法来绘制子View。
    draw过程简单遵循如下几步:
    (1)绘制背景backgroud.draw(canvas)
    (2)绘制自己(onDraw)
    (3) 绘制children(dispatchDraw)
    (4)绘制装饰 onDrawScrollBars

    相关文章

      网友评论

        本文标题:View的绘制流程

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