Android-View的繪制流程分析
文章目錄
- Android-View的繪制流程分析
- 一、概述
- 二、繪制流程圖
- 三、代碼分析
- 1、View.requestLayout()
- 2、ViewRootImpl#requestLayout()
- 3、ViewRootImpl#scheduleTraversals()
- 4、ViewRootImpl#TraversalRunnable
- 5、ViewRootImpl#doTraversal()
- 6、ViewRootImpl#performTraversals()
- 7、ViewRootImpl#performDraw()
- 8、ViewRootImpl#draw()
- 9、ViewRootImpl#drawSoftware()
- 10、View#draw()
- 11、View#dispatchDraw(Canvas canvas)
- 12、ViewGroup#dispatchDraw(Canvas canvas)
- 13、ViewGroup#drawChild
- 14、View#draw(canvas, this, drawingTime)
- 15、View#draw(Canvas canvas)
- 四、總結
一、概述
android系統中串列的滑動,影片的展示,文字的渲染等都是android中的View不斷的繪制形成的,今天我們就來分析下android的繪制流程
我會從android的原始碼層面來逐步分析呼叫程序,關鍵代碼點,以及作用
二、繪制流程圖
三、代碼分析
1、View.requestLayout()
這個方法會進行視圖的重新測量、布局、繪制
public void requestLayout() {
//清理measure的快取資訊,這個是測量的資訊
//這個資訊在view的mesure中被創建,measure完成后會被保存下來
/*
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL);
*/
if (mMeasureCache != null) mMeasureCache.clear();
/*
mAttacbhInfo 這個變數在View出現很多,但是感覺我們自己沒怎么用過,這個變數不是public的,一般在ViewRootImpl用的多,
mAttacbhInfo:字面意思是附著的資訊,我們知道每個View都是在一個視窗之上,這個里面就有關于視窗的資訊
還有其他的資訊比如token,viewrootImpl資訊等
*/
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
//這個viewRoot是從mAttachInfo 獲取的
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
//這個的判斷含義是,如果ViewRootImpl正在布局的話,就直接回傳了,
//大家有興趣點開ViewRootImpl 跟蹤下邏輯就知道了
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
//正在布局的view
mAttachInfo.mViewRequestingLayout = this;
}
//添加一個flag標記
//這個標記是在layout的標記,在View#layout()中去除
//是一個狀態標記位
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
//isLayoutRequested() 最侄訓通過PFLAG_FORCE_LAYOUT來判斷,當前是否在layout
//如果父視圖正在布局中,就回傳
if (mParent != null && !mParent.isLayoutRequested()) {
//核心代碼,呼叫父視圖的布局,View的父視圖 只能是ViewGroup
//但是ViewGroup 并沒有重新requestLayout方法,說明viewgroup還是會呼叫view的requestlayout
//這個邏輯就形成了一個層層往上呼叫的邏輯了
//當到達最頂層ViewRootImpl的時候mParent==null了,就會呼叫ViewRootImpl的requestLayout
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
2、ViewRootImpl#requestLayout()
@Override
1046 public void requestLayout() {
//沒有在布局的話 就進入
1047 if (!mHandlingLayoutInLayoutRequest) {
//檢查當前操作的執行緒是否為主執行緒,這個就是我們常看到的非主執行緒例外了,這個checkThread 執行緒檢查在
//這里面很多
/*
void checkThread() {
6891 if (mThread != Thread.currentThread()) {
6892 throw new CalledFromWrongThreadException(
6893 "Only the original thread that created a view hierarchy can touch its views.");
6894 }
6895 }
*/
1048 checkThread();
1049 mLayoutRequested = true;
//核心代碼,進入計劃遍歷程序中
1050 scheduleTraversals();
1051 }
1052 }
3、ViewRootImpl#scheduleTraversals()
void scheduleTraversals() {
1223 if (!mTraversalScheduled) {
1224 mTraversalScheduled = true;
1225 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
/*
mChoreographer 這個在我的其他文章中有介紹,這個就是android的一個時間編排者,主要是對視圖渲染,輸入,影片處理做時間的統一,這個的時間是由ServiceFlinger控制的,說白了,由硬體來參與了控制
發送的Vsync請求信號,然后間隔一定時間收到Vsync信號,這個邏輯由Choreographer做了封裝處理
信號型別CALLBACK_TRAVERSAL
我們接著看mTraversalRunnable的實作
*/
1226 mChoreographer.postCallback(
1227 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
1228 if (!mUnbufferedInputDispatch) {
1229 scheduleConsumeBatchedInput();
1230 }
1231 notifyRendererOfFramePending();
1232 pokeDrawLockIfNeeded();
1233 }
1234 }
4、ViewRootImpl#TraversalRunnable
final class TraversalRunnable implements Runnable {
6336 @Override
6337 public void run() {
6338 doTraversal();
6339 }
6340 }
5、ViewRootImpl#doTraversal()
void doTraversal() {
1246 if (mTraversalScheduled) {
1247 mTraversalScheduled = false;
1248 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
1249
1250 if (mProfile) {
1251 Debug.startMethodTracing("ViewAncestor");
1252 }
1253 //核心方法,進入看
1254 performTraversals();
1255
1256 if (mProfile) {
1257 Debug.stopMethodTracing();
1258 mProfile = false;
1259 }
1260 }
1261 }
6、ViewRootImpl#performTraversals()
//這個方法太長了,無法完全拷貝,我寫成了偽代碼了
//這個方法是真正的執行分發遍歷的核心方法,是ViewRootImpl的核心
//這個方法主要做了三件事
performTraversals()
{
xxxxxx
//執行測量流程
performMeasure()
xxxxx
//執行布局流程
performLayout()
xxx
//執行繪制流程
performDraw()
}
/*
上面的每一個流程最終都會觸發View樹的對應處理流程
我們先用其中的一個performDraw來分析
*/
7、ViewRootImpl#performDraw()
private void performDraw() {
2598 if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
2599 return;
2600 }
2601
2602 final boolean fullRedrawNeeded = mFullRedrawNeeded;
2603 mFullRedrawNeeded = false;
2604
2605 mIsDrawing = true;
2606 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
2607 try {
//核心方法,進入看
2608 draw(fullRedrawNeeded);
2609 } finally {
2610 mIsDrawing = false;
2611 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2612 }
2613
2614 // For whatever reason we didn't create a HardwareRenderer, end any
2615 // hardware animations that are now dangling
2616 if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
2617 final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
2618 for (int i = 0; i < count; i++) {
2619 mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
2620 }
2621 mAttachInfo.mPendingAnimatingRenderNodes.clear();
2622 }
8、ViewRootImpl#draw()
public void draw(){
xxxx
.....
xxxxxx
省略代碼
2810 if (mAttachInfo.mHardwareRenderer != null &&
2811 !mAttachInfo.mHardwareRenderer.isEnabled() &&
2812 mAttachInfo.mHardwareRenderer.isRequested()) {
2813
2814 try {
2815 mAttachInfo.mHardwareRenderer.initializeIfNeeded(
2816 mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
2817 } catch (OutOfResourcesException e) {
2818 handleOutOfResourcesException(e);
2819 return;
2820 }
2821
2822 mFullRedrawNeeded = true;
2823 scheduleTraversals();
2824 return;
2825 }
2826 //這里是核心代碼,這里的引數surface對應的表面,dirty是要繪制的區域
2827 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
2828 return;
2829 }
2830 }
2831 }
2832
2833 if (animating) {
2834 mFullRedrawNeeded = true;
2835 scheduleTraversals();
2836 }
2837 }
9、ViewRootImpl#drawSoftware()
2842 private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
2843 boolean scalingRequired, Rect dirty) {
2844
2845 // Draw with software renderer.
2846 final Canvas canvas;
2847 try {
2848 final int left = dirty.left;
2849 final int top = dirty.top;
2850 final int right = dirty.right;
2851 final int bottom = dirty.bottom;
2852 //鎖定surface 獲得canvas 這個操作大家都熟悉把,這個surface是Ui執行緒下的一個surface,所有的View共享一個surface,
2853 canvas = mSurface.lockCanvas(dirty);
2854
2855 // The dirty rectangle can be modified by Surface.lockCanvas()
2856 //noinspection ConstantConditions
2857 if (left != dirty.left || top != dirty.top || right != dirty.right
2858 || bottom != dirty.bottom) {
2859 attachInfo.mIgnoreDirtyState = true;
2860 }
2861
2862 // TODO: Do this in native
//設定密度
2863 canvas.setDensity(mDensity);
2864 } catch (Surface.OutOfResourcesException e) {
2865 handleOutOfResourcesException(e);
2866 return false;
2867 } catch (IllegalArgumentException e) {
2868 Log.e(mTag, "Could not lock surface", e);
2869 // Don't assume this is due to out of memory, it could be
2870 // something else, and if it is something else then we could
2871 // kill stuff (or ourself) for no reason.
2872 mLayoutRequested = true; // ask wm for a new surface next time.
2873 return false;
2874 }
2875
2876 try {
2877 if (DEBUG_ORIENTATION || DEBUG_DRAW) {
2878 Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
2879 + canvas.getWidth() + ", h=" + canvas.getHeight());
2880 //canvas.drawARGB(255, 255, 0, 0);
2881 }
2882
2883 // If this bitmap's format includes an alpha channel, we
2884 // need to clear it before drawing so that the child will
2885 // properly re-composite its drawing on a transparent
2886 // background. This automatically respects the clip/dirty region
2887 // or
2888 // If we are applying an offset, we need to clear the area
2889 // where the offset doesn't appear to avoid having garbage
2890 // left in the blank areas.
2891 if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
//做一些顏色的初始化
2892 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
2893 }
2894
2895 dirty.setEmpty();
2896 mIsAnimating = false;
2897 mView.mPrivateFlags |= View.PFLAG_DRAWN;
2898
2899 if (DEBUG_DRAW) {
2900 Context cxt = mView.getContext();
2901 Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
2902 ", metrics=" + cxt.getResources().getDisplayMetrics() +
2903 ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
2904 }
2905 try {
//做一些畫布的移動
2906 canvas.translate(-xoff, -yoff);
2907 if (mTranslator != null) {
2908 mTranslator.translateCanvas(canvas);
2909 }
2910 canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
2911 attachInfo.mSetIgnoreDirtyState = false;
2912 //這個是核心方法,這個draw是View類的方法
//這個mView就是Dectorview,繼承自FrameLayout,FrameLayout繼承自ViewGroup
//所以這個draw 我們就先到ViewGroup尋找,發現ViewGroup沒有重寫,直接用的view 的draw
//去View查看draw,進入看
2913 mView.draw(canvas);
2914
2915 drawAccessibilityFocusedDrawableIfNeeded(canvas);
2916 } finally {
2917 if (!attachInfo.mSetIgnoreDirtyState) {
2918 // Only clear the flag if it was not set during the mView.draw() call
2919 attachInfo.mIgnoreDirtyState = false;
2920 }
2921 }
2922 } finally {
2923 try {
//繪制完成就post,通知繪制
2924 surface.unlockCanvasAndPost(canvas);
2925 } catch (IllegalArgumentException e) {
2926 Log.e(mTag, "Could not unlock surface", e);
2927 mLayoutRequested = true; // ask wm for a new surface next time.
2928 //noinspection ReturnInsideFinallyBlock
2929 return false;
2930 }
2931
2932 if (LOCAL_LOGV) {
2933 Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
2934 }
2935 }
2936 return true;
2937 }
10、View#draw()
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
我們發現google已經為我們清晰的注釋了draw要做的作業了
* 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 保存layers
* 3. Draw view's content 畫內容
* 4. Draw children 畫子視圖,這個是核心,這個會呼叫ViewGroup中的dispatchDraw
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance) //畫滑動bar
* 7. If necessary, draw the default focus highlight
*/
// Step 1, draw the background, if needed
int saveCount;
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
//我們繼續看View的dispatchDraw方法
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
11、View#dispatchDraw(Canvas canvas)
protected void dispatchDraw(Canvas canvas) {
//空實作,因為view不負責分發,只有去ViewGroup中找了
}
12、ViewGroup#dispatchDraw(Canvas canvas)
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
//處理布局影片的東西
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
if (mAnimationListener != null) {
//布局影片的開始回呼
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
/*
下面的程序主要是對viewgroup中的canvas進行裁切,就是clipRect操作,讓對應的View只能在對應的區域繪制,所以先要canvas.save 保存當前的狀態,后面來恢復,
我們看到里面有padding,這個padding是只有ViewGroup有的,裁切的回傳會減去這個邊距
*/
int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
//下面就是開始正式的遍歷子視圖,然后遍歷繪制
// We will draw our child's animation, let's reset the flag
//清除標志
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
final long drawingTime = getDrawingTime();
if (usingRenderNodeProperties) canvas.insertReorderBarrier();
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
int transientIndex = transientCount != 0 ? 0 : -1;
// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
/*
這個方法的核心就是對子view的遍歷然后繪制分發
*/
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
//呼叫drawChild 繪制
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
if (preorderedList != null) preorderedList.clear();
// Draw any disappearing views that have animations
//這個mDisappearingChildren 是只的是 正在因為執行影片 即將消失的View,這些View執行了去除影片,所以view已經移除,不能點擊,但是還是要把他們繪制出來的
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
if (usingRenderNodeProperties) canvas.insertInorderBarrier();
if (isShowingLayoutBounds()) {
onDebugDraw(canvas);
}
if (clipToPadding) {
canvas.restoreToCount(clipSaveCount);
}
// mGroupFlags might have been updated by drawChild()
flags = mGroupFlags;
//因為設定了FLAG_INVALIDATE_REQUIRED 可能重繪
if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);
}
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
mLayoutAnimationController.isDone() && !more) {
// We want to erase the drawing cache and notify the listener after the
// next frame is drawn because one extra invalidate() is caused by
// drawChild() after the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
@Override
public void run() {
notifyAnimationListener();
}
};
//布局影片完成了就回呼
post(end);
}
}
13、ViewGroup#drawChild
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
//呼叫了View的draw方法,進度看
return child.draw(canvas, this, drawingTime);
}
14、View#draw(canvas, this, drawingTime)
//這個方法是View和View的通用繪制處理
//主要是關于Canvas 的裁剪,移動,影片處理,分發
protected void draw(canvas, this, drawingTime)
{
xxxxx
省略代碼
...
//關于影片的處理
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
,,,,
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
//看到這個大家熟悉了嗎,這個就是那個處理scroller 需要的回呼
computeScroll();
sx = mScrollX;
sy = mScrollY;
}
//一堆關于canvas的處理,比如offsetForScroll,canvas.translate,還有Transformation 影片
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the transformation matrix,
// then redo the scroll translate to get the correct result.
canvas.translate(-transX, -transY);
//實作影片的移動
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
// Deal with alpha if it is or used to be <1
//下面代碼是處理alpha 透明度的繪制,有些View是設定了alpha的
if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (alpha < 1) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;
} else {
mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
if (!drawingWithDrawingCache) {
final int multipliedAlpha = (int) (255 * alpha);
if (!onSetAlpha(multipliedAlpha)) {
if (drawingWithRenderNode) {
renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
} else if (layerType == LAYER_TYPE_NONE) {
canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
multipliedAlpha);
}
} else {
// Alpha is handled by the child directly, clobber the layer's alpha
mPrivateFlags |= PFLAG_ALPHA_SET;
}
}
}
} else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
onSetAlpha(255);
mPrivateFlags &= ~PFLAG_ALPHA_SET;
}
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((RecordingCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
//核心代碼,會呼叫ViewGroup中的dispathchDraw 然會形成遞回呼叫,直到最后一個是View為止
dispatchDraw(canvas);
} else {
//呼叫自己的View的draw
draw(canvas);
}
}
}
...
省略代碼
xxxxxx
}
15、View#draw(Canvas canvas)
之前那個View#draw,實際是Viewgroup,會呼叫dispatchDraw
但是現在這個是一個View 呼叫自己的dispatchDraw 是空實作
到此為止所有的遍歷走完了
之前說的measure ,layout跟這個draw的呼叫類似,自行分析
四、總結
整個程序包括通知和執行
通知布局實質就是呼叫View的跟節RootViewImpl,
執行:測量、布局、繪制,核心是通過不斷的ViewGroup的遍歷,迭代,直到最后的View是一個View,為止
我們可以把這個這個View形象的比喻成一棵樹,整個就是View樹,根部是RootViewImpl,RootViewImpl上面是DectorView,然后在上面就是由各種ViewGroup和View組成的視圖,最上面的葉子是View,比如TextView
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/345788.html
標籤:其他
上一篇:iOS15適配本地通知功能
