Android高级进阶——View的工作原理Draw过程
Android高级进阶——View的工作原理Draw过程。
前两篇已经详细的介绍了 Measure 以及 Layout 过程,就剩下一个 Draw 绘制过程了,Draw 其实也不是很复杂,但是想要彻底掌握绘制的技巧就需要了解 Canvas 的使用了,后续会再开几篇详细介绍 Canvas 的具体使用
老规矩,还是先给出 ViewRootImpl#performTraversals 方法
ViewRootImpl#performTraversals 方法
private void performTraversals() { ... if (!mStopped || mReportNextDraw) { boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... if (didLayout) { performLayout(lp, mWidth, mHeight); ... if (!cancelDraw && !newSurface) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); ...
不废话,直接看 performDraw 方法
ViewRootImpl #performDraw 方法
private void performDraw() { ...省略部分代码 try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ...省略部分代码
ViewRootImpl#draw() 方法
private void draw(boolean fullRedrawNeeded) { ...省略部分代码 scrollToRectOrFocus(null, false); if (mAttachInfo.mViewScrollChanged) { mAttachInfo.mViewScrollChanged = false; mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); } ...省略部分代码 mAttachInfo.mTreeObserver.dispatchOnDraw(); ...省略部分代码 //根据是否开启了硬件加速,是否开启硬件加速,View 的绘制流程都是一样的,区别就是 Canvas 不同 if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { //开启了硬件加速,则执行该方法,内部最终还是会执行到view 的 draw 方法 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); } else { //未开启硬件加速,则执行该方法,直接调用 view 的 draw 方法 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } }
measure 和 layout 过程直接调用的就是 ViewRootImpl的performMeasure和performLayout方法,而 draw 过程调用的是 ViewRootImpl的performMeasure和performLayout方法,draw调用的是ViewRootImpl的performDraw()方法,再由performDraw中的draw(boolean fullRedrawNeeded)方法来调用ViewTreeObserver中的dispatchOnDraw()方法,进行通知所有挂在view树上的view开始draw,随后通过 drawSoftware 方法调用 view 的 draw 方法开始绘制
ViewTreeObserver.dispatchOnDraw()
public final void dispatchOnDraw() { if (mOnDrawListeners != null) { mInDispatchOnDraw = true; final ArrayList listeners = mOnDrawListeners; int numListeners = listeners.size(); for (int i = 0; i < numListeners; ++i) { listeners.get(i).onDraw(); } mInDispatchOnDraw = false; } }
ViewRootImpl#drawSoftware 方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { final Canvas canvas; canvas = mSurface.lockCanvas(dirty); //...省略部分代码 mView.draw(canvas); //...省略部分代码 }
最终终于执行到了 View 的 draw() 方法了,这个方法很重要,我们要显示的内容都是在这个方法中实现的,没有实现这个方法的逻辑,就是前面的 Measure 和 Layout 逻辑处理的在漂亮,也不能呈现。
View #draw(Canvas canvas)
public void draw(Canvas canvas) { 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 * //绘制 View 的内容 * 3. Draw view's content * //绘制子 View,子 View 的绘制也是按照这个流程进行 * 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 // Step 1, 绘制背景 int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // 通常情况下,会跳过第 2 步和第 5 步 // 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) { //绘制 View 的内容,需要子类具体实现,View 的 onDraw 是一个空实现,因为 View 并不是一个具体的 View ,不知道要绘制的内容,所以要绘制的内容留给具体的子类去具体实现 // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); //绘制内部包含的子 View,这个方法 View 也没有实现,具体的实现是在 ViewGroup 中,后面会具体分析该方法 // 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; } ...省略部分代码
View #drawBackground(Canvas)
该方法用于绘制 View 的背景,这个背景是我们在创建 View 时设定的
private void drawBackground(Canvas canvas) { final Drawable background = mBackground; if (background == null) { return; } mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop); ...省略硬件加速相关代码 //判断 View 是否设置了 scrollX 和 mScrollY,并平移滑动,绘制完背景后,在平移到原来的位置 final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } }
View 的 onDraw(Canvas) 方法
protected void onDraw(Canvas canvas) { }
onDraw 是用来绘制 View 的显示内容的,但是 View 并没有实现 onDraw ,具体的实现逻辑需要派生类去给根据自身情况去绘制具体的内容,可以参看 TextView 的 onDraw 方法
View #dispatchDraw
protected void dispatchDraw(Canvas canvas) { }
dispatchDraw( )方法用于通知子View自己绘制,View未实现该方法,由ViewGroup来实现该方法。我们来看一下吧;
ViewGroup#dispatchDraw(Canvas) 方法
protected void dispatchDraw(Canvas canvas) { 。。。(省略) int clipSaveCount = 0; final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { // 这里会对画布进行剪切,切掉Padding值 clipSaveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } 。。。(省略) // 遍历子View for (int i = 0; i < childrenCount; i++) { 。。。(动画相关操作 省略) int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { // 绘制子View drawChild(canvas, child, drawingTime); } } 。。。(省略) if (clipToPadding) { canvas.restoreToCount(clipSaveCount); } 。。。(省略) }
ViewGroup#drawChild 方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
这里调用的是 View 这个类的重载方法,来看一下
View draw(Canvas canvas,ViewGroup parent,long drawingTime) 方法
// 画布canvas的大小是 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { // 是否开启硬件加速标识 boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas; 。。。省略 int sx = 0; int sy = 0; if (!drawingWithRenderNode) { // 我们在自定义滑动控件时,一般会重写该方法,并设置mScrollX和mScrollY computeScroll(); sx = mScrollX; sy = mScrollY; } final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode; final boolean offsetForScroll = cache == null && !drawingWithRenderNode; int restoreTo = -1; restoreTo = canvas.save(); // 根据mScrollX和mScrollY移动画布的坐标系 canvas.translate(mLeft - sx, mTop - sy); 。。。(设置画布透明度的操作 省略) if (!drawingWithRenderNode) { // apply clips directly, since RenderNode won't do it for this draw if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) { canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight()); } } if (!drawingWithDrawingCache) { if (drawingWithRenderNode) { // 开启硬件加速时走该分支 ((DisplayListCanvas) canvas).drawRenderNode(renderNode); } else { // 调用View.draw()进行绘制 draw(canvas); } } if (restoreTo >= 0) { canvas.restoreToCount(restoreTo); } 。。。省略 return more; }
到这里关于 View 的三个工作流程就介绍完了,后面会详细的介绍一下 View 的绘制技巧 —— Canvas 的使用。
上一篇: 七类蛛蛛陷坑 网站优化中必须要躲避