前几篇文章,码进讲述了measure,阶之w机layout流程等,深入接下来将详细分析绘制流程。理解
测量流程决定了View的制流制大小,布局流程决定了View的码进位置,那么绘制流程将决定View的阶之w机样子,一个View该显示什么由绘制流程完成;
那我们就开始开车了;
三大工作流程始于ViewRootImpl#performTraversals,理解在这个方法内部会分别调用performMeasure,performLayout,制流制performDraw三个方法来分别完成测量,码进布局,阶之w机绘制流程。深入那么我们现在先从performDraw方法看起;
里面又调用了ViewRootImpl#draw方法,理解我们来看看ViewRootImpl#draw:
private void draw(boolean fullRedrawNeeded) { ... //获取mDirty,制流制该值表示需要重绘的区域 final Rect dirty = mDirty; if (mSurfaceHolder != null) { // The app owns the surface, we wont draw. dirty.setEmpty(); if (animating) { if (mScroller != null) { mScroller.abortAnimation(); } disposeResizeBuffer(); } return; } //如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制 //第一次绘制流程,需要绘制所有视图 if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true; dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } //... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } }根据fullRedrawNeeded来判断是否需要重置dirty区域,最后调用了ViewRootImpl#drawSoftware方法,并把相关参数传递进去,包括dirty区域,我们接着看该方法的源码;
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas; try { final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; //锁定canvas区域,由dirty区域决定 canvas = mSurface.lockCanvas(dirty); // The dirty rectangle can be modified by Surface.lockCanvas() //noinspection ConstantConditions if (left != dirty.left || top != dirty.top || right != dirty.right || bottom != dirty.bottom) { attachInfo.mIgnoreDirtyState = true; } canvas.setDensity(mDensity); } try { if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } dirty.setEmpty(); mIsAnimating = false; attachInfo.mDrawingTime = SystemClock.uptimeMillis(); mView.mPrivateFlags |= View.PFLAG_DRAWN; try { canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; //正式开始绘制 mView.draw(canvas); } } return true; }实例化了Canvas对象,然后锁定该canvas的区域,由dirty区域决定,接着对canvas进行一系列的高防服务器属性赋值,最后调用了mView.draw(canvas)方法,
mView就是DecorView,也就是说从DecorView开始绘制;
由于ViewGroup没有重写draw方法,因此所有的View都是调用View#draw方法,因此,我们直接看它的源码
public void draw(Canvas canvas) { .... // 1. 绘制本身View背景 if (!dirtyOpaque) { drawBackground(canvas); } if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content // 2. 绘制内容,默认空实现 需复写 if (!dirtyOpaque) onDraw(canvas); // 3. 绘制 children dispatchDraw(canvas); drawAutofilledHighlight(canvas); // 4. 分发Draw (单一View空实现,ViewGroup见下面分析) if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // 5. 绘制装饰 (前景色,滚动条) onDrawForeground(canvas); return; } .... }可以看到,draw过程比较复杂,但是逻辑十分清晰。首先来看一开始的标记位dirtyOpaque,
该标记位的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,服务器租用比如绘制背景、绘制内容等;
绘制流程的五个步骤:
对View的背景进行绘制; 绘制View的内容; 对View的子View进行绘制(如果有子View); 分发Draw;绘制View的装饰(例如:前景色,滚动条);
这里调用了View#onDraw方法,View中该方法是一个空实现,因为不同的View有着不同的内容,这需要我们自己去实现,即在自定义View中重写该方法来实现;
当前的View是一个ViewGroup类型,站群服务器那么就需要绘制它的子View,这里调用了dispatchDraw,而View中该方法是空实现,实际是ViewGroup重写了这个方法,那么我们来看看;
@Override protected void dispatchDraw(Canvas canvas) { ... // 遍历子View final int childrenCount = mChildrenCount; ... 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) { 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) { // 调用 drawChild 方法,进行子元素绘制 more |= drawChild(canvas, child, drawingTime); } } .... }所谓的绘制装饰,就是指View除了背景、内容、子View的其余部分,例如滚动条等,我们看View#onDrawForeground
public void onDrawForeground(Canvas canvas) { //绘制指示器 onDrawScrollIndicators(canvas); //绘制滚动条 onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; if (foreground != null) { if (mForegroundInfo.mBoundsChanged) { mForegroundInfo.mBoundsChanged = false; final Rect selfBounds = mForegroundInfo.mSelfBounds; final Rect overlayBounds = mForegroundInfo.mOverlayBounds; if (mForegroundInfo.mInsidePadding) { selfBounds.set(0, 0, getWidth(), getHeight()); } else { selfBounds.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); } final int ld = getLayoutDirection(); Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); foreground.setBounds(overlayBounds); } //调用 Drawable 的 draw 方法,绘制前景色 foreground.draw(canvas); } }到目前为止,View的绘制流程也讲述完毕了;
其实绘制这块还是很重要的,下次还是要继续讲解下;
学如逆水行舟,不进则退;心似平原走马,易放难收;
一起加油老铁们
本文转载自微信公众号「Android开发编程」
【编辑推荐】
任何Ubuntu用户都应安装的四大Linux应用程序 工信部:5G手机终端连接数达4.19亿户 只需两步,教会你正确处理旧手机,变废为宝 MySQL的三条JOIN子句使用指南 苹果iOS 15再次迎来更新,除了实况文本外,还有五个新发现