[Android] The story that Animation and Gone have to tell: analyze the impact of animation on View

background

View's Animation and Gone are already very familiar to everyone. Animation is responsible for adding animation to View, and Gone can hide View.

So, when the animation of a View is not finished, setting Gone will terminate the animation? Will the View be hidden?

This is a phenomenon I encountered during the development process. Simply restore the scene:

First customize LoadingView, the implementation is very simple. After setting the background, when visible, start an animation that rotates around itself:

public class LoadingView extends View {
    
    

    private RotateAnimation animation;

    private void init(){
    
    
        animation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, .5f, Animation.RELATIVE_TO_SELF, .5f);
        animation.setRepeatMode(Animation.RESTART);
        animation.setInterpolator(new LinearInterpolator());
        animation.setRepeatCount(-1);
        animation.setDuration(1250);
	    setBackgroundResource(R.drawable.loading_frame);
    }

    @Override
    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
    
    
        super.onVisibilityChanged(changedView, visibility);
        if(visibility == View.VISIBLE){
    
    
            if (!animation.hasStarted()||animation.hasEnded()) {
    
    
                startAnimation(animation);
            }
        }
    }
}

Then use LoadingView in the layout file, and use FrameLayout as the outermost layer:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
            <LoadingView
            android:id="@+id/progress"
            android:layout_width="42dp"
            android:layout_height="42dp"
            android:layout_gravity="center"
            />
    
</FrameLayout>

Finally call Gone in Activity:

mProgressView.setVisibility(GONE)

As a result, loading is still spinning around there.
insert image description here

Check the properties of LoadingView, visibility is gone:

insert image description here

width and height also have values.

insert image description here

It seems to be a problem, but there are at least two details hidden behind it:

1. View animation and drawing
2. Gone processing in mainstream layout

View animation and drawing

In the official comments of View, the drawing is divided into the following steps:
insert image description here

  1. draw background
  2. Save the layers of the canvas in preparation for gradients, if necessary
  3. draw the contents of the view
  4. draw subviews
  5. Draw gradient edges and restore layers if necessary
  6. Draw decorations (e.g. scrollbars)

The fourth step is to draw the subview. There is such a piece of code in ViewGroup:

protected void dispatchDraw(Canvas canvas) {
    
    
    for (int i = 0; i < childrenCount; i++) {
    
    
         ...
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
    
    
            more |= drawChild(canvas, child, drawingTime);
        }
    }
}

The code shows that if the subview is visible, or if there is animation, the drawing logic of the subview will be called .

Therefore, under the premise of animation, it is impossible to hide View by using gone .

Handling of gone in the mainstream layout

View is hidden, usually we think that the width and height of this View are both 0.

In fact, during the layout measurement process, the sub-View whose visibility property is gone is skipped.

# FrameLayout

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    

   for (int i = 0; i < count; i++) {
    
    
      final View child = getChildAt(i);
      if (mMeasureAllChildren || child.getVisibility() != GONE) {
    
    
          if (measureMatchParentChildren) {
    
    
              if (lp.width == LayoutParams.MATCH_PARENT ||
                      lp.height == LayoutParams.MATCH_PARENT) {
    
    
                  mMatchParentChildren.add(child);
              }
          }
      }
  }

}
# LinearLayout

void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
    
    
	...
	for (int i = 0; i < count; ++i) {
    
    
		...
        if (child.getVisibility() == GONE) {
    
    
            i += getChildrenSkipCount(child, i);
            continue;
        }
     	...
	 }

}

Therefore, sub-Views whose property is gone have not been re-measured, and their sizes are still the last measured size.

touch event

Having identified the above problems, another question arose:

The child View that is set to go and perform animation at the same time is visible, so can it receive Touch events?

After playing the log, it is found that it is possible.

The reason for this can be found in the source code.

The Touch event of the child View is passed by the parent control. In the event distribution method of ViewGroup, there is the following code:

#ViewGroup
    
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
	...
    if (!child.canReceivePointerEvents()
           || !isTransformedTouchPointInView(x, y, child, null)) {
    
    
       ev.setTargetAccessibilityFocus(false);
       continue;
   }
	...
}

If child.canReceivePointerEvents()returned false, distribution is skipped.

canReceivePointerEventsThe code is as follows:

#View

protected boolean canReceivePointerEvents() {
    
    
    return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
}

Although gone is set, the Touch event can be received because the animation is not empty.

other

Even if the View is set to gone at the beginning and the user cannot see the View, the View will continue to perform animations and consume system performance.

So be cautious when using animations, and call clearAnimationoff animations in time.

Summarize

  1. clearAnimationWhen I encountered this problem at the beginning, it was solved by calling it . But I can't understand the reason, and I won't be able to do it next time I encounter a similar problem. In order to understand the principle, the problem that can be solved in one minute took less than a day, and I personally feel that it is worth it.
  2. Master the knowledge of event distribution in advance, and be targeted when analyzing Touch events.
  3. Familiar with Choreographer and the interface refresh process, you can quickly understand the animation execution process.
  4. The focus is on accumulation. Some knowledge may not be useful right now, but you will know how comfortable it is when you use it.

Reference materials:
how the interface is refreshed process
View animation operation principle

Guess you like

Origin blog.csdn.net/qq_23049111/article/details/127531493