View的繪製流程主要包括measure,layout,draw三大流程,measure用來確定view的測量寬/高,layout用來確定view的最終寬/高和四個頂點的位置,而draw則將View繪製到屏幕上
Measure
-
如果只是一個原始的View,那麼通過meaure方法就完成了其測量過程,如果是一個ViewGroup,除了完成自己的測量過程外,還會遍歷去調用所有子View的measure方法,各個子元素再去遞歸調用這個流程
-
view的measure過程由measure方法來完成,measure方法是一個final方法,這意味着子類不能重寫此方法,在View的measure方法中回去調用view的onMeasure方法
-
onMeasure方法如下(直接繼承原始View)
/** * <p> * 測量視圖及其內容以確定測量的寬度和測量的高度,這個方法由measure方法調用,並且應該被子類重寫,以 * 準確和有效的測量其內容 * Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and * should be overridden by subclasses to provide accurate and efficient * measurement of their contents. * </p> * * <p> * 當子類重寫了此方法,則必須調用setMeasuredDimension方法來存儲view的測量寬/高,如果不這樣做將會觸發由 *measure方法拋出的異常throw new IllegalStateException("View with id " + getId() + ": " + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); * <strong>CONTRACT:</strong> When overriding this method, you * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the * measured width and height of this view. Failure to do so will trigger an * <code>IllegalStateException</code>, thrown by * {@link #measure(int, int)}. Calling the superclass' * {@link #onMeasure(int, int)} is a valid use. * </p> * * <p> * The base class implementation of measure defaults to the background size, * unless a larger size is allowed by the MeasureSpec. Subclasses should * override {@link #onMeasure(int, int)} to provide better measurements of * their content. * </p> * * <p> * If this method is overridden, it is the subclass's responsibility to make * sure the measured height and width are at least the view's minimum height * and width ({@link #getSuggestedMinimumHeight()} and * {@link #getSuggestedMinimumWidth()}). * </p> * * @param widthMeasureSpec horizontal space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * @param heightMeasureSpec vertical space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * * @see #getMeasuredWidth() * @see #getMeasuredHeight() * @see #setMeasuredDimension(int, int) * @see #getSuggestedMinimumHeight() * @see #getSuggestedMinimumWidth() 取決於背景尺寸和minWidth的二者中的較大值minWidth默認爲0 * @see android.view.View.MeasureSpec#getMode(int) * @see android.view.View.MeasureSpec#getSize(int) */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
onMeasure方法中必須調用
setMeasuredDimension
方法,否則將拋出異常,在這裏我們只需關注getDefaultSize
方法/** * 一個用來返回默認大小的實用方法,如果MeasureSpec沒有提供任何限制則用系統提供的尺寸(背景尺寸或者minWidth * 的尺寸取二者最大);如果MeasureSpec允許,尺寸將會變大 * Utility to return a default size. Uses the supplied size if the * MeasureSpec imposed no constraints. Will get larger if allowed * by the MeasureSpec. * * @param size Default size for this view * @param measureSpec Constraints imposed by the parent * @return The size this view should be. */ 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;
可以看出getDefaultSize的邏輯很簡單,我們只需要看
AT_MOST
和EXACTLY
這兩種情況,返回的大小就是MeasureSpec中的specSize,也就是View測量後的大小,直接繼承view的自定義控件需要重寫onMeasure方法並設置WRAP_CONTENT時自身的大小,否則在佈局中使用WRAP_CONTENT就相當於MATCH_PARENT -
ViewGoup的measure過程
對於ViewGroup來說,除了完成自己的measure過程外,還會遍歷去調用所有子元素的measure方法,各個子元素再去遞歸執行這個過程,ViewGroup是一個抽象類,沒有重寫onMeasure方法,但是提供了
measureChildren
,measureChild
,measureChildWithMargins
方法/** * Ask all of the children of this view to measure themselves, taking into * account both the MeasureSpec requirements for this view and its padding. * We skip children that are in the GONE state The heavy lifting is done in * getChildMeasureSpec. * * @param widthMeasureSpec The width requirements for this view * @param heightMeasureSpec The height requirements for this 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); } } } /** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding. * The heavy lifting is done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param parentHeightMeasureSpec The height requirements for this 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); }
measureChild的思想就是就是取出子元素的LayoutParams,然後再通過
getChildMeasureSpec
來創建子元素的MeasureSpec從而傳遞給子元素的measure方法。ViewGroup並沒有onMeasure方法,這是因爲ViewGroup是一個抽象類,其測量過程需要各個子類去具體實現,比如LinearLayout、RelativeLayout都有不同的佈局特徵,它們在onMeasure中都會調用measureChildWithMargins
或者measureChild
去傳遞測量過程,最終傳遞到單一View的onMeasure,最終父元素會先測量子元素,測量完成子元素後根據子元素的測量情況來測量自己的大小
獲取View的寬高的一種方式
手動調用View的measure方法view.measure(int widthMeasureSpec, int heightMeasureSpec)
,這種方法比較複雜,這裏要分情況處理,根據View的LayoutParams區分:
-
match_parent
直接放棄,根據
getChildMeasureSpec
方法得知,構造此種MeasureSpec需要知道父元素剩餘可用空間,而這個我們是無從得知的,所以理論上是不可能測量出View的大小 -
具體的豎直(dp/px)
比如寬高都是100px,如下measure
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); view.measure(widthMeasureSpec,heightMeasureSpec);
-
wrap_content 如下measure
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) -1, View.MeasureSpec.AT_MOST); int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) -1, View.MeasureSpec.AT_MOST); view.measure(widthMeasureSpec,heightMeasureSpec);
注意到(1 << 30) -1,因爲View的尺寸用30位二進制表示,也就是最大是30個1(即2^30-1),也就是(1 << 30) -1,在最大化模式下,我們用View理論上能支持的最大值去構造MeasureSpec是合理的
layout過程
Layout的作用是ViewGroup用來確定子元素的位置,當ViewGroup的位置被確定後,它會在onLayout中會遍歷所有子元素並調用其layout方法,在layout方法中,onLayout又會被調用。layout方法確定view本身的位置,而onLayout方法確定所有子元素的位置(當然單一View會做一下其它事情參考TextView)。先看View的layout方法如下
/**
* 爲view及其子view分匹配大小和位置
* Assign a size and position to a view and all of its
* descendants
*
* 這是佈局機制的第二階段,第一階段是測量(measure),在這一階段,每個父級視圖都會調用子元素的layout方法以確定子元素
* 的位置
* <p>This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().</p>
*
* 派生類不應該重寫此方法,派生類如果有子元素,那應該重寫onLayout方法,在onLayout方法中去調用每個子元素
* 的layout方法
* <p>Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.</p>
*
* @param l Left position, relative to parent //相對父佈局
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
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);//調用onLayout方法
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
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);
}
}
}
//........後面省略
從代碼註釋也可以看出,我們自定義View只需重寫onLayout方法,在onLayout方法中去調用子元素(如果有)的layout方法;layout的大致流程如下:首先會通過setOpticalFrame
或者setFrame
來設置View的四個頂點的位置,View的四個頂點一旦確定,那麼View在父容器中的位置也就確定了,接着會調用onLayout方法,這個方法用來確定每個子元素的位置,和onMeasure類似,onLayout的具體實現同樣和具體的佈局有關,所以View和ViewGroup均沒有真正實現onLayout方法
draw過程
draw的過程比較簡單但是最能玩出花樣的,它的作用是將View繪製到屏幕上,View的繪製過程一般遵循以下幾步
- Draw the background,繪製背景
- If necessary, save the canvas’ layers to prepare for fading,如果有需要保存畫布的圖層
- Draw view’s content,繪製自己的內容(onDraw)
- Draw children 繪製children(diapatchDraw)
- If necessary, draw the fading edges and restore layers
- Draw decorations (scrollbars for instance)繪製裝飾