Android视图加载流程(6)之View的详细绘制流程Draw
Android视图加载流程(5)之View的详细绘制流程Layout
上一篇文章我们对View的测量(Measure)进行讲解了。接着我们开始聊布局(Layout),以下是我们熟悉的performTraversals
方法。
private void performTraversals() { ...... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... //本文重点 canvas = mSurface.lockCanvas(dirty); mView.draw(canvas); ......}
我们看出ViewRootImpl创建一个canvas,然后mView(DecorView)调用draw方法并传入canvas,此方法执行具体的绘制工作。与Measure和Layout类似的需要递归绘制。
理解图 本文重点Draw
源码解读
Step1 View
由于ViewGroup没有重写View的draw方法,我们看下View的draw方法。
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 ...... if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) ...... // Step 2, save the canvas' layers ...... if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } ...... // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ...... if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } ...... // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); ......}
整个的绘制流程分成6步,通过注释可以知道第2步和第5步(skip step 2 & 5 if possible (common case))可以忽略跳过,我们对剩余4步就行分析。
Step2 View
第一步:对View的背景进行绘制
private void drawBackground(Canvas canvas) { //获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable final Drawable background = mBackground; ...... //根据layout过程确定的View位置来设置背景的绘制区域 if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; rebuildOutline(); } ...... //调用Drawable的draw()方法来完成背景的绘制工作 background.draw(canvas); ......}
draw方法通过调运drawBackground(canvas);方法实现了背景绘制。
Step3 View
第三步:对View的内容进行绘制
protected void onDraw(Canvas canvas) {}
ViewGroup没有重写该方法,view的方法也紧紧是一个空方法而已。大家都知道不同的View是显示不同的内容的,所以这块必须是子类去实现具体逻辑。
Step4.1 View
第四步:对当前View的所有子View进行绘制
protected void dispatchDraw(Canvas canvas) {}
View的dispatchDraw()方法是一个空方法。这个我们也比较好理解,本身View自身就没有所谓的子视图,而拥有子视图的就是ViewGroup!所以我们可以看下ViewGroup的dispatchDraw
Step4.2 ViewGroup
@Overrideprotected void dispatchDraw(Canvas canvas) { ...... final int childrenCount = mChildrenCount; final View[] children = mChildren; ...... for (int i = 0; i < childrenCount; i++) { ...... if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } ...... // Draw any disappearing views that have animations if (mDisappearingChildren != null) { ...... for (int i = disappearingCount; i >= 0; i--) { ...... more |= drawChild(canvas, child, drawingTime); } } ......} protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime);}
可见,Viewgroup重写了dispatchDraw()方法,该方法内部会遍历每个子View,然后调用drawChild()方法。而drawChild方法内部是直接由子视图调用draw()方法。
Step5 View
第六步:对View的滚动条进行绘制
protected final void onDrawScrollBars(Canvas canvas) { //绘制ScrollBars分析不是我们这篇的重点,所以暂时不做分析 ......}
可以看见其实任何一个View都是有(水平垂直)滚动条的,只是一般情况下没让它显示而已。
总结 Summary
通过以上几个步骤的分析,绘制(draw)的流程基本与measure和layout类似。通过循环调用draw来绘制各个子视图。
- 如果对象为view就不用遍历子视图,如果对象为viewGroup就要遍历子视图
- View默认不会绘制任何内容,子类必须重写onDraw方法
- View的绘制是借助onDraw方法传入的Canvas类来进行的
- 默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。
额外 extra
我们经常在自定义View的时候会遇到两种方法invalidate和postinvalidate。我们来看看两个方法与视图的绘制有什么样的联系呢?
invalidate方法源码分析
由于ViewGroup并没有重写该方法,所以我们直接看View的invalidate
View
//public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部Viewpublic void invalidate(Rect dirty) { final int scrollX = mScrollX; final int scrollY = mScrollY; //实质还是调运invalidateInternal方法 invalidateInternal(dirty.left - scrollX, dirty.top - scrollY, dirty.right - scrollX, dirty.bottom - scrollY, true, false);}//public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部Viewpublic void invalidate(int l, int t, int r, int b) { final int scrollX = mScrollX; final int scrollY = mScrollY; //实质还是调运invalidateInternal方法 invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);}//public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个Viewpublic void invalidate() { //invalidate的实质还是调运invalidateInternal方法 invalidate(true);}//default的权限,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个Viewvoid invalidate(boolean invalidateCache) { //实质还是调运invalidateInternal方法 invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);}//这是所有invalidate的终极调运方法!!!!!!void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { ...... // Propagate the damage rectangle to the parent view. final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; //设置刷新区域 damage.set(l, t, r, b); //传递调运Parent ViewGroup的invalidateChild方法 p.invalidateChild(this, damage); } ......}
View的invalidate方法最终调动invalidateInternal方法。而invalidateInternal方法是将要刷新的区域传递给父视图,并调用父视图的invalidateChild。
ViewGroup
public final void invalidateChild(View child, final Rect dirty) { ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; ...... do { ...... //循环层层上级调运,直到ViewRootImpl会返回null parent = parent.invalidateChildInParent(location, dirty); ...... } while (parent != null);}
这个过程不断的向上寻找父亲视图,当父视图为空时才停止。所以我们可以联想到根视图的ViewRootImpl
ViewRootImpl
@Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) { ...... //View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法 scheduleTraversals(); ...... return null;}
返回为空。刚好符合上面的循环!接着我们看scheduleTraversals()这个方法是不是感觉很熟悉呢?
这就是Android视图加载流程(3)之ViewRootImpl的UI刷新机制的Step4.2。ViewRootImpl正准备调用绘制View视图的代码。
ViewRootImpl
void scheduleTraversals() { mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);} //实现了Runnable接口final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); }} void doTraversal() { performTraversals();} private void performTraversals() { //测量 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //布局 mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); //绘制 mView.draw(canvas);}
一组看下来是不是觉得很清晰呢。View调用invalidate方法,其实是层层往上递,直到传递到ViewRootImpl后出发sceheduleTraversals方法,然后整个View树开始进行重绘制任务。
理解图:
postInvalidate方法源码分析
上面也说道invalidate方法只能在UI线程中执行,其他需要postInvalidate方法
View
public void postInvalidate() { postInvalidateDelayed(0);} public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; //核心,实质就是调运了ViewRootImpl.dispatchInvalidateDelayed方法 if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); }}
此方法必须是在视图已经绑定到Window才能使用,即attachInfo是否为空。随后调用ViewRootImpl的dispatchinvalidateDelayed
ViewRootImple
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds);}
Handler乱入!此时ViewrootImpl类的Handler发送了一条MSG_INVALIDATE消息。哪里接收这个消息呢?
ViewRootImple
final class ViewRootHandler extends Handler { public void handleMessage(Message msg) { ...... switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; ...... } ...... }}
实质上还是在UI线程中调用了View的invalidate()方法。
postInvalidate是在子线程中发消息,UI线程接收消息并刷新UI。
理解图:
常见的引起invalidate方法操作的原因主要有:
- 直接调用invalidate方法.请求重新draw,但只会绘制调用者本身。
- 触发setSelection方法。请求重新draw,但只会绘制调用者本身。
- 触发setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。
- 触发setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。
- 触发requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。
requestLayout方法源码分析
和invalidate类似,层层往上传递。
View
public void requestLayout() { ...... if (mParent != null && !mParent.isLayoutRequested()) { //由此向ViewParent请求布局 //从这个View开始向上一直requestLayout,最终到达ViewRootImpl的requestLayout mParent.requestLayout(); } ......}
获取父类对象,调用requestlayout(),最后到达ViewRootImpl
ViewRootImpl
@Overridepublic void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; //View调运requestLayout最终层层上传到ViewRootImpl后最终触发了该方法 scheduleTraversals(); }}
与invalidate是不是很像呢?requestLayout()会分别调用measure和layout过程,在layout过程的时候视图如果有位置变化,如长宽变化,此时会调用draw的过程重新绘制,如果视图没有位置变化,则不会调用draw的过程
至此一整块的视图加载流程结束了!
PS:本文
整理
自以下文章,若有发现问题请致邮 caoyanglee92@gmail.com工匠若水 Android应用层View绘制流程与源码分析
更多相关文章
- android 关于paint的设置
- DataBinding使用指南(四):BindingAdapter
- android studio 常用的快捷键
- android rk及allwinner方案bug解决方法集
- Android24_Service初步
- android——读写内部和外部存储方法
- 1、重写TextView的onDraw方法
- 如何在Android中调用webservice
- Android中Dialog去黑边的方法