Android View框架的draw機制


概述

        Android中View框架的工作機制中,主要有三個過程:

                1、View樹的測量(measure) Android View框架的measure機制

                2、View樹的布局(layout)Android View框架的layout機制

                3、View樹的繪制(draw)Android View框架的draw機制

        View框架的工作流程為:測量每個View大小(measure)-->把每個View放置到相應的位置(layout)-->繪制每個View(draw)。

 

         本文主要講述三大流程中的draw過程。不清楚measure過程的,可以參考這篇文章 Android View框架的measure機制

                                                                                    不清楚layout過程的,可以參考這篇文章Android View框架的layout機制

 

        這篇文章不講述graphic模塊具體繪制API的使用,只是描述View框架的繪制過程。

 

 

帶着問題來思考整個draw過程。

1、系統為什么要有draw過程?

        View框架在經過了measure過程和layout過程之后,就已經確定了每一個View的尺寸和位置。那么接下來,也是一個重要的過程,就是draw過程,draw過程是用來繪制View的過程,它的作用就是使用graphic框架提供的各種繪制功能,繪制出當前View想要的樣子。

 

2、draw過程都干了點什么事?

        View框架中,draw過程主要是繪制View的外觀。ViewGroup除了負責繪制自己之外,還需要負責繪制所有的子View。而不含子View的View對象,就負責繪制自己就可以了。

        draw過程的主要流程如下:

        1、繪制 backgroud(drawBackground)     
        2、如果需要的話,保存canvas的layer,來准備fading(不是必要的步驟)
        3、繪制view的content(onDraw方法)
        4、繪制children(dispatchDraw方法)
        5、如果需要的話,繪制fading edges,然后還原layer(不是必要的步驟)
        6、繪制裝飾器、比如scrollBar(onDrawForeground)

 

 

源代碼分析

        在View的源代碼中,提取到了下面一些關於layout過程的信息。

        我們知道,整棵View樹的根節點是DecorView,它是一個FrameLayout,所以它是一個ViewGroup,所以整棵View樹的測量是從一個ViewGroup對象的draw方法開始的。

 

View:

1、draw

/** 

繪制一個View以及他的子View。最好不要覆寫該方法,應該覆寫onDraw方法來繪制自己。

*/

public void draw(Canvas canvas);

源代碼:

這里不給出,有興趣的讀者,可以自行查閱SDK。

偽代碼

public void draw(Canvas canvas) {
    1、繪制 backgroud(drawBackground)  ;
    2、如果需要的話,保存canvas的layer,來准備fading ;
    3、繪制view的content(onDraw方法);
    4、繪制children(dispatchDraw方法);
    5、如果需要的話,繪制fading edges,然后還原layer ;
    6、繪制裝飾器、比如scrollBar(onDrawForeground);
}

 


 

2、onDraw

/** 

繪制一個View的外觀。View的默認實現是空實現,所以這里沒有源碼給出。

*/

protected void onDraw(Canvas canvas);

 

 

ViewGroup:

1、dispatchDraw

/** 繪制子View,View類是空實現,ViewGroup類中有實現 */

protected void dispatchDraw(Canvas canvas);

源代碼:

這里不再給出,有興趣的讀者自行查閱SDK。

偽代碼:

protected void dispatchDraw(Canvas canvas) {
    if (需要繪制布局動畫) {
    for (遍歷子View) {
        綁定布局動畫;
    }
    啟動動畫控制,通知動畫開始;
    }

    for (遍歷子View) {
    child.draw();
    }
}

 


 

動手操作

        下面我們自己寫一個自定義的ViewGroup,讓它內部的每一個子View都垂直排布,並且讓每一個子View的左邊界都距離上一個子View的左邊界一定的距離。然后在整個布局內部繪制一個圓形。大概看起來如下圖所示:

 

實際運行效果如下:

 

代碼如下:

public class VerticalOffsetLayout extends ViewGroup {

    private static final int OFFSET = 100;
    private Paint mPaint;

    public VerticalOffsetLayout(Context context) {
        super(context);
        init(context, null, 0);
    }

    public VerticalOffsetLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }

    public VerticalOffsetLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        mPaint = new Paint(Color.BLUE);
        mPaint.setAntiAlias(true);
        mPaint.setAlpha(125);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width = 0;
        int height = 0;

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            ViewGroup.LayoutParams lp = child.getLayoutParams();
            int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
            int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
            child.measure(childWidthSpec, childHeightSpec);
        }

        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    int widthAddOffset = i * OFFSET + child.getMeasuredWidth();
                    width = Math.max(width, widthAddOffset);
                }
                break;
            default:
                break;

        }

        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    height = height + child.getMeasuredHeight();
                }
                break;
            default:
                break;

        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left = 0;
        int right = 0;
        int top = 0;
        int bottom = 0;

        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            left = i * OFFSET;
            right = left + child.getMeasuredWidth();
            bottom = top + child.getMeasuredHeight();

            child.layout(left, top, right, bottom);

            top += child.getMeasuredHeight();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int x = getWidth()/2;
        int y = getHeight()/2;
        canvas.drawCircle(x, y, Math.min(x, y), mPaint);
    }
}

 


 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM