基础/背景知识
如何理解Drawable?
在 Android 中,Drawable 是一个抽象的概念,表示可以绘制到屏幕上的内容。
它可以是位图图像、矢量图形、形状、颜色等。
Drawable 本身并不是一个 View,它不能直接添加到布局中,而是作为 View 的一部分进行绘制,例如作为 View 的背景、图标或前景。
Drawable 的主要作用是提供一种与 View 分离的图形绘制机制,这样可以将图形资源与 View 的逻辑代码分离,提高代码的可维护性和可重用性。
例如, 您可以将一个 Drawable 设置为多个 View 的背景,而无需为每个 View 都编写相同的绘制代码。
Drawable 的类型:Android 提供了多种类型的 Drawable,例如: BitmapDrawable: 用于显示位图图像,例如 PNG、JPG 或 GIF 文件。
ShapeDrawable: 用于绘制各种形状,例如矩形、圆形、椭圆形等。
LayerDrawable: 用于将多个 Drawable 堆叠在一起,形成一个复合 Drawable。
StateListDrawable: 用于根据 View 的状态显示不同的 Drawable,例如按钮的按下状态和正常状态。
VectorDrawable: 用于显示矢量图形,例如 SVG 文件。
Drawable 的使用方法:可以通过以下方式使用 Drawable:
在 XML 中定义: 将 Drawable 资源文件放在 res/drawable 目录下,然后在 XML 布局文件中引用它。
在代码中创建: 使用 Drawable 类的构造函数或静态方法创建 Drawable 对象。
从资源文件中加载:使用 getResources().getDrawable() 方法从资源文件中加载 Drawable 对象。
Drawable 的 draw() 方法如何将内容绘制到屏幕上?Drawable 的 draw() 方法是将 Drawable 内容绘制到 Canvas 上的关键方法。当 View 需要绘制 Drawable 时,它会调用 Drawable 的 draw() 方法,并将 Canvas 对象作为参数传递给它。Drawable 的 draw() 方法会根据 Drawable 的类型和属性,将内容绘制到 Canvas 上。
onDraw(Canvas canvas)简单理解
调用drawable的draw方法完成具体的绘制;
是最简单的绘制方法,是学习自定义控件的基本方法。canvas参数提供了绘制的画布,可以重写这个方法,来实现我们的绘制逻辑,比如绘制直线,绘制矩形,绘制图片
onDraw的调用时机:屏幕刷新
先找个调用栈,我们跟着看,先从调用链路最下面的android.view.Choreographer$FrameHandler.handleMessage开始;
"main@21848" prio=5 tid=0x2 nid=NA runnablejava.lang.Thread.State: RUNNABLEat android.widget.TextView.onDraw(TextView.java:8302)at android.view.View.draw(View.java:23444)at android.view.View.updateDisplayListIfDirty(View.java:22307)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:703)at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:709)at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:807)at android.view.ViewRootImpl.draw(ViewRootImpl.java:4823)at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4525)at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3718)at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2437)at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9398)at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1693)at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1701)at android.view.Choreographer.doCallbacks(Choreographer.java:1288)at android.view.Choreographer.doFrame(Choreographer.java:1195)at android.view.Choreographer$FrameHandler.handleMessage(Choreographer.java:1593)at android.os.Handler.dispatchMessage(Handler.java:106)at android.os.Looper.loopOnce(Looper.java:237)at android.os.Looper.loop(Looper.java:339)at android.app.ActivityThread.main(ActivityThread.java:8750)at java.lang.reflect.Method.invoke(Method.java:-1)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:951)
屏幕硬件刷新-> 通知应用刷新,通过Vsync信号,源码中从何处调用通知的呢?进而调用到onDraw进行界面绘制
Choreographer
Choreographer 是 Android UI 系统中一个重要的组件,它负责协调动画、输入和绘制事件的发生时间, 以确保 UI 的流畅性和响应性。它采用了单例模式,并通过与 VSYNC 信号同步、安排回调函数和管理帧绘制来实现其功能。
其路径是:
frameworks/base/core/java/android/view/Choreographer.java
这里的屏幕刷新也与他有关
Choreographer中的frameDisplayEventReceiver接受vsync回调,发送消息给this。执行自己runable的run方法
doFrame逻辑:
逻辑比较多,调用到doCallbacks中的 TRAVERSAL逻辑
在Choreographer里面,总共4种callback,分别是Choreographer.CALLBACK_INPUT,Choreographer.CALLBACK_INSETS_ANIMATION,Choreographer.CALLBACK_TRAVERSAL,Choreographer.CALLBACK_COMMIT。这4种回调是按照优先级先后进行调用,所有在一个次屏幕刷新中,最先处理的是Input回调,然后是Animation回调,然后是Traversal回调,这个Traversal回调指的就是进行UI元素遍历更新。类型的回调是在ViewRootImpl中注册的。
doCallbacks中会先拿到全部的callbacks,按照类型进行处理,callbacks的add过程在viewrootimpl中。
一次调用每个callback的run方法。调用到CallbackRecord中的run方法,先调用下面的,再调用上面的FRAME_CALLBACK_TOKEN调用action的都Frame;
根据堆栈最终是调用到了viewrootimpl中的
这里的调用只有一个地方
打印了一下堆栈,点击一个按钮对应的效果,这里可以看到
基本符合直观的逻辑,只不过对于down事件和up事件链路不太一样,不过只要发生view变化,都会触发到这里;
"main@21864" prio=5 tid=0x2 nid=NA runnablejava.lang.Thread.State: RUNNABLEat android.view.ViewRootImpl.scheduleTraversals(ViewRootImpl.java:2411)at android.view.ViewRootImpl.invalidate(ViewRootImpl.java:2065)at android.view.ViewRootImpl.onDescendantInvalidated(ViewRootImpl.java:2058)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.invalidateChild(ViewGroup.java:6113)at android.view.View.invalidateInternal(View.java:19458)at android.view.View.invalidate(View.java:19388)at android.view.View.invalidateDrawable(View.java:24190)at android.widget.TextView.invalidateDrawable(TextView.java:8137)at android.graphics.drawable.Drawable.invalidateSelf(Drawable.java:473)at android.graphics.drawable.RippleDrawable.invalidateSelf(RippleDrawable.java:1024)at android.graphics.drawable.RippleDrawable.startPatternedAnimation(RippleDrawable.java:839)at android.graphics.drawable.RippleDrawable.setRippleActive(RippleDrawable.java:356)at android.graphics.drawable.RippleDrawable.onStateChange(RippleDrawable.java:339)at android.graphics.drawable.Drawable.setState(Drawable.java:836)at android.view.View.drawableStateChanged(View.java:24367)at android.widget.TextView.drawableStateChanged(TextView.java:6218)at androidx.appcompat.widget.AppCompatButton.drawableStateChanged(AppCompatButton.java:170)at android.view.View.refreshDrawableState(View.java:24449)at com.google.android.material.button.MaterialButton.refreshDrawableState(MaterialButton.java:494)at android.view.View.setPressed(View.java:13077)at com.google.android.material.button.MaterialButton.setPressed(MaterialButton.java:1306)at android.view.View.setPressed(View.java:13055)at android.view.View.onTouchEvent(View.java:16634)at android.widget.TextView.onTouchEvent(TextView.java:11544)at android.view.View.dispatchTouchEvent(View.java:15134)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:553)at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1912)at android.app.Activity.dispatchTouchEvent(Activity.java:4277)at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:70)at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:511)at android.view.View.dispatchPointerEvent(View.java:15393)at android.view.ExtViewRootImplImpl.onDispatchPointerEvent(ExtViewRootImplImpl.java:62)at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6870)at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6667)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6118)at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6175)at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6141)at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6306)at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6149)at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:6363)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6122)at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6175)at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6141)at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6149)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6122)at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:9294)at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:9235)at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:9194)at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:9430)at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:319)at android.os.MessageQueue.nativePollOnce(MessageQueue.java:-1)at android.os.MessageQueue.next(MessageQueue.java:335)at android.os.Looper.loopOnce(Looper.java:190)at android.os.Looper.loop(Looper.java:339)at android.app.ActivityThread.main(ActivityThread.java:8750)at java.lang.reflect.Method.invoke(Method.java:-1)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:951)
"main@21869" prio=5 tid=0x2 nid=NA runnablejava.lang.Thread.State: RUNNABLEat android.view.ViewRootImpl.scheduleTraversals(ViewRootImpl.java:2409)at android.view.ViewRootImpl.invalidate(ViewRootImpl.java:2065)at android.view.ViewRootImpl.onDescendantInvalidated(ViewRootImpl.java:2058)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.invalidateChild(ViewGroup.java:6113)at android.view.View.invalidateInternal(View.java:19458)at android.view.View.invalidate(View.java:19388)at android.view.View.invalidateDrawable(View.java:24190)at android.widget.TextView.invalidateDrawable(TextView.java:8137)at android.graphics.drawable.Drawable.invalidateSelf(Drawable.java:473)at android.graphics.drawable.RippleDrawable.invalidateSelf(RippleDrawable.java:1024)at android.graphics.drawable.RippleDrawable.exitPatternedAnimation(RippleDrawable.java:844)at android.graphics.drawable.RippleDrawable.setRippleActive(RippleDrawable.java:358)at android.graphics.drawable.RippleDrawable.onStateChange(RippleDrawable.java:339)at android.graphics.drawable.Drawable.setState(Drawable.java:836)at android.view.View.drawableStateChanged(View.java:24367)at android.widget.TextView.drawableStateChanged(TextView.java:6218)at androidx.appcompat.widget.AppCompatButton.drawableStateChanged(AppCompatButton.java:170)at android.view.View.refreshDrawableState(View.java:24449)at com.google.android.material.button.MaterialButton.refreshDrawableState(MaterialButton.java:494)at android.view.View.setPressed(View.java:13077)at com.google.android.material.button.MaterialButton.setPressed(MaterialButton.java:1306)at android.view.View$UnsetPressedState.run(View.java:29921)at android.os.Handler.handleCallback(Handler.java:942)at android.os.Handler.dispatchMessage(Handler.java:99)at android.os.Looper.loopOnce(Looper.java:237)at android.os.Looper.loop(Looper.java:339)at android.app.ActivityThread.main(ActivityThread.java:8750)at java.lang.reflect.Method.invoke(Method.java:-1)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:951)
接下来就是看最核心的
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:807)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:4823)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4525)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3718)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2437)
ViewRootImpl.performTraversals
doTraversal->performTraversals 是非常核心的逻辑
void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}}
在performTraversals中,逻辑特别多,这里只针对draw相关的去看
之前在实际工作中看到过这里其实和窗口、touch等等都有相关逻辑的处理。
比如
- performMeasure和performLayout这种对view的布局等等,会调用应用自己的onMeasure和onlayout等等;
- relayoutWindow和windowmanagerservice通信,进行窗口的添加等等;
下面开始看重点 performDraw执行绘制
performTraversals->performDraw
在performTraversals中performDraw的调用。
performDraw->draw
draw函数看起来就没那么长了,看一下看起来比较重要的逻辑
- 检查surface + trackFps
看起来surface在之前已经ready了;
- 一大堆其他逻辑,先不看(有个dirty的逻辑,看起来挺重要的)
- 判断硬件绘制 + 一堆东西 + draw
这里面draw的一行
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
if (isHardwareEnabled()) {// If accessibility focus moved, always invalidate the root.boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;mInvalidateRootRequested = false;// Draw with hardware renderer.mIsAnimating = false;if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {mHardwareYOffset = yOffset;mHardwareXOffset = xOffset;invalidateRoot = true;}if (invalidateRoot) {mAttachInfo.mThreadedRenderer.invalidateRoot();}dirty.setEmpty();// Stage the content drawn size now. It will be transferred to the renderer// shortly before the draw commands get send to the renderer.final boolean updated = updateContentDrawBounds();if (mReportNextDraw) {// report next draw overrides setStopped()// This value is re-sync'd to the value of mStopped// in the handling of mReportNextDraw post-draw.mAttachInfo.mThreadedRenderer.setStopped(false);}if (updated) {requestDrawWindow();}useAsyncReport = true;if (mHdrRenderState.updateForFrame(mAttachInfo.mDrawingTime)) {final float renderRatio = mHdrRenderState.getRenderHdrSdrRatio();applyTransactionOnDraw(mTransaction.setExtendedRangeBrightness(getSurfaceControl(), renderRatio,mHdrRenderState.getDesiredHdrSdrRatio()));mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(renderRatio);}if (activeSyncGroup != null) {registerCallbacksForSync(syncBuffer, activeSyncGroup);if (syncBuffer) {mAttachInfo.mThreadedRenderer.forceDrawNextFrame();}} else if (mHasPendingTransactions) {// Register a callback if there's no sync involved but there were calls to// applyTransactionOnDraw. If there is a sync involved, the sync callback will// handle merging the pending transaction.registerCallbackForPendingTransactions();}long timeNs = SystemClock.uptimeNanos();mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);// Only trigger once per {@link ViewRootImpl} instance.if (mAppStartInfoTimestampsFlagValue && mRenderThreadDrawStartTimeNs == -1) {mRenderThreadDrawStartTimeNs = timeNs;}
后面判断软件绘制 + draw(不是重点,先不看了)
VRI.draw-> ThreadRenderer.draw
- 核心逻辑:updateRootDisplayList + updateDisplayListIfDirty
调用updateViewTreeDisplayList对UI树上的所有元素调用view.updateDisplayListIfDirty
mRootNode.beginRecording生成一个RecordingCanvas,然后调用drawRenderNode
updateViewTreeDisplayList这个函数更新了当前viewtree的 displayList,其实里面没有很多其他逻辑 ,只是搞了一堆flag,用来判断是否需要update,如果需要后面会在dirty中更新
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");updateViewTreeDisplayList(view);
...if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);try {final int saveCount = canvas.save();canvas.translate(mInsetLeft, mInsetTop);callbacks.onPreDraw(canvas);canvas.enableZ();canvas.drawRenderNode(view.updateDisplayListIfDirty());canvas.disableZ();callbacks.onPostDraw(canvas);canvas.restoreToCount(saveCount);mRootNodeNeedsUpdate = false;} finally {mRootNode.endRecording();}}Trace.traceEnd(Trace.TRACE_TAG_VIEW);}
updateViewTreeDisplayList
中通过View.PFLAG_DRAWN设置为已经绘制
如果这个View调过invlidate,则需要重新创建displayList,
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED,因为计算出了mRecreateDisplayList,重置这个flag
mRecreateDisplayList为false则后面这个view的 displaylist不需要重新生成了;
private void updateViewTreeDisplayList(View view) {view.mPrivateFlags |= View.PFLAG_DRAWN;view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)== View.PFLAG_INVALIDATED;view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;view.updateDisplayListIfDirty();view.mRecreateDisplayList = false;}
updateDisplayListIfDirty()
对于dirty的view则进行更新这个displayList
/*** Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)* @hide*/@NonNull@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)public RenderNode updateDisplayListIfDirty() {final RenderNode renderNode = mRenderNode;
硬件加速:if (!canHaveDisplayList()) {// can't populate RenderNode, don't tryreturn renderNode;}
缓存无效 || 没有过displayList || 需要重新生成displayListif ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| !renderNode.hasDisplayList()|| (mRecreateDisplayList)) {// Don't need to recreate the display list, just need to tell our// children to restore/recreate theirs第一种情况:不需要重新生成displayList(第一次不进入)第二种情况:不是第一次,进入这里的时候cache失效为0,但是有list,但是不需要重新生成的情况;这种情况是不需要绘制自身,需要但是需要调用dispatchGetDisplayList去遍历child,为变化的child重新生成list,这是由于viewgroup某个child属性变化,导致父容器进行invalidate,此时只需要这个child重新执行ondraw;if (renderNode.hasDisplayList()&& !mRecreateDisplayList) {mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;mPrivateFlags &= ~PFLAG_DIRTY_MASK;dispatchGetDisplayList();return renderNode; // no work needed}// If we got here, we're recreating it. Mark it as such to ensure that// we copy in child display lists into ours in drawChild()mRecreateDisplayList = true;
...重新创建一个RecordingCanvasfinal RecordingCanvas canvas = renderNode.beginRecording(width, height);if (layerType == LAYER_TYPE_SOFTWARE) {buildDrawingCache(true);Bitmap cache = getDrawingCache(true);if (cache != null) {canvas.drawBitmap(cache, 0, 0, mLayerPaint);}} else { 硬件绘制// Fast path for layouts with no backgrounds是否需要绘制自身(一般view group不需要绘制自己,则直接dispatch,调用viewGroup自己实现的dispatchDraw逻辑。)if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {dispatchDraw(canvas);drawAutofilledHighlight(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().draw(canvas);}if (isShowingLayoutBounds()) {debugDrawFocus(canvas);}} else {调用view的draw逻辑draw(canvas);}}} finally {结束绘制renderNode.endRecording();setDisplayListProperties(renderNode);}........return renderNode;}
上面提到的第二种情况:
第一种情况:不需要重新生成displayList(第一次不进入)
第二种情况:不是第一次,进入这里的时候cache失效为0,但是有list,但是不需要重新生成的情况;这种情况是不需要绘制自身,需要但是需要调用dispatchGetDisplayList去遍历child,为变化的child重新生成list,这是由于viewgroup某个child属性变化,导致父容器进行invalidate,此时只需要这个child重新执行ondraw;
ViewGroup.dispatchGetDisplayList()
/*** This method is used to cause children of this ViewGroup to restore or recreate their* display lists. It is called by getDisplayList() when the parent ViewGroup does not need* to recreate its own display list, which would happen if it went through the normal* draw/dispatchDraw mechanisms.** @hide*/
此方法用于使此 ViewGroup 的子级恢复或重新创建其显示列表。
当父级 ViewGroup 不需要重新创建其自己的显示列表时,
getDisplayList() 会调用此方法(
如果父级 ViewGroup 已通过正常的 draw/dispatchDraw 机制,则会发生这种情况)。@Override@UnsupportedAppUsageprotected void dispatchGetDisplayList() {final int count = mChildrenCount;final View[] children = mChildren;for (int i = 0; i < count; i++) {final View child = children[i];if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {recreateChildDisplayList(child);}}final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();for (int i = 0; i < transientCount; ++i) {View child = mTransientViews.get(i);if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {recreateChildDisplayList(child);}}if (mOverlay != null) {View overlayView = mOverlay.getOverlayView();recreateChildDisplayList(overlayView);}if (mDisappearingChildren != null) {final ArrayList<View> disappearingChildren = mDisappearingChildren;final int disappearingCount = disappearingChildren.size();for (int i = 0; i < disappearingCount; ++i) {final View child = disappearingChildren.get(i);recreateChildDisplayList(child);}}}
view.draw()
这里的注释相对清晰
- 绘制遍历执行几个绘制步骤,这些步骤必须按照适当的顺序执行:
-
- 绘制背景
-
- 如有必要,保存画布的图层以准备淡入淡出
-
- 绘制视图的内容
-
- 绘制子项
-
- 如有必要,绘制淡入淡出边缘并恢复图层
-
- 绘制装饰(例如滚动条)
-
- 如有必要,绘制默认焦点突出显示
如果这个View需要重新绘制
layer是LAYER_TYPE_SOFTWARE,则使用一个普通的基于Bitmap的Canvas来传给View来绘制
硬件使用RenderNode.beginRecordings生成的RecordingCavas来绘制。
绘制的时候,存在很多步骤,会根据实际情况,先调用ondraw再dispatchDraw,对于viewGroup来说,调用dispatch就会调用到子view中,子view中则dispatch为空,则直接调用到了onDraw;
public void draw(@NonNull Canvas canvas) {final int privateFlags = mPrivateFlags;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)* 7. If necessary, draw the default focus highlight*/// Step 1, draw the background, if neededint 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 contentonDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);drawAutofilledHighlight(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(canvas);if (isShowingLayoutBounds()) {debugDrawFocus(canvas);}// we're done...return;}
阶段总结
Vsync接收->choreographer处理->ViewRootImpl.performTraversal->draw->ThreadRenderer.draw开始生产新帧内容
ViewRootImpl通过finishDrawing完成通知wms
ViewGroup的drawChild 和 View的updateDisplayListDirty 都使用了缓存来优化,当一个View没有发生变化时,直接使用上一次绘制的内容绘制到父容器中去