Android UI管理系統的層級關係和UI繪製流程

主要說下視圖的繪製,不關注啓動流程。

一個應用啓動,首先會啓動一個主Activity,然後開始加載視圖進行繪製。而繪製會從跟視圖ViewRootImpl.java 的performTraversals()方法開始,從上到下遍歷整個視圖樹,每個view控件負責繪製自己,而viewGroup還需要負責通知自己的子View進行繪製操作。

視圖繪製的過程可以分爲三個步驟:

測量(Measure)

佈局(Layout)

繪製(Draw)

performTraversals的核心代碼

 private void performTraversals(){
        //....
        int childWithMeasureSpec=getRootMesureSpec(mWidth,lp.width);
        int childHeightMeasureSpec=getRootMeasureSpec(mHeight,lp.height);
        //....
        //執行測量流程
        performMeasure(childWithMeasureSpec,childHeightMeasureSpec);
        //.....
        //執行佈局流程
        performLayout(lp,desiredWindowWidth,desiredWindowHeight);
        //....
        //....執行繪製流程
        performDraw();
 }

首先我們來說下Measure中的MeasureSpec:

MeasureSpec表示的是一個32位的整型值,它的高2位表示測量模式SpecMode,低30位表示某種測量模式下的規格大小SpecSize.

        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        /**
         * 不指定測量模式(通常用於系統內部,應用開發很少用)。
父視圖沒有限制子視圖的大小,子視圖可以是想要的任何尺寸
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        /**
         *精確測量模式,當該視圖的layout_width或者layout_height
指定爲具體指或者match_parent 的時候生效
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        /**
         * 最大值測量模式,當視圖的layout_width或者layout_height
指定爲wrap_content時生效。此時子視圖的尺寸可以是不超過父視圖允許的最大尺寸的任何尺寸
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

我們在來看看測量的具體:

//在viewGroup中遍歷子view 然後在調用子view的自身測量
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
}
//注意 childWidthMeasureSpec 這裏爲了獲取子view的測量模式,傳遞參數是父view的測量模式

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec =    getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//這一系列的調用最終是到onMeasure()方法了 這個方法在我們自定義view的時候
//自定義測量時需要重寫此方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
       //.....
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
       //.....
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            //.......
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
               //.....
            }

           //......
        }
        //.......
}
//設置測量後的結果
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),        widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//通過傳入的測量spec 來獲取最終大小
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;
}

至此measure的流程大致走完了。具體的還要去看源碼再細細品嚐。

接下來我們來看Layout:

ViewRootImpl.java

 //執行佈局流程
 performLayout(lp,desiredWindowWidth,desiredWindowHeight);

(上面貼過的代碼)

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                               int desiredWindowHeight) {
        //.....
        try {
        //.....
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        //.....
        }finally{
        //.....
        }
}
 public void layout(int l, int t, int r, int b) {
       //.....
        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);
        }else{
            //.....
        }
}
//空方法,子類如果是ViewGroup類型,則重寫這個方法,實現ViewGroup中所有view控件佈局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}

Layout的流程具體實現還要在onLayout裏面。

最後一個Draw

private boolean draw(boolean fullRedrawNeeded){
    //....
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                                        scalingRequired, dirty, surfaceInsets)) {
                                return false;
    }
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    //...
       mView.draw(canvas);
    //...
}
public void draw(Canvas canvas) {
        //步驟一:繪製view的背景.....
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
        //步驟二:如果需要的話,保存Canvas的圖層,爲fading做準備
        saveCount = canvas.getSaveCount();
        
        int solidColor = getSolidColor();
        if (solidColor == 0) {
            if (drawTop) {
                canvas.saveUnclippedLayer(left, top, right, top + length);
            }
            if (drawBottom) {
                canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
            }
            if (drawLeft) {
                canvas.saveUnclippedLayer(left, top, left + length, bottom);
            }
            if (drawRight) {
                canvas.saveUnclippedLayer(right - length, top, right, bottom);
            }
        }
        //步驟三:繪製view的內容
        onDraw(canvas);
        //步驟四: 繪製view的子view
        dispatchDraw(canvas);
        //步驟五:如果需要的話繪製view的fading邊緣並恢復圖層
        if (drawTop) {
            //...
            canvas.drawRect(left, top, right, top + length, p);
        }
        if (drawBottom) {
            //...
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }
        if (drawLeft) {
            //....
            canvas.drawRect(left, top, left + length, bottom, p);
        }
        if (drawRight) {
            //...
            canvas.drawRect(right - length, top, right, bottom, p);
        }
        //.....
        canvas.restoreToCount(saveCount);
        // 步驟六:繪製view的裝飾(例如滾動條)
        onDrawForeground(canvas);
    }
    public void onDrawForeground(Canvas canvas) {
        onDrawScrollIndicators(canvas);
        onDrawScrollBars(canvas);
        //......
    }

到此整個view的創建到繪製流程就結束了。最重要的還是在繪製那塊,比如我們需要的一些動效和一些特殊形狀都需要在這裏面實現。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章