读前补充
关于标记位在Android运用的是非常的多,简单的说就是使用二进制中的一位表示一个状态,下面简单的举个栗子。
private int flag = 0;private static final int NEED_DRAW = 0x1; //0001; //表示需要绘制private static final int HAS_ANIMATION = 0x2; //0010;//表示有动画private static final int HAS_BACKGROUND = 0x4 //0100;//有背景色private static final int HAS_FORGROUND = 0x8 //1000;//有前景色void action(){ if(flag & NEED_DRAW == NEED_DRAW){ draw(); } }void draw(){ if(flag & HAS_FORGROUND == HAS_FORGROUND){ drawForground(); } ... }void clearDrawFlag(){ flag &= ~NEED_DRAW; }void setDrawFlag(){ flag |= NEED_DRAW; }
上面的思维在View
中使用的比较的多,我们只要记住flag & HAS_FORGROUND == HAS_FORGROUND
就表示flag
存在HAS_FORGROUND
标记位,另外,也可以一次判断多个标记的存在情况。
if(flag & (HAS_FORGROUND | HAS_BACKGROUND) == (HAS_FORGROUND | HAS_BACKGROUND)){ //前景和背景同时存在 }
View Animation绘制的流程
在View中,我们知道View
的绘制过程是从函数draw(canvas)
开始,下面我们来分析一下该函数,根据它的注释部分,我们很容易得到简化版的逻辑代码。
//View.java@CallSuperpublic void draw(Canvas canvas) { // Step 1, draw the background, if needed drawBackground(canvas); // skip step 2 & 5 if possible (common case) // Step 3, draw the content 非透明才绘制 if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); }
我们知道,View树的顶层是DecorView
,它是FrameLayout
的子类,绘制是从根节点开始(RootViewImpl
中的dispatchDraw()
),根据上面的代码会分发绘制到子View
,有子View
一定是ViewGroup
的子类,所以看看ViewGroup
的dispatchDraw(canvas)
,View
的该函数是个空实现,因为根本就不需要分发绘制。
//ViewGroup.java@Overrideprotected void dispatchDraw(Canvas canvas) { //balabala 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; } } 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) { more |= drawChild(canvas, child, drawingTime); } } //balabala}protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
这里代码好多,选择性的看吧,反正逻辑就是调用了函数drawChild()
,然而 drawChild()
就是直接调用child
的draw(Canvas canvas, ViewGroup parent, long drawingTime)
函数,这里就是把绘制分发到子View
,使得整个View Tree
得以绘制。OK,我们接着看到底是怎么绘制的。
//View.javaboolean draw(Canvas canvas, ViewGroup parent, long drawingTime){ boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas; final Animation a = getAnimation(); if (a != null) { //如果有动画就在这个函数内部请求重绘,return True if the animation is still running more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired); concatMatrix = a.willChangeTransformationMatrix(); if (concatMatrix) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; } transformToApply = parent.getChildTransformation(); } if(drawingWithRenderNode){ renderNode = updateDisplayListIfDirty(); //其内部调用draw(canvas) } return more; }private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, Animation a, boolean scalingRequired) { //t为根据动画产生的Transformation{mAlpha,mMatrix},可以控制基本的动画 boolean more = a.getTransformation(drawingTime, t, 1f); if (more) { final RectF region = parent.mInvalidateRegion; a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region, invalidationTransform); // The child need to draw an animation, potentially offscreen, so // make sure we do not cancel invalidate requests // //这里设置了一个表示该View含有动画的标记位,在后面会使用这个标记位 parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; final int left = mLeft + (int) region.left; final int top = mTop + (int) region.top; //这里请求重绘,根据动画改变的子View的可绘制区域的位置。 //我们知道View Animation 是没有改变原本View的属性,包括宽高位置。 parent.invalidate(left, top, left + (int) (region.width() + .5f), top + (int) (region.height() + .5f)); } //balabala}public void invalidate(int l, int t, int r, int b) { final int scrollX = mScrollX; final int scrollY = mScrollY; invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false); }public RenderNode updateDisplayListIfDirty() { //.... if(condition){ .... draw(canvas); } //....}
这里调用invalidateInternal()
函数就是请求重绘,具体怎么重绘,我们在下面会讲。
上面的过程是View
绘制的大体流程,下面看看设置动画的情况。我们设置View动画的入口函数一般都是startAnimation()
,下面我们从这里入手。
//View.javapublic void startAnimation(Animation animation) { animation.setStartTime(Animation.START_ON_FIRST_FRAME); setAnimation(animation); invalidateParentCaches(); invalidate(true); }
invalidate(true)
函数的主要作用是请求View树进行重绘,但是具体是怎么绘制的呢,我们接着往下看。
//View.javavoid invalidate(boolean invalidateCache) { //这里的mLeft,mRight都是相当于父View来讲的。所以这里的参数就是当前View所在的区域。 invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); }void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { //不需要绘制的情况(View不可见&&没有动画等) if (skipInvalidate()) { return; } //... if(condition/*满足需要绘制的条件*/){ 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); p.invalidateChild(this, damage); } } //....}
OK,在上面小节提到的函数在这里出现了,我们看看究竟是怎么一回事。上面函数大多是设置标记位和判断标记位,具体什么意思还不是很清楚,我们重点看下那一小段,这里的mAttachInfo
是在View第一次attach到Window时,ViewRoot传给自己的子View的。这个AttachInfo之后,会顺着布局体系一直传递到最底层的View,下面看看ViewParent
到底做了什么?
//ViewGroup.javapublic final void invalidateChild(View child, final Rect dirty) { ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) == PFLAG_DRAW_ANIMATION; //applyLegacyAnimation()中有设置这个标记位 final int[] location = attachInfo.mInvalidateChildLocation; location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; //localtion存放的子view的右上角的坐标 ... do { View view = null; if (parent instanceof View) { view = (View) parent; } //这里意味着如果子View有动画,那么父View也要设置上动画标记位,一直到顶层ViewRootImpl if (drawAnimation) { if (view != null) { view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; } else if (parent instanceof ViewRootImpl) { ((ViewRootImpl) parent).mIsAnimating = true; } } // If the parent is dirty opaque or not dirty, mark it dirty with the opaque // flag coming from the child that initiated the invalidate if (view != null) { if ((view.mViewFlags & FADING_EDGE_MASK) != 0 && view.getSolidColor() == 0) { opaqueFlag = PFLAG_DIRTY; } if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) { view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } } parent = parent.invalidateChildInParent(location, dirty); if (view != null) { // Account for transform on current parent Matrix m = view.getMatrix(); if (!m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); dirty.set((int) (boundingRect.left - 0.5f), (int) (boundingRect.top - 0.5f), (int) (boundingRect.right + 0.5f), (int) (boundingRect.bottom + 0.5f)); } } } while (parent != null); } }
这里调用了函数invalidateChildInParent()
,需要注意的是这里这个函数的实现有两个,一个是ViewGroup
中,而另一个是ViewRootImpl
。
//ViewGroup.java//总体来讲,这里就是修改一些参数,使其满足当前的ViewGroup,比如坐标等。public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN || (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) { if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != FLAG_OPTIMIZE_INVALIDATE) { dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, location[CHILD_TOP_INDEX] - mScrollY); if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } final int left = mLeft; final int top = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) { dirty.setEmpty(); } } mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; } return mParent; } else { mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID; location[CHILD_LEFT_INDEX] = mLeft; location[CHILD_TOP_INDEX] = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { dirty.set(0, 0, mRight - mLeft, mBottom - mTop); } else { // in case the dirty rect extends outside the bounds of this container dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; } return mParent; } } return null; }
而在ViewRootImpl
中就是请求整个View Tree进行重绘,具体的代码如下。
@Overridepublic void invalidateChild(View child, Rect dirty) { invalidateChildInParent(null, dirty); }@Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); //UI线程才可以操作UI ... invalidateRectOnScreen(dirty); return null; }private void invalidateRectOnScreen(Rect dirty) { ... if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); } }
根据上篇对Window/WindowManager和WindowManagerSystem的理解,我们通过分析ViewRootImpl
的绘制的时候分析过,函数scheduleTraversals()
的逻辑其实是执行一个Runnable
,而这个Runnable
其实就是去执行函数doTraversal()
,而函数doTraversal()
会调用performTraversals()
,到这里我们发现它开始重绘了。总体来说就是动画的执行会导致整个View Tree
重绘,但是Android内部有一些优化,比如一张图片做移动,我们不需要真正的去重新绘制,Android内部提供缓存机制,不会显示的再调用onDraw(canvas)
函数。
到这里,动画的执行逻辑大体清楚了。
QA
ViewGroup及其子类onDraw(canvas)没有执行
ViewGroup
没有背景时默认是不会执行onDraw(canvas)
方法的,具体的原因我们在下面分析。
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initViewGroup(); initFromAttributes(context, attrs, defStyleAttr, defStyleRes); }private void initViewGroup() { // ViewGroup doesn't draw by default if (!debugDraw()) { setFlags(WILL_NOT_DRAW, DRAW_MASK); //设置相关的标记位 } .... }//#View.java//可以看见在View里也包含这个设置//willNotDraw = true ,不绘画public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }//View.java//WILL_NOT_DRAW = 0x00000080;//DRAW_MASK = 0x00000080;void setFlags(int flags, int mask) { .... int old = mViewFlags; mViewFlags = (mViewFlags & ~mask) | (flags & mask);//(flags & mask)= 0x00000080 int changed = mViewFlags ^ old; .... if ((changed & DRAW_MASK) != 0) { if ((mViewFlags & WILL_NOT_DRAW) != 0) { if (mBackground != null || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) { //需要绘制时的标记位,通过这里我们知道,有背景时设置是无效的,这里很容易验证 mPrivateFlags &= ~PFLAG_SKIP_DRAW; } else { mPrivateFlags |= PFLAG_SKIP_DRAW; //不要绘制,添加标记位 } } else { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } requestLayout(); invalidate(true); } ... }//View.java//这个函数上面有分析,在boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)//中调用,使用这个函数来绘制View本身部分,通过这里,我们可以看到如果添加了PFLAG_SKIP_DRAW标记//那么该View的绘制会被跳过,从而直接分发到子Viewpublic RenderNode updateDisplayListIfDirty() { ... //当包含标记位时直接分发 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); ... } else { draw(canvas); //文章开头的6步绘制过程 } ... }
作者:KuTear
链接:https://www.jianshu.com/p/db3c3b99cc9c