? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?View體系詳解(2)
? ? 前言:看了大概一個(gè)月SystemUI的相關(guān)源碼,里面關(guān)于自定義View的知識(shí)比較多,迫使自己要去了解以前不太懂的顯示子系統(tǒng)的知識(shí),以前只知道一些粗略的view知識(shí),如它是用來(lái)顯示具體畫(huà)面的,它的載體是window,它可以復(fù)寫(xiě)事件處理函數(shù)去處理某些點(diǎn)擊事件,自定義view要實(shí)現(xiàn)onMeasure, onLayout, onDraw等,但是一直比較模糊,只是知道個(gè)大概,經(jīng)過(guò)一陣子的源碼和博客的閱讀,對(duì)view體系有了許多新認(rèn)識(shí)和領(lǐng)悟,因此記錄下來(lái)。計(jì)劃分以下幾部分:
? ? View體系詳解(2):自定義View流程以及系統(tǒng)相關(guān)行為
? ? View體系詳解(3):View事件處理機(jī)制
? ? ? ? ? ? 寫(xiě)的不好請(qǐng)理解,由于自己知識(shí)水平和技術(shù)經(jīng)驗(yàn)所限,不可避免有錯(cuò)漏的地方,懇請(qǐng)指正。? ? ?
自定義View流程以及系統(tǒng)相關(guān)行為
? ? ? 1.首先,我們要知道系統(tǒng)繪制view的過(guò)程,我們才能知道view是怎么顯示出來(lái)的,所以在寫(xiě)自定義view之前先縷清系統(tǒng)如何繪制view。在這里,我們可以從ViewManager的接口函數(shù)addView()來(lái)看看流程,分別看看ViewGroup和WindowManager這兩個(gè)實(shí)現(xiàn)類(lèi)對(duì)于這個(gè)函數(shù)的邏輯處理,我們就能知道系統(tǒng)是怎么管理這么多的view了。
? ? ? 從View體系詳解(1)中我們知道了ViewGroup是view的根節(jié)點(diǎn),每個(gè)view的mParent對(duì)象是它所屬的ViewGroup對(duì)象的實(shí)例,在ViewGroup中的addView實(shí)現(xiàn)如下:
```java
@Override
public void addView(View child, LayoutParams params) {
?? addView(child, -1, params);? //調(diào)到內(nèi)部函數(shù)addView()
}
------------------------------------------------------------------------------------------------------
? public void addView(View child, int index, LayoutParams params) {
? ?? ***省略
? ?? requestLayout();? //該函數(shù)會(huì)重新觸發(fā)繪制邏輯
? ?? invalidate(true);
? ?? addViewInner(child, index, params, false);
? ?? }
------------------------------------------------------------------------------------------------------ ? ?
?? private void addViewInner(View child, int index, LayoutParams params,
? ? ? ? ?? boolean preventRequestLayout) {
? ? ?? ***省略
? ? ?? if (index < 0) {
? ? ? ? ?? index = mChildrenCount;? //一般插入的index為-1,這里改變它的值,讓它能插入到線性表的后面
? ? ?? }
? ? ?? addInArray(child, index);? //把子view插入到線性表中
? ? ?? // tell our children
? ? ?? if (preventRequestLayout) {
? ? ? ? ?? child.assignParent(this);
? ? ?? } else {
? ? ? ? ?? child.mParent = this; ? //告訴子view的父節(jié)點(diǎn)是自己,也就是給View中的變量mParent賦值為自己
? ? ?? }
? ?? ***省略
?? }
? ? ? ? 所以在ViewGroup的addView()實(shí)現(xiàn)中,是把子view加到自己的線性表中,構(gòu)成view樹(shù)去管理,然后觸發(fā)一次繪制流程,把添加到view樹(shù)中的子view去顯示出來(lái),觸發(fā)繪制的流程是requestLayout(), 可以看到ViewGroup中并沒(méi)有相關(guān)實(shí)現(xiàn),而是在父類(lèi)View中:
public void requestLayout() {
?? if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
? ? ?? // Only trigger request-during-layout logic if this is the view requesting it,
? ? ?? // not the views in its parent hierarchy
? ? ?? ViewRootImpl viewRoot = getViewRootImpl();
? ? ?? if (viewRoot != null && viewRoot.isInLayout()) {
? ? ? ? ?? if (!viewRoot.requestLayoutDuringLayout(this)) {
? ? ? ? ? ? ?? return;
? ? ? ? ?? }
? ? ?? }
? ? ?? mAttachInfo.mViewRequestingLayout = this;
?? }
?? if (mParent != null && !mParent.isLayoutRequested()) {
? ? ?? mParent.requestLayout(); ?? //調(diào)用到ViewRootImpl的requestLayout
?? }
?? if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
? ? ?? mAttachInfo.mViewRequestingLayout = null;
?? }
}
? ? 回憶一下,一般子view的mParent,也就是父節(jié)點(diǎn)為ViewGroup,那么ViewGroup的父節(jié)點(diǎn),是誰(shuí)呢?就是ViewRootImpl。由此我們可以知道,繪制流程都在ViewRootImpl中,其實(shí)這也合理,一旦view樹(shù)的狀態(tài)改變了,我們就該從根節(jié)點(diǎn)去遍歷一遍,然后遞歸繪制。如此一來(lái),我們就再次驗(yàn)證了我們之前說(shuō)過(guò)的view的層級(jí)關(guān)系,子view的parent是ViewGroup,在子view中調(diào)用requestLayout,那么是調(diào)用到了ViewGroup中的requestLayout,由于ViewGroup中沒(méi)有相關(guān)實(shí)現(xiàn),那么又調(diào)用到它的父類(lèi)View的requestLayout,它里面的parent變量是ViewRootImpl,所以最終的繪制流程都是在ViewRootImpl中觸發(fā)。

? ? 那么繪制過(guò)程是如何進(jìn)行的呢?答案在requestLayout函數(shù)實(shí)現(xiàn)中。
@Override
public void requestLayout() {
?? if (!mHandlingLayoutInLayoutRequest) {
? ? ?? checkThread();? //檢查線程
? ? ?? mLayoutRequested = true;
? ? ?? scheduleTraversals();
?? }
}
------------------------------------------------------------------
?? void scheduleTraversals() {
? ? ?? if (!mTraversalScheduled) {
? ? ? ? ?? mTraversalScheduled = true;
? ? ? ? ?? mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
? ? ? ? ?? mChoreographer.postCallback(
? ? ? ? ? ? ? ? ?? Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //繪制任務(wù)觸發(fā)
? ? ? ? ?? if (!mUnbufferedInputDispatch) {
? ? ? ? ? ? ?? scheduleConsumeBatchedInput();
? ? ? ? ?? }
? ? ? ? ?? notifyRendererOfFramePending();
? ? ? ? ?? pokeDrawLockIfNeeded();
? ? ?? }
?? }
? ? ? 這個(gè)checkThread()是用來(lái)檢查觸發(fā)繪制的線程是否是創(chuàng)建這個(gè)view的線程,不是則拋出異常,所以一般我們需要在主線程繪制觸發(fā)這個(gè)函數(shù),否則會(huì)報(bào)錯(cuò)。那么非主線程,自己new的一個(gè)Thread對(duì)象去創(chuàng)建了view,然后在這個(gè)子線程觸發(fā)繪制,能否可行?答案是可以的,只要你在創(chuàng)建的線程中去觸發(fā)繪制流程就行了,只不過(guò)安卓一般是在主線程創(chuàng)建視圖。繪制任務(wù)主要是下面的函數(shù)來(lái)執(zhí)行,這里是我們view繪制的主邏輯:
private void performTraversals() {
? performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);? //開(kāi)始測(cè)量流程
? performLayout(lp, mWidth, mHeight); ? ? ? ? ? ? ? ? ? ? ? ? ? ? //開(kāi)始布局流程
? performDraw(); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? //開(kāi)始繪畫(huà)流程
}
? ? 該函數(shù)差不多800行代碼,很長(zhǎng),但是我們不去扣細(xì)節(jié),具體問(wèn)題再具體分析,相關(guān)繪制的操作入口就在這個(gè)函數(shù),如果遇到了相關(guān)問(wèn)題,我們可以進(jìn)來(lái)再看。這個(gè)函數(shù)關(guān)于view的,主要就是上面的三個(gè)函數(shù),當(dāng)然還有其他的,如dispatchAttachedToWindow(mAttachInfo, 0)的調(diào)用,是把一些重要信息通過(guò)父節(jié)點(diǎn)傳遞給子節(jié)點(diǎn)等,但是這些都挑出來(lái)說(shuō)就太多了,至于surface相關(guān),以及與native層的通信等,也一概先不談,因?yàn)檫@些都不是我們可以操作的到的層面,與我們相關(guān)的就是三大流程,逐一分析一下,還是只挑出來(lái)重要的邏輯流程,而不去扣細(xì)節(jié):
? ? (1)performMeasure:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
? ? ?? mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
-----------------------------------------------------------------------------------------------------
?? 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));
?? }
? ? Measure的過(guò)程就是從根節(jié)點(diǎn)調(diào)用Mesure,然后測(cè)量出來(lái)大小,把高度和寬度計(jì)算出來(lái)。至于MeasureSpec,是一個(gè)int型,32個(gè)bit位,它用兩位去記錄一個(gè)type,EXACTLY, AT_MOST,UNSPECIFIED,比如你在layout布局中定義了width為80dp,即view的寬度為80個(gè)像素點(diǎn),那么它就對(duì)應(yīng)了EXACTLY,如果定義了WRAP_CONTENT,那么就它對(duì)應(yīng)了AT_MOST。至于后面的30位,就是用來(lái)保存size,即大小。
? 問(wèn)題來(lái)了,從代碼看,就測(cè)量了父節(jié)點(diǎn)一個(gè)view么?肯定不是,首先ViewGroup是無(wú)法實(shí)例化的,它是一個(gè)抽象類(lèi),真正添加到ViewRootImpl中的父節(jié)點(diǎn)是具體的ViewGroup的子類(lèi),比如FrameLayout,所以當(dāng)我們?cè)诶L制流程觸發(fā)的時(shí)候,它實(shí)際觸發(fā)的時(shí)候FrameLayout中的onMeasure函數(shù),下面以FrameLayout復(fù)寫(xiě)的onMeasure函數(shù)為例,看看父節(jié)點(diǎn)是怎么把子view的測(cè)量結(jié)果也確定下來(lái):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
?? int count = getChildCount();
final boolean measureMatchParentChildren =
? ? ? ? ?? MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
? ? ? ? ?? MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
?? mMatchParentChildren.clear();
?? int maxHeight = 0;
?? int maxWidth = 0;
?? int childState = 0;
?? for (int i = 0; i < count; i++) {
? ? ?? final View child = getChildAt(i);
? ? ?? if (mMeasureAllChildren || child.getVisibility() != GONE) {
? ? ? ? ?? measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); //遞歸測(cè)量,其中父節(jié)點(diǎn)的MeasureSpec會(huì)影響子view
? ? ? ? ?? }
? ? ? ? }
?? // 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));
?? count = mMatchParentChildren.size();
?? if (count > 1) {
? ? ?? for (int i = 0; i < count; i++) {
? ? ? ? ?? final View child = mMatchParentChildren.get(i);
? ? ? ? ?? final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
? ? ? ? ?? final int childWidthMeasureSpec;
? ? ? ? ?? if (lp.width == LayoutParams.MATCH_PARENT) {
? ? ? ? ? ? ?? final int width = Math.max(0, getMeasuredWidth()
? ? ? ? ? ? ? ? ? ? ?? - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
? ? ? ? ? ? ? ? ? ? ?? - lp.leftMargin - lp.rightMargin);
? ? ? ? ? ? ?? childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
? ? ? ? ? ? ? ? ? ? ?? width, MeasureSpec.EXACTLY);
? ? ? ? ?? } else {
? ? ? ? ? ? ?? childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
? ? ? ? ? ? ? ? ? ? ?? getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
? ? ? ? ? ? ? ? ? ? ?? lp.leftMargin + lp.rightMargin,
? ? ? ? ? ? ? ? ? ? ?? lp.width);
? ? ? ? ?? }
? ? ? ? ?? final int childHeightMeasureSpec;
? ? ? ? ?? if (lp.height == LayoutParams.MATCH_PARENT) {
? ? ? ? ? ? ?? final int height = Math.max(0, getMeasuredHeight()
? ? ? ? ? ? ? ? ? ? ?? - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
? ? ? ? ? ? ? ? ? ? ?? - lp.topMargin - lp.bottomMargin);
? ? ? ? ? ? ?? childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
? ? ? ? ? ? ? ? ? ? ?? height, MeasureSpec.EXACTLY);
? ? ? ? ?? } else {
? ? ? ? ? ? ?? childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
? ? ? ? ? ? ? ? ? ? ?? getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
? ? ? ? ? ? ? ? ? ? ?? lp.topMargin + lp.bottomMargin,
? ? ? ? ? ? ? ? ? ? ?? lp.height);
? ? ? ? ?? }
? ? ? ? ?? child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //mode為MATCH_PARENT的再測(cè)量一遍
? ? ?? }
?? }
}
? ? ? 我們可以看到,這是一個(gè)遞歸遍歷測(cè)量的過(guò)程,從根節(jié)點(diǎn)一直往下測(cè),所有的子view都會(huì)被遍歷。這里我們要注意影響長(zhǎng)寬的因素,有:(1)父view的MeasureSpec的參數(shù),這個(gè)一般是定義再xml文件中的屬性,代碼中也可以設(shè)置;(2)view自己本身的LayoutParam,即布局參數(shù),基本的長(zhǎng)寬等參數(shù)在這個(gè)屬性類(lèi)中。所以view 的大小,是由自己的屬性值和父view的大小決定的,子view如果定義的值大于父view,它也最多只能獲得父view的剩余空間。
? ? (2)performLayout:? ? ? ?
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
? ? ?? int desiredWindowHeight) {
? ? ?? host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); //host就是根節(jié)點(diǎn)的view
? }
? ----------------------------------------------------------------------------------------------
? ? public void layout(int l, int t, int r, int b) {? //屏幕的左邊和上邊均為0,右邊和下邊的值分別為寬度和高度,因?yàn)榘沧康脑c(diǎn)處于屏幕左上方
? ? ?? int oldL = mLeft;
? ? ?? int oldT = mTop;
? ? ?? int oldB = mBottom;
? ? ?? int oldR = mRight;
? ? ?? if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
? ? ? ? ?? onLayout(changed, l, t, r, b);
? ? ? ? ?? }
? ? ?? }
? ----------------------------------------------------------------------------------------------- ? ? ?
? ? ?? protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
?? } ?
? ? 流程基本與Measure差不多,在View的layout被調(diào)用后,onLayout的方法緊接著也會(huì)被調(diào)用,該方法在View中為空實(shí)現(xiàn),因?yàn)樗皇潜仨氁獙?shí)現(xiàn)的,但是如果是根節(jié)點(diǎn)view,那么必須實(shí)現(xiàn),因?yàn)樽觱iew很多,需要一個(gè)布局的方案,由于ViewGroup為抽象類(lèi),那么實(shí)現(xiàn)就在某個(gè)Layout類(lèi)中了,再以FrameLayout為例:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
?? layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
----------------------------------------------------------------------------------------------------------------------------
?? void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
? ? ?? final int count = getChildCount();
? ? ?? final int parentLeft = getPaddingLeftWithForeground();
? ? ?? final int parentRight = right - left - getPaddingRightWithForeground();
? ? ?? final int parentTop = getPaddingTopWithForeground();
? ? ?? final int parentBottom = bottom - top - getPaddingBottomWithForeground();
? ? ?? for (int i = 0; i < count; i++) {
? ? ? ? ?? final View child = getChildAt(i);
? ? ? ? ?? if (child.getVisibility() != GONE) {
? ? ? ? ? ? ?? final LayoutParams lp = (LayoutParams) child.getLayoutParams();
? ? ? ? ? ? ?? final int width = child.getMeasuredWidth();
? ? ? ? ? ? ?? final int height = child.getMeasuredHeight();
? ? ? ? ? ? ?? int childLeft;
? ? ? ? ? ? ?? int childTop;
? ? ? ? ? ? ?? int gravity = lp.gravity;
? ? ? ? ? ? ?? if (gravity == -1) {
? ? ? ? ? ? ? ? ?? gravity = DEFAULT_CHILD_GRAVITY;
? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? final int layoutDirection = getLayoutDirection();
? ? ? ? ? ? ?? final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
? ? ? ? ? ? ?? final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
? ? ? ? ? ? ?? switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
? ? ? ? ? ? ? ? ?? case Gravity.CENTER_HORIZONTAL:
? ? ? ? ? ? ? ? ? ? ?? childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
? ? ? ? ? ? ? ? ? ? ?? lp.leftMargin - lp.rightMargin;
? ? ? ? ? ? ? ? ? ? ?? break;
? ? ? ? ? ? ? ? ?? case Gravity.RIGHT:
? ? ? ? ? ? ? ? ? ? ?? if (!forceLeftGravity) {
? ? ? ? ? ? ? ? ? ? ? ? ?? childLeft = parentRight - width - lp.rightMargin;
? ? ? ? ? ? ? ? ? ? ? ? ?? break;
? ? ? ? ? ? ? ? ? ? ?? }
? ? ? ? ? ? ? ? ?? case Gravity.LEFT:
? ? ? ? ? ? ? ? ?? default:
? ? ? ? ? ? ? ? ? ? ?? childLeft = parentLeft + lp.leftMargin;
? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? switch (verticalGravity) {
? ? ? ? ? ? ? ? ?? case Gravity.TOP:
? ? ? ? ? ? ? ? ? ? ?? childTop = parentTop + lp.topMargin;
? ? ? ? ? ? ? ? ? ? ?? break;
? ? ? ? ? ? ? ? ?? case Gravity.CENTER_VERTICAL:
? ? ? ? ? ? ? ? ? ? ?? childTop = parentTop + (parentBottom - parentTop - height) / 2 +
? ? ? ? ? ? ? ? ? ? ?? lp.topMargin - lp.bottomMargin;
? ? ? ? ? ? ? ? ? ? ?? break;
? ? ? ? ? ? ? ? ?? case Gravity.BOTTOM:
? ? ? ? ? ? ? ? ? ? ?? childTop = parentBottom - height - lp.bottomMargin;
? ? ? ? ? ? ? ? ? ? ?? break;
? ? ? ? ? ? ? ? ?? default:
? ? ? ? ? ? ? ? ? ? ?? childTop = parentTop + lp.topMargin;
? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? child.layout(childLeft, childTop, childLeft + width, childTop + height);
? ? ? ? ?? }
? ? ?? }
?? }
? ? ? 這里面就是計(jì)算出來(lái)坐標(biāo)點(diǎn)的位置,然后遍歷view樹(shù)去計(jì)算自己的位置,影響的因素主要是父容器的剩余位置,可以看看Gravity的各個(gè)變量是怎么影響到layout布局的,以Gravity.Left為例,如果在xml文件中的layout_gravity中設(shè)置了這個(gè)值,那么在FrameLayout布局中的處理方式,就是把父View的左邊的坐標(biāo)值加上自己的間隔值(leftMargin),得到自己這個(gè)view的左邊的坐標(biāo)值,而且ViewGroup中設(shè)置了該值,會(huì)讓子view都往左邊去靠,因?yàn)槭菑膌eft這個(gè)坐標(biāo)點(diǎn)去開(kāi)始去遍歷去排序的。按這個(gè)思路,我們也可以實(shí)現(xiàn)自己的布局方式,只要復(fù)寫(xiě)就好了。
? (3)performDraw
? ? ? Draw是一個(gè)比較復(fù)雜的過(guò)程,還是說(shuō)關(guān)于view的,如何繪制出一個(gè)特定的樣子就不說(shuō)了,因?yàn)閍pi十分多。
private void performDraw() {
? ? ?? draw(fullRedrawNeeded);
}
-----------------------------------------------------------------------
?? private void draw(boolean fullRedrawNeeded) {
? ? ? ? ? ? ? ? ? if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
? ? ? ? ? ? ? ? ?? return;
? ? ? ? ? ? ?? }
?? }
?? ----------------------------------------------------------------------
? ? ?? private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
? ? ? ? ?? boolean scalingRequired, Rect dirty) {
? ? ?? // Draw with software renderer.
? ? ?? final Canvas canvas;
? ? ?? canvas = mSurface.lockCanvas(dirty);
? ? ?? mView.draw(canvas);
? ? ?? return true;
?? }
? ? ? ? View中的draw方法:
public void draw(Canvas canvas) {
?? // Step 1, draw the background, if needed
?? 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) {
? ? ?? // Step 3, draw the content
? ? ?? onDraw(canvas);
? ? ?? // Step 4, draw the children
? ? ?? dispatchDraw(canvas);
? ? ?? // Step 6, draw decorations (foreground, scrollbars)
? ? ?? onDrawForeground(canvas);
? ? ?? // Step 7, draw the default focus highlight
? ? ?? drawDefaultFocusHighlight(canvas);
? ? ?? // we're done...
? ? ?? return;
?? }
?? ***省略
}
? 方法很長(zhǎng),但是源碼也給了注釋?zhuān)紫任覀円驯尘?,也就是布局的drawable對(duì)象畫(huà)出來(lái),然后畫(huà)父view的內(nèi)容,再遍歷畫(huà)子view的內(nèi)容,其他操作不是必須走的,與要實(shí)現(xiàn)的效果強(qiáng)相關(guān)。Canva可以理解為一個(gè)函數(shù)庫(kù),里面有各種api畫(huà)出來(lái)我們的內(nèi)容,此塊也不涉及。
/*
Draw traversal performs several drawing steps which must be executed in the appropriate order:? ? *
Draw the background
If necessary, save the canvas' layers to prepare for fading
Draw view's content
Draw children
If necessary, draw the fading edges and restore layers
Draw decorations (scrollbars for instance)
If necessary, draw the default focus highlight*/
? 至此,我們的繪制流程就走完了,需要注意的是,這是framework層的繪制流程,甚至都沒(méi)涉及到native層的代碼,安卓顯示系統(tǒng)龐大而復(fù)雜,這里談?wù)摰闹R(shí)上層的view的繪制流程,不涉及具體實(shí)現(xiàn),而且如果談?wù)摷?xì)節(jié)相關(guān)的東西,繁瑣且沒(méi)有多大的意義。但是,知道這么多,我們就可以去自定義一個(gè)自己的view了。
那么,WindowManager的addView函數(shù),又發(fā)生了什么呢?答案在WindowManager的實(shí)現(xiàn)類(lèi)WindowManagerImpl中。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
?? applyDefaultToken(params);
?? mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
----------------------------------------------------------------------------------
?? public void addView(View view, ViewGroup.LayoutParams params,
? ? ? ? ?? Display display, Window parentWindow) {
? ? ?? final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
? ? ?? if (parentWindow != null) {
? ? ? ? ?? parentWindow.adjustLayoutParamsForSubWindow(wparams);
? ? ?? }
? ? ?? ViewRootImpl root;
? ? ?? View panelParentView = null;
? ? ?? synchronized (mLock) {
? ? ? ? ?? int index = findViewLocked(view, false);
? ? ? ? ?? if (index >= 0) {
? ? ? ? ? ? ?? if (mDyingViews.contains(view)) {
? ? ? ? ? ? ? ? ?? // Don't wait for MSG_DIE to make it's way through root's queue.
? ? ? ? ? ? ? ? ?? mRoots.get(index).doDie();
? ? ? ? ? ? ?? } else {
? ? ? ? ? ? ? ? ?? throw new IllegalStateException("View " + view
? ? ? ? ? ? ? ? ? ? ? ? ?? + " has already been added to the window manager.");
? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? // The previous removeView() had not completed executing. Now it has.
? ? ? ? ?? }
? ? ? ? ?? root = new ViewRootImpl(view.getContext(), display);
? ? ? ? ? ?view.setLayoutParams(wparams);
? ? ? ? ?? mViews.add(view);
? ? ? ? ?? mRoots.add(root);
? ? ? ? ?? mParams.add(wparams);
? ? ? ? ?? // do this last because it fires off messages to start doing things
? ? ? ? ?? try {
? ? ? ? ? ? ?? root.setView(view, wparams, panelParentView);
? ? ? ? ?? } catch (RuntimeException e) {
? ? ? ? ? ? ?? // BadTokenException or InvalidDisplayException, clean up.
? ? ? ? ? ? ?? if (index >= 0) {
? ? ? ? ? ? ? ? ?? removeViewLocked(index, true);
? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? throw e;
? ? ? ? ?? }
? ? ?? }
?? }
? ? ? ? ? ? 交給WindowManagerGlobal的addView,然后我們看到了頂層view,ViewRootImpl的實(shí)例化,并調(diào)用它的setView(),再次說(shuō)明層級(jí)關(guān)系:window > ViewRootImpl > ViewGroup > View.? 下面的setView的主要代碼:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
?? synchronized (this) {
? ? ?? if (mView == null) {
? ? ? ? ?? mView = view;
? ? ? ? ?? // Schedule the first layout -before- adding to the window
? ? ? ? ?? // manager, to make sure we do the relayout before receiving
? ? ? ? ?? // any other events from the system.
? ? ? ? ?? requestLayout(); ? //開(kāi)始繪制
? ? ? ? ?? if ((mWindowAttributes.inputFeatures
? ? ? ? ? ? ? ? ?? & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
? ? ? ? ? ? ?? mInputChannel = new InputChannel();? //輸入通道創(chuàng)建,用于接受諸如TouchEvent等事件
? ? ? ? ?? }
? ? ? ? ? ? ?? res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
? ? ? ? ? ? ? ? ? ? ?? getHostVisibility(), mDisplay.getDisplayId(),
? ? ? ? ? ? ? ? ? ? ?? mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
? ? ? ? ? ? ? ? ? ? ?? mAttachInfo.mOutsets, mInputChannel);? //添加Window到WMS中
? ? ? ? ?? if (mInputChannel != null) {
? ? ? ? ? ? ?? if (mInputQueueCallback != null) {
? ? ? ? ? ? ? ? ?? mInputQueue = new InputQueue();
? ? ? ? ? ? ? ? ?? mInputQueueCallback.onInputQueueCreated(mInputQueue);
? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,? //java層的接受輸入的地方
? ? ? ? ? ? ? ? ? ? ?? Looper.myLooper());
? ? ? ? ?? }
? ? ? ? ?? view.assignParent(this); ? //把自己設(shè)置到根節(jié)點(diǎn)view的parent變量中
? ? ? ? ?? // Set up the input pipeline.
? ? ? ? ?? CharSequence counterSuffix = attrs.getTitle();
? ? ? ? ?? mSyntheticInputStage = new SyntheticInputStage();
? ? ? ? ?? InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);? //責(zé)任鏈模式,讓input事件挨個(gè)流轉(zhuǎn)直到找到處理者
? ? ? ? ?? InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
? ? ? ? ? ? ? ? ?? "aq:native-post-ime:" + counterSuffix);
? ? ? ? ?? InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
? ? ? ? ?? InputStage imeStage = new ImeInputStage(earlyPostImeStage,
? ? ? ? ? ? ? ? ?? "aq:ime:" + counterSuffix);
? ? ? ? ?? InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
? ? ? ? ?? InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
? ? ? ? ? ? ? ? ?? "aq:native-pre-ime:" + counterSuffix);
? ? ? ? ?? mFirstInputStage = nativePreImeStage;
? ? ? ? ?? mFirstPostImeInputStage = earlyPostImeStage;
? ? ? ? ?? mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
? ? ?? }
?? }
}
? ? ? 在setView中,主要做了三個(gè)事:觸發(fā)繪制流程,添加一個(gè)新的window到WMS,創(chuàng)建input接受器。這也是ViewRootImpl類(lèi)的主要職責(zé)。WindowSession是對(duì)應(yīng)一個(gè)窗口,是客戶(hù)端與WMS調(diào)用的代理類(lèi)。至于WMS怎么處理這個(gè)新添加的window等邏輯,以后再寫(xiě),畢竟這部分的邏輯也很多,此處還是專(zhuān)注于View,input事件的接受和遍歷發(fā)送以及處理等,在下面一篇講。
? ? 至此,流程就明確了,下面的流程圖就是在WindowManager的addView的調(diào)用流程,由于ViewGroup中的addView接口主要也是重新觸發(fā)繪制流程,所以不單獨(dú)畫(huà)了,只要知道WM的addView是會(huì)創(chuàng)建頂層View和新窗口以及input channel,而ViewGroup的addView是把一個(gè)view加入到自己的view樹(shù)中即可。

? 至于自定義view如何實(shí)現(xiàn),這個(gè)就看需求場(chǎng)景了,源碼里有許多定義好的,如TextView,Button等,可以參考,一般自定義View都要復(fù)寫(xiě)Measure,Layout,Draw。
? ? 總結(jié):
1.ViewGroup中addView是把子view加到view樹(shù)中,這期間沒(méi)有window的創(chuàng)建等,會(huì)重新觸發(fā)繪制流程,而WM的addView會(huì)創(chuàng)建頂層view和window等,也會(huì)觸發(fā)繪制流程。
2.自定義View一般復(fù)寫(xiě)onMeasure,onLayout,onDraw,其中如果你是ViewGrup類(lèi)型的子view,那么必須實(shí)現(xiàn)onLayout。
view繪制出來(lái)后,還有一個(gè)重要功能:響應(yīng)用戶(hù)的輸入事件,這又涉及到View是如何知道派發(fā)給哪個(gè)view去處理這個(gè)input事件的,下節(jié)整理出來(lái)。其實(shí)關(guān)于View的顯示,在上層的東西就這么多,但是底層,比如繪制頻率,硬件如何刷新和同步,操作系統(tǒng)的顯示模塊接口如何工作的,在native層與java層相對(duì)應(yīng)的東西,等等,還有很多知識(shí)點(diǎn),只有了解完這些才能徹底了解view是如何顯示出來(lái)的,這也是為什么draw相關(guān)的東西我沒(méi)寫(xiě)多少,因?yàn)槲乙矝](méi)徹底懂呢~~~