接着前面的一篇 Android Application 启动流程分析及其源码调用探究,从 Activity 启动,到正式开始绘制流程,整个过程以下图所示:java
从上一篇 Android Application 启动流程分析及其源码调用探究 的最后一步 STEP 14 中,咱们看到主线程收到 H.LAUNCH_ACTIVITY
消息,交由 ActivityThread#handleLaunchActivity()
方法处理。该方法接着调用了 performLaunchActivity()
和 handleResumeActivity()
这两个方法,下面分别讲一下。canvas
ActivityThread.javabash
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { ··· Activity a = performLaunchActivity(r, customIntent); if (a != null) { ··· handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason); ··· } } else { ··· } } 复制代码
performLaunchActivity()
方法里面执行了几个操做,建立 Activity 对象,调用 Activity#attach()
, 建立 PhoneWindow 对象,调用 Activity#onCreate()
,初始化 DecorView ,添加布局到 DecorView 的 content ,调用 Activity#onStart()
;markdown
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ··· Activity activity = null; try { ··· //建立Activity对象 activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); ··· } catch (Exception e) { ··· } try { ··· if (activity != null) { ··· //调用 Activity#attach(),建立 PhoneWindow 对象 activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback); ··· //调用Activity#onCreate(),初始化DecorView,添加布局到DecorView的content if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } ··· if (!r.activity.mFinished) { //调用 Activity#onStart() activity.performStart(); ··· } ··· } ··· } catch (SuperNotCalledException e) { ··· } catch (Exception e) { ··· } return activity; } 复制代码
PhoneWindow 对象的建立时机在 activity 执行 attach()
方法里面,下面看看源码:app
Activity.java框架
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
···
//建立 PhoneWindow 对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
···
//为 PhoneWindow 设置 WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
···
//将 PhoneWindow 关联到 Activity
mWindowManager = mWindow.getWindowManager();
···
}
复制代码
接下来,ActivityThread 调用 Activity#onCreate()
方法,咱们知道 Activity 执行 onCreate()
方法会调用 setContentView()
,而 Activity 的 setContentView()
实际的实现来自 PhoneWindow,能够看下 getWindow()
方法返回的其实就是上面 attach()
方法中赋值的 mWindow 对象。ide
Activity.java函数
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } 复制代码
咱们继续看看 PhoneWindow 里面:oop
PhoneWindow.java布局
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { //初始化 DecorView installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { ··· } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { ··· } else { //添加布局到 DecorView 的 content mLayoutInflater.inflate(layoutResID, mContentParent); } ··· } private void installDecor() { ··· if (mDecor == null) { //建立 DecorView 对象 mDecor = generateDecor(-1); ··· } else { ··· } if (mContentParent == null) { //为 DecorView 的 ContentView 设置布局 mContentParent = generateLayout(mDecor); ··· } } 复制代码
上面的 mContentParent 就是 Activity 中 setContentView 中设置的 layout.xml 布局文件中的最外层父布局, DecroView 里面 ContentView 对应的布局部分。
handleResumeActivity()
方法里面执行了2个操做,调用 Activity#onResume()
,将 DecorView 添加到 WindowManager。
ActivityThread.java
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ··· //调用 Activity#onResume() r = performResumeActivity(token, clearHide, reason); if (r != null) { ··· if (r.window == null && !a.mFinished && willBeVisible) { ··· if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; //将 DecorView 添加到 WindowManager, //WindowManager 的实现类是 WindowManagerImpl, //因此实际调用的是 WindowManagerImpl 的 addView 方法 wm.addView(decor, l); } else { ··· } } } else if (!willBeVisible) { ··· } ··· } else { ··· } } 复制代码
WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
复制代码
performTraversals()
,从而实现视图的绘制。咱们看看 WindowManagerGlobal 的 addView()
方法。WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
···
synchronized (mLock) {
···
//建立 ViewRootImpl 对象
root = new ViewRootImpl(view.getContext(), display);
···
try {
//把 DecorView 加载到 Window 中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
···
}
}
}
复制代码
接着,咱们进入到 ViewRootImpl 的源码中,继续追踪:
ViewRootImpl.java
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(); ··· //将 ViewRootImpl 对象和 DecorView 创建关联 view.assignParent(this); ··· } } } @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { ··· scheduleTraversals(); } } void scheduleTraversals() { if (!mTraversalScheduled) { ··· mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ··· } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void doTraversal() { if (mTraversalScheduled) { ··· // performTraversals(); ··· } } 复制代码
整个 View 树的绘图流程是在 ViewRootImpl 类的 performTraversals()
方法开始的,它把控着整个绘制的流程。该函数作的执行过程主要是根据以前设置的状态,判断是否从新计算视图大小、是否从新放置视图的位置、以及是否重绘,从上到下遍历整个视图树,每一个 View 控件负责绘制本身,而 ViewGroup 还须要负责通知本身的子 View 进行绘制操做。
下面,咱们以 performTraversals()
为起点,来分析 View 的整个绘制流程。
ViewRootImpl.java
private void performTraversals() { final View host = mView; ··· int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ··· performLayout(lp, mWidth, mHeight); ··· performDraw(); ··· } 复制代码
咱们都知道,每个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()
、onLayout()
和 onDraw()
,特别是咱们进行自定义 View 的时候,能够明显地看出来,这三个方法分别对应到上面 ViewRootImpl 类中源码的三个 performXXX() 方法。下面咱们逐一来看看。
ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
···
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
···
}
}
复制代码
上面的 mView 正是以前分析过的 DecorView ,另外,咱们能够看获得 measure()
这个方法是 final 的,所以咱们没法在子类中去重写这个方法,说明 Android 是不容许咱们改变 View 的 measure 框架。
咱们来看 View 的 measure() 方法:
View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ··· if (forceLayout || needsLayout) { ··· int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); ··· } else { ··· } ··· } ··· } 复制代码
measure 阶段是最复杂的,在这里咱们删繁就简,省去 MeasureSpec 相关的分析。 前面说了,measure()
这个方法是 final 的,因此 View 子类只能经过重载 onMeasure()
来实现本身的测量逻辑。并且这里还会先判断是否知足从新绘制的条件才会进行实际的测量工做,即 forceLayout (表示强制从新布局,能够经过 View.requestLayout()
来实现)或者 needsLayout (表示本次传入的 MeasureSpec 与上次传入的不一样)为 true。
View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } 复制代码
这里简单总结一下:
- 测量该 View 以及它的内容来决定测量的宽度和高度。该方法被
measure(int,int)
调用,而且应该被子类重写来提供准确并且有效的对它们的内容的测量。- 当重写该方法时,您必须调用
setMeasuredDimension(int,int)
来存储该 view 测量出的宽和高。若是不这样作将会触发 IllegalStateException,由measure(int,int)
抛出。- 测量的基类实现默认为背景的尺寸,除非 MeasureSpec 容许使用更大的尺寸。子类应该重写
onMeasure(int,int)
方法来提供对内容更好的测量。- 若是该方法被重写,子类负责确保测量的高和宽至少是该 View 的 最小高度和最小宽度值。
- 对于非 ViewGroup 的 View 而言,经过调用上面默认的
onMeasure()
便可完成 View 的测量(固然你也能够重载onMeasure()
并调用setMeasuredDimension()
来设置任意大小的布局,这里就能够根据实际需求来决定,也就是说,若是你不想使用系统默认的测量方式,能够按照本身的意愿进行定制)。- 当经过
setMeasuredDimension()
方法最终设置完成 View 的 measure 以后 View 的 mMeasuredWidth 和 mMeasuredHeight 成员变量才会有具体的数值,在setMeasuredDimension()
方法调用以后,咱们才能使用getMeasuredWidth()
和getMeasuredHeight()
来获取视图测量出的宽高,以此以前调用这两个方法获得的值都会是 0。- 一个布局中通常都会包含多个子视图,每一个视图都须要经历一次 measure 过程。ViewGroup 中定义了一个
measureChildren()
、measureChild()
、measureChildWithMargins()
方法来去测量子视图的大小,三个方法最终都是调用子视图的measure()
方法。measureChildren()
内部实质上是循环调用measureChild()
,而measureChild()
和measureChildWithMargins()
的区别是在因而否把 margin 和 padding 也做为子视图的大小。(见下面源码)
ViewGroup.java
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); } } } 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); } 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 过程完成后,接下来就会进行 layout 阶段,即布局阶段。layout 的做用是根据前面测量的尺寸以及设置的其它属性值,共同来肯定 View 的位置。
ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ··· final View host = mView; ··· try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ··· if (numViewsRequestingLayout > 0) { ··· if (validLayoutRequesters != null) { ··· host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ··· } } } finally { ··· } ··· } 复制代码
上面的 host 就是 DecorView ,DecorView 继承了 FrameLayout ,咱们来看看 ViewGroup 的 layout()
方法:
ViewGroup.java
@Override public final void layout(int l, int t, int r, int b) { if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) { ··· super.layout(l, t, r, b); } else { ··· } } 复制代码
咱们能够看获得,跟 measure()
方法相似,ViewGroup 的 layout()
方法是被 final 修饰的,可见 Android 是不容许自定义的 ViewGroup 子类改变 ViewGroup 的 layout 框架的。这里面直接调用了 View 的 layout()
方法,咱们来看看 View 的 layout()
方法。
View.java
public void layout(int l, int t, int r, int b) { ··· 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); ··· } ··· } 复制代码
layout()
方法会调用setFrame()
方法,setFrame()
方法是真正执行布局任务的步骤,至于setOpticalFrame()
方法,其中也是调用setFrame()
方法,经过设置 View 的 mLeft、mTop、mRight 和 mBottom 四个参数来执行布局,对应描述了 View 相对其父 View 的位置。- 在
setFrame()
方法中会判断 View 的位置是否发生了改变,以肯定有没有必要对当前的视图进行重绘。- 而对子 View 的局部是经过
onLayout()
方法实现的,因为非 ViewGroup 视图不含子 View,因此 View 类的onLayout()
方法为空,正由于 layout 过程是父布局容器布局子 View 的过程,onLayout()
方法对叶子 View 没有意义,只有 ViewGroup 才有用。
接下来咱们看看 ViewGroup 的 onLayout()
方法:
ViewGroup.java
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
复制代码
能够看到,ViewGroup 中的 onLayout()
方法是一个抽象方法,这就意味着全部 ViewGroup 的子类(FrameLayout 、LinearLayout、RelativeLayout)都必须重写这个方法,而后在内部按照各自的规则对子视图进行布局。这里咱们以 DecorView 来分析一下,DecerView 继承自 FrameLayout ,咱们直接来看 FrameLayout 的 onLayout()
方法。
FrameLayout.java
@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(); ··· for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { ··· final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); ··· child.layout(childLeft, childTop, childLeft + width, childTop + height); } } } 复制代码
- 能够看获得,这里面也是对每个子视图调用
layout()
方法的。若是该子视图仍然是父布局,会继续递归下去;若是是叶子 view,则会走到 view 的onLayout()
空方法,该叶子view布局流程走完。- width 和 height 分别来源于measure阶段存储的测量值,若是这里经过其它渠道赋给 width 和 height 值,那么 measure 阶段的测量就显得没有意义了。
- 在
onLayout()
过程结束后,咱们就能够调用getWidth()
方法和getHeight()
方法来获取视图的宽高值。getWidth()
方法和getMeasureWidth()
方法的区别:getMeasureWidth()
方法在measure()
阶段结束后就能够获取到值,而getWidth()
方法要在layout()
阶段结束后才能获取到。另外,getMeasureWidth()
方法中的值是经过setMeasuredDimension()
方法来进行设置的,而getWidth()
方法中的值则是经过视图右边的坐标减去左边的坐标计算出来的。
在自定义 View 里面,若是在 onLayout()
方法中给子视图的 layout()
方法传入的四个参数是 0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight()
,那么 getWidth()
方法和 getMeasuredWidth()
获得的值就是相同的;若是传入的四个参数是别的自定义的值,那么 getWidth()
方法和 getMeasuredWidth()
获得的值就不会再相同(这里不建议这么操做)。
到这里,layout 阶段的大体流程咱们就分析完了,这个阶段主要就是根据上一阶段获得的 View 的测量宽高来肯定 View 的最终显示位置。
ViewRootImpl.java
private void performDraw() { ··· try { draw(fullRedrawNeeded); } finally { ··· } ··· } private void draw(boolean fullRedrawNeeded) { ··· if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { ··· } else { ··· if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } } ··· } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { ··· ··· try { ··· try { ··· mView.draw(canvas); ··· } finally { ··· } } finally { ··· } ··· } 复制代码
咱们跟踪代码,会发现,从 performDraw()
方法开始,最后会调用 mView.draw(canvas);
,而这个 mView 就是咱们以前分析过的 DecorView:
DecorView.java
@Override public void draw(Canvas canvas) { super.draw(canvas); if (mMenuBackground != null) { mMenuBackground.draw(canvas); } } 复制代码
这里,DecorView 直接调用了父类的 draw()
方法,后面继续绘制了菜单背景,咱们重点来看父类的方法,跟踪进去发现,直接就到了 View 类里面,说明 FrameLayout 和 ViewGroup 都没有重写 draw()
方法。
View.java
@CallSuper public void draw(Canvas canvas) { ··· /* * 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) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children 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 (debugDraw()) { debugDrawFocus(canvas); } // we're done... return; } ··· } 复制代码
到了 Draw 阶段,源码里面注释也比较清晰明了,这里总共执行了 7 步,其中第 2 步和第 5 步 一般来讲都是忽略不执行的,这里一样化繁就简,只分析其余 5 步。
Step 1. 绘制背景; Step 2. 忽略跳过; Step 3. 绘制内容; Step 4. 绘制子视图; Step 5. 忽略跳过; Step 6. 绘制装饰(前景,滚动条); Step 7. 绘制默认焦点高光。
这里咱们重点分析 Step 3 和 Step 4 。 Step 3 这里调用了咱们熟悉的 onDraw()
方法:
View.java
/** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { } 复制代码
这个方法里面什么都没作,可是注释讲得很清楚,重写该方法以完成你想要的绘制。由于每一个 View 的内容部分是各不相同的,因此须要由子类去实现具体逻辑。以 DecorView 为例,这里 ViewGroup 和 FrameLayout 都没有重写 onDraw()
方法,只有 DecorView 重写了该方法。DecorView 重写 onDraw()
在里面实现本身须要的绘制。
DecorView.java
@Override public void onDraw(Canvas c) { super.onDraw(c); // When we are resizing, we need the fallback background to cover the area where we have our // system bar background views as the navigation bar will be hidden during resizing. mBackgroundFallback.draw(isResizing() ? this : mContentRoot, mContentRoot, c, mWindow.mContentParent); } 复制代码
绘制完本身的需求,接着来到 Step 4 的 dispatchDraw()
:
View.java
/** * Called by draw to draw the child views. This may be overridden * by derived classes to gain control just before its children are drawn * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ protected void dispatchDraw(Canvas canvas) { } 复制代码
View 的 dispatchDraw()
方法是一个空方法,可是注释说明了若是 View 包含子类须要重写该方法,实际上对于叶子 View 来讲,该方法没有什么意义,由于它没有子 View 须要画了,而对于 ViewGroup 来讲,就须要重写该方法来画它的子 View。可是咱们能够发现,咱们熟悉的 RelativeLayout、LinearLayout、DecorView 之类的布局并无重写 dispatchDraw()
方法,那咱们就直接来看 ViewGroup 里面:
ViewGroup.java
@Override protected void dispatchDraw(Canvas canvas) { ··· for (int i = 0; i < childrenCount; i++) { ··· if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) { more |= drawChild(canvas, transientChild, drawingTime); } ··· 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 ··· more |= drawChild(canvas, transientChild, drawingTime); ··· } ··· // Draw any disappearing views that have animations if (mDisappearingChildren != null) { ··· for (int i = disappearingCount; i >= 0; i--) { ··· more |= drawChild(canvas, child, drawingTime); } } ··· } 复制代码
大致能够看得出来,这里面就是遍历子 View ,调用 drawChild()
,以绘制每一个子视图:
/** * Draw one child of this View Group. This method is responsible for getting * the canvas in the right state. This includes clipping, translating so * that the child's scrolled origin is at 0, 0, and applying any animation * transformations. * * @param canvas The canvas on which to draw the child * @param child Who to draw * @param drawingTime The time at which draw is occurring * @return True if an invalidate() was issued */ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } 复制代码
drawChild()
方法里面直接就只有调用子 View 的 draw()
方法,很是明了。一样的,若是该子 View 还有子视图,也会继续遍历下去调用 drawChild()
方法,继续绘制子 View,直到叶子 View 为止,这样不断递归下去,直到画完整棵 DecorView 树。
- 经过上面的分析,咱们知道,View 是不会帮咱们绘制内容部分的,所以须要每一个视图根据想要展现的内容来自行绘制。就是咱们自定义 View 过程当中重写
onDraw()
方法。- 若是该 View 是一个 ViewGroup,则须要递归绘制其所包含的全部子 View。
- 绘制的方式主要是借助 Canvas 这个类,它会做为参数传入到
onDraw()
方法中,供给每一个视图使用。Canvas 这个类的用法很是丰富,基本能够把它当成一块画布,在上面绘制任意的东西。- 在获取画布剪切区(每一个 View 的
draw()
方法中传入的 Canvas)时会自动处理掉padding,子 View 获取 Canvas 不用关注这些逻辑,只用关心如何绘制便可。- 默认状况下子 View 的
ViewGroup.drawChild()
绘制顺序和子 View 被添加的顺序一致,可是你也能够重载ViewGroup.getChildDrawingOrder()
方法提供不一样顺序。
onMeasure()
,onLayout()
,onDraw()
来完成要自定义的部分。