UI繪製流程--View的繪製流程
版權宣告:本文為博主原創文章,未經博主允許不得轉載。https://blog.csdn.net/mingyunxiaohai/article/details/88775422
上一篇UI繪製流程–View是如何被新增到螢幕上的 我們學習了View是怎麼新增到螢幕上的,這一片來學習View繪製流程,它的入口在入口ActivityThread.handleResumeActivity()。
本篇基於9.0的原始碼以前的一篇文章Activity啟動流程 ,最後我Activity啟動的最後走到了ActivityThread中執行handleResumeActivity方法並裡面執行了activity的onResume方法我們在來看這個方法
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { ... final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); ... final Activity a = r.activity; ... final Activity a = r.activity; ... //獲取Window也就是PhoneWindow r.window = r.activity.getWindow(); //獲取PhoneWindow中的DecorView View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); //獲取PhoneWindow的引數 WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; ... a.mWindowAdded = true; wm.addView(decor, l); ... Looper.myQueue().addIdleHandler(new Idler()); }
程式碼中performResumeActivity就是去執行activity的onResume方法,之後建立了一個ViewManager ,然後拿到WindowManager的LayoutParams,最後通過addView方法把DecorView和LayoutParams放入ViewManager中。那ViewManager是什麼呢
從這裡我們可以知道,view的新增和繪製是onResume之後才開始的,所以onResume的時候我們是拿不到View的寬和高的
我們看到它是通過a.getWindowManager()獲得,a是activity,那就去activity中找一下這個方法
public WindowManager getWindowManager() { return mWindowManager; }
這裡直接返回了activity的一個成員變數mWindowManager,那我們去找一下這個成員變數的賦值的地方,可以找到一個set方法
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }
可以看到是呼叫了WindowManagerImpl中的createLocalWindowManager方法來建立的
public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mContext, parentWindow); }
結果返回了一個WindowManagerImpl物件,所以上面的ViewManager其實就是一個WindowManagerImpl物件。所以呢最後呼叫的就是它的addView方法
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }
它又呼叫了mGlobal的addView方法,mGlobal是個WindowManagerGlobal物件在成員變數中直接通過單例建立WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
去看它的addView方法
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; ... ViewRootImpl root; View panelParentView = null; ... //建立一個ViewRootImpl並設定引數 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); //儲存傳過來的view,ViewRootImpl,LayoutParams mViews.add(view); mRoots.add(root); mParams.add(wparams); ... root.setView(view, wparams, panelParentView); ... }
看到這裡建立了一個ViewRootImpl,給傳過來的DecorView置LayoutParams引數,然後放到對應的集合中快取,最後呼叫root.setView方法將他們關聯起來。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { ... requestLayout(); ... view.assignParent(this); }
裡面程式碼太多了,我們只關注裡面的 requestLayout()方法就行
public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { //判斷是不是主執行緒 checkThread(); mLayoutRequested = true; scheduleTraversals(); } } void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
判斷是不是在當前執行緒,當前activity的啟動時在主執行緒,這就是為什麼不能再子執行緒中更新UI,不過這裡我們知道上面的方法時在onResume之後執行的,所以如果我們在onResume之前的子執行緒中執行一個很快的更新UI的操作,如果沒有執行到這裡就不會報錯
首先判斷是不是在主執行緒然後呼叫了scheduleTraversals方法。
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
我們看到mChoreographer.postCallback方法中傳了一個mTraversalRunnable引數到佇列中去執行,mTraversalRunnable是TraversalRunnable物件,TraversalRunnable其實是一個Runnable物件,所以真正的的執行的程式碼在其run方法中。
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { ... //真正的開始執行繪製 performTraversals(); ... } }
又呼叫了performTraversals方法
private void performTraversals() { //DecorView final View host = mView; ... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ... //measure過程 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... //layout過程 performLayout(lp, mWidth, mHeight); ... //繪製過程 performDraw(); }
程式碼比較多,只提取出3個主要的方法,這幾個方法主要執行View的主要繪製流程:測量,佈局和繪製。
以上程式碼其實就是將我們的頂級view->DecorView新增到視窗上,關聯到ViewRootImpl中,並呼叫requestLayout();方法請求繪製,最後到了performTraversals方法中執行performMeasure,performLayout,performDraw真正的開始繪製。
下面就分別來看一下這三個方法。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
可以看到這裡呼叫了mView的measure方法,這個mView就是我們的前面add進來的DecorView。它是一個FrameLayout。點進去檢視
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... onMeasure(widthMeasureSpec, heightMeasureSpec); ... } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth= insets.left + insets.right; int opticalHeight = insets.top+ insets.bottom; measuredWidth+= optical ? opticalWidth: -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); }
首先我們看到點進來之後到了View這個類中,measure這個方法時final型別的,所以不能被重寫,因此就算他是FrameLayout最終也是在View類中執行measure的方法。
measure方法中又呼叫了onMeasure方法,然後直接呼叫setMeasuredDimension方法,最後呼叫了setMeasuredDimensionRaw方法。這些方法時幹什麼的呢,
首先我們先找到傳入的引數widthMeasureSpec和heightMeasureSpec瞭解這兩個引數的作用。
這兩個引數是怎麼來的呢,回到我們上面的performTraversals()方法,可以看到int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
第一個引數表示視窗的寬度,第二個引數表示當前view也就是DectorView的LayoutParams
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
可以看到根據我們View設定的MATCH_PARENT還是WRAP_CONTENT等返回了一個通過MeasureSpec.makeMeasureSpec方法返回了一個int型別的值measureSpec,那它代表什麼呢?
我們在測量View的時候需要知道兩點:
第一點View的測量模式
第二點View的尺寸
measureSpec表示一個32的整數值,其高兩位代表測量模式SpecMode,底30位表示該測量模式下的尺寸SpecSize。
我們進入MeasureSpace類可以看到3個常量
/** * 表示父容器不對子容器進行限制,子容器可以是任意大小, * 一般是系統內部使用 */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * 精準測量模式,當view的layout_width 或者 layout_height指定為固定值值 * 或者為match_parent的時候生效,這時候view的測量值就是SpecSize */ public static final int EXACTLY= 1 << MODE_SHIFT; /** * 父容器指定一個固定的大小,子容器可以使不超過這個值的任意大小 * 對應我們的wrap_content */ public static final int AT_MOST= 2 << MODE_SHIFT;
對於DecorView這個頂級View來說,它的MeasureSpec 由視窗的尺寸和其自身的LayoutParams決定。
我們在回到measure方法中檢視onMeasure方法,我們知道measure方法是個final方法不能被子類重寫,不過onMeasure方法就沒這個限制了,DecorView繼承自FrameLayout,所以我們進入FrameLayout中檢視它的onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ... for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { //迴圈測量子view measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } ... // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } //設定自身的寬高 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); }
這裡找出所有的子View,然後迴圈呼叫measureChildWithMargins方法測量子view的寬高,之後呼叫setMeasuredDimension確定自己的寬高
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //獲取子控制元件的測量規格 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
先獲取子控制元件的寬高的測量規格,然後呼叫子控制元件的measure方法傳入測量規格,子控制元件的測量規格是怎麼獲取的呢,點進去看到getChildMeasureSpec這個方法是在ViewGroup類中
/** * @param spec 父控制元件的測量規格 * @param padding 父控制元件已經佔用的大小(減去padding和margin) * @param childDimension 子控制元件LayoutParams中的尺寸 * @return a MeasureSpec integer for the child */ public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //父控制元件的測量模式 int specMode = MeasureSpec.getMode(spec); //父控制元件的尺寸 int specSize = MeasureSpec.getSize(spec); //子容器可用大小要減去父view的padding和子view的margin int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // 如果父控制元件是精準尺寸,也就是父控制元件知道自己的大小 case MeasureSpec.EXACTLY: //如果子view設定了尺寸比如100dp,那麼測量大小就是100dp if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子view設定的MATCH_PAREN想要沾滿父view //父view是精準模式,那麼把父view的size給它 resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子view設定的WRAP_CONTENT,那麼它想隨意決定自己的大小 //你可以隨意玩,但是不能大於父控制元件的大小, //那麼暫時把父view的size給它 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 如果父控制元件是最大模式,也就是父控制元件也不知道自己的大小 case MeasureSpec.AT_MOST: //子控制元件設定了具體值 if (childDimension >= 0) { //那就返回這個具體值 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //子view想和父view一樣大,但是父view也不知道自己多大 //把暫時父view的size給它,約束它不能超過父view resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //子view想要自己確定尺寸 //不能大於父view的size resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 父view是不確定的,一般是系統呼叫開發中不用 case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
對於普通的view來說
它的MeasureSpec由其父view的MeasureSpec和自身的LayoutParams來決定
parentSpecMode/childLayoutParams | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp/px | EXACTLY/ chileSize | EXACTLY/ chileSize | EXACTLY / chileSize |
match_parent | EXACTLY / parentSize | AT_MOST /parentSize | 0 |
wrap_content | AT_MOST/ parentSize | AT_MOST /parentSize | 0 |
- 當view採用固定寬高的時候,不管父容器是什麼模式,子view的MeasureSpec都是精確模式,並且大小就是其LayoutParams中設定的大小
- 當view的寬或高是match_parent的時候,如果父容器是精準模式,那麼子view的也是精準模式,其大小是父view的剩餘空間,如果父容器是最大模式,那麼子view也是最大模式,其大小暫時設為父view的大小並不能超過父view的大小。
- 當view的寬或高是wrap_content的時候,不管父容器是什麼模式,子view總是最大化,並且不超過父容器的剩餘空間。
OK總結一下
- ViewGroup執行measure方法->裡面通過onMeasure方法遞迴測量子控制元件的寬高,測量完後通過setMeasuredDimension呼叫setMeasuredDimensionRaw方法最終儲存自己的寬高。
- View執行measure->onMeasure測量自己->測量完後通過setMeasuredDimension呼叫setMeasuredDimensionRaw方法最終儲存自己的寬高
我們回到view的onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
可以看到它在呼叫setMeasuredDimension傳參的的時候呼叫了getDefaultSize方法
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; }
這個邏輯很簡單,首先UNSPECIFIED我們不用管一般系統用,然後我們看到AT_MOST和EXACTLY最後的結果是一樣的都賦值為specSize,這個specSize就是view測量後的大小。也就是getSuggestedMinimumWidth和getSuggestedMinimumHeight兩個方法返回的值。
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
從上面可以看出如果view沒有設定背景,則返回mMinWidth,反之則寬度為mMinWidth和背景寬度的最大值。mMinWidth對應我們xml中設定的android:minWidth屬性值,如果沒設定則為0,mBackground.getMinimumWidth()則是返回的Drawable的原始寬度。
從上面的getDefaultSize方法我們可以得出一個結論,當我們直接繼承view自定義控制元件的時候,需要重寫其onMeasure方法,然後設定其wrap_content時候的大小,否則即便我們在佈局中使用wrap_content,實際情況也相當於match_parent。原因可以從上面的表中看到,如果一個view設定了wrap_content,那麼其測量模式是AT_MOST,在這種模式下view的寬高都等於父容器的剩餘空間大小。
那怎麼解決上面的問題呢?看一個重寫onMeasure的例子
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 寬的測量規格 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); // 寬的測量尺寸 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); // 高度的測量規格 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); // 高度的測量尺寸 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); //根據View的邏輯得到,比如TextView根據設定的文字計算wrap_content時的大小。 //這兩個資料根據實現需求計算。 int wrapWidth,wrapHeight; // 如果是是AT_MOST則對哪個進行特殊處理 if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){ setMeasuredDimension(wrapWidth, wrapHeight); }else if(widthSpecMode == MeasureSpec.AT_MOST){ setMeasuredDimension(wrapWidth, heightSpecSize); }else if(heightSpecMode == MeasureSpec.AT_MOST){ setMeasuredDimension(widthSpecSize, wrapHeight); } }
我們只需給view指定一個預設的寬高,並在AT_MOST的時候設定寬高即可,預設寬高的大小根據實際情況來
OK,measure的方法就看完了下面來看layout的流程,這個比measure簡單多了
//lp頂層佈局的佈局屬性,頂層佈局的寬和高 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ... final View host = mView; ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); }
這裡吧mView賦值給host然後呼叫了其layout方法,我們知道mView其實就是DecorView。
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; //setFrame來確定4個頂點的位置 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //父容器確定子view的位置 onLayout(changed, l, t, r, b); 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); } } } final boolean wasLayoutValid = isLayoutValid(); mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; if (!wasLayoutValid && isFocused()) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; if (canTakeFocus()) { // We have a robust focus, so parents should no longer be wanting focus. clearParentsWantFocus(); } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) { clearFocusInternal(null, /* propagate */ true, /* refocus */ false); clearParentsWantFocus(); } else if (!hasParentWantsFocus()) { // original requestFocus was likely on this view directly, so just clear focus clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; View focused = findFocus(); if (focused != null) { if (!restoreDefaultFocus() && !hasParentWantsFocus()) { focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } } } if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); } }
layout的流程首先通過setFrame方法設定view的4個頂點的位置,4個頂點確定了,view在父容器中的位置也就確定了,然後呼叫onLayout方法來確定子元素的位置。onLayout需要不同的ViewGroup去自己實現比如LinearLayout和RelativeLayout的實現是不同的。
OK,layout也看完了下面看最後一步Draw的流程
private void performDraw() { ... boolean canUseAsync = draw(fullRedrawNeeded); ... } private boolean draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { ... } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { ... mView.draw(canvas); ... }
通過一系列的跳轉,我們終於找到關鍵方法mView.draw(canvas),從這裡就進入了view中的draw方法
public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * *1. Draw the background *2. If necessary, save the canvas' layers to prepare for fading *3. Draw view's content *4. Draw children *5. If necessary, draw the fading edges and restore layers *6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; //繪製背景 if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // 繪製自己 if (!dirtyOpaque) onDraw(canvas); // 繪製子view dispatchDraw(canvas); drawAutofilledHighlight(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // 繪製裝飾 前景滾動條(foreground, scrollbars) onDrawForeground(canvas); // 繪製預設的焦點突出顯示 drawDefaultFocusHighlight(canvas); if (debugDraw()) { debugDrawFocus(canvas); } // we're done... return; } ...
view的繪製過程上面註釋已經寫清楚了
- 繪製背景 (background.draw(canvas))
- 繪製自己 (onDrow)
- 繪製子view(dispatchDrow)
- 繪製裝飾(前景、滾動條)
如果我們是自定義view,就去實現onDraw方法,如果我們是自定義ViewGroup,那就去實現dispatchDraw方法,dispatchDraw方法中會遍歷子view呼叫子view的draw方法。
到這裡draw方法就看完了,view的繪製流程也執行完畢!
ps:view中有個特殊的方法setWillNotDraw
/** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */ public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
從註釋中看出來,如果一個view不需要繪製任何東西,這個標誌位設定為true之後,系統會進行相應的優化。
預設情況下,view沒有啟動這個標誌位,但是ViewGroup是會預設啟動這個標誌位的。所以當我們繼承ViewGroup的時候並且明確知道需要通過onDraw來繪製內容的時候,我們需要顯示的關閉這個標誌位。