Read the supplementary

There are a lot of tags used in Android. Simply speaking, a bit in binary is used to represent a state. Here is a simple example.

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; Private static final int HAS_FORGROUND = 0x8; private static final int HAS_FORGROUND = 0x8; 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; }Copy the code

We only need to remember that flag & HAS_FORGROUND == HAS_FORGROUND indicates that flag has HAS_FORGROUND flag bits. In addition, we can also judge the existence of multiple flags at one time.

If (flag & (HAS_FORGROUND | HAS_BACKGROUND) = = (HAS_FORGROUND | HAS_BACKGROUND)) {/ / the foreground and background are}Copy the code

The drawing process of the View Animation

In View, we know that the drawing process of View starts from the function draw(canvas), let’s analyze this function, according to its comment section, we can easily get a simplified version of the logic code.

//View.java @CallSuper public 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 not transparent to draw if (! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); }Copy the code

We know that at the top of the View tree is a DecorView, which is a subclass of FrameLayout and draws from the root node (dispatchDraw() in RootViewImpl), So if you look at the ViewGroup dispatchDraw(Canvas), the View is an empty implementation because there is no need to distribute drawings.

//ViewGroup.java @Override protected 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); }Copy the code

There’s a lot of code here, so let’s see, DrawChild () = drawChild(); drawChild() = draw(Canvas Canvas, ViewGroup parent, The long drawingTime () function distributes the drawing to the sub-view so that the whole View Tree can be drawn. OK, so let’s see how the base is drawn.

//View.java boolean draw(Canvas canvas, ViewGroup parent, long drawingTime){ boolean drawingWithRenderNode = mAttachInfo ! = null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas; final Animation a = getAnimation(); if (a ! = null) {// If there is animation inside this function request redraw, 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)} if (transformToApply! RenderNode = null) {if (concatMatrix) {if (drawingWithRenderNode) {// Modify renderNode, apply animation renderNode.setAnimationMatrix(transformToApply.getMatrix()); } else { // Undo the scroll translation, apply the transformation matrix, // then redo the scroll translate to get the correct result. canvas.translate(-transX, -transY); / / modify canvas, canvas. The application of animation concat (transformToApply. GetMatrix ()); canvas.translate(transX, transY); } parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; } float transformAlpha = transformToApply.getAlpha(); if (transformAlpha < 1) { alpha *= transformAlpha; parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; } } return more; } private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, Animation a, Boolean scalingRequired) {//t for Transformation{mAlpha,mMatrix}, Boolean more = a.gaetTransformation (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 // // Will use this tag behind a parent. MPrivateFlags | = PFLAG_DRAW_ANIMATION; final int left = mLeft + (int) region.left; final int top = mTop + (int) region.top; // Redraw the position of the drawable region of the child View according to the animation. // We know that the View Animation does not change the properties of the original View, including the width and height positions. parent.invalidate(left, top, left + (int) (region.width() + .5f), top + (int) (region.height() + .5f)); 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); } / /... }Copy the code

The invalidateInternal() function is called to request a redraw, which we’ll discuss below. The above process is the general process of drawing a View. Now let’s look at setting up the animation. The entry function for setting the View animation is usually startAnimation(), so let’s start with that.

//View.java
public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);
    invalidateParentCaches();
    invalidate(true);
}
Copy the code

The invalidate(true) function is used to request that the View tree be redrawn, but to see how it is drawn, let’s move on.

Java void invalidate(Boolean invalidateCache) {// mLeft, mRight are equal to the parent View. So the argument here is the area where the current View is. invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, Boolean fullInvalidate) {if (skipInvalidate()) {return; } / /... If (condition/* meets the conditions */){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); }} / /... }Copy the code

OK, the function mentioned in the previous section appears here, let’s see what happens. MAttachInfo is a View that was passed to its child View when the View first attached to the Window. Once AttachInfo is attached, it goes all the way to the View at the bottom of the layout. What does the ViewParent do?

//ViewGroup.java public 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 (), and set the tag of the final int [] location = attachInfo. MInvalidateChildLocation; location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; // The location of the sub-view... do { View view = null; if (parent instanceof View) { view = (View) parent; } // This means that if the child View has animation, then the parent View should also set the animation flag, all the way to the top 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.5 f)); } } } while (parent ! = null); }}Copy the code

InvalidateChildInParent () is called. Note that there are two implementations of this function, one in ViewGroup and the other in ViewRootImpl.

// viewgroup. Java // In general, this is to modify some parameters to meet the current ViewGroup, such as coordinates, etc. 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; }Copy the code

In ViewRootImpl, the entire View Tree is requested to be redrawn.

@Override public void invalidateChild(View child, Rect dirty) { invalidateChildInParent(null, dirty); } @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); // Only the UI thread can manipulate the UI... invalidateRectOnScreen(dirty); return null; } private void invalidateRectOnScreen(Rect dirty) { ... if (! mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); }}Copy the code

The logic of scheduleTraversals() is to execute a Runnable, The Runnable is actually going to execute the function doTraversal(), and doTraversal() will call performTraversals(), and here we see it redrawing. Generally speaking, the execution of animation will cause the whole View Tree to be redrawn. However, there are some internal optimizations in Android, such as moving a picture, we do not need to redraw it. Android provides a caching mechanism, and calls onDraw(canvas) function if it is not displayed. At this point, the animation’s execution logic is generally clear.

QA

ViewGroup and its subclass onDraw(Canvas) are not executed

The onDraw(Canvas) method is not executed by default if the ViewGroup does not have a background, for reasons discussed below.

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); // Set the relevant flag bit}.... } public void setWillNotDraw(Boolean willNotDraw) {public void setWillNotDraw(Boolean willNotDraw) {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 // This function is called in Boolean draw(Canvas Canvas, ViewGroup parent, long drawingTime) // Public RenderNode updateDisplayListIfDirty() {RenderNode updateDisplayListIfDirty() {RenderNode updateDisplayListIfDirty() {... If ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {dispatchDraw(canvas); . } else { draw(canvas); // The 6-step process at the beginning of the article}... }Copy the code