概述
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); } }