Detailed explanation of Android Transition Framework --- super cool animation framework

Detailed explanation of Android Transition Framework --- super cool animation framework

Preface

As early as Android 4.4, Transition was introduced, but it was not truly implemented until 5.0. What is Transition used for? Next, I will analyze this powerful animation framework of Google through examples and principle analysis.

Let's start with a rendering to calm the situation

This effect will be described below, but you must first understand some basic concepts of this framework.

The core of Transition Framework is to help developers automatically generate animations based on different scenes (explained below). Usually, animations are enabled through the following methods.

  • TransitionManager.go()
  • beginDelayedTransition()
  • setEnterTransition()/setSharedElementEnterTransition()

Let’s explain each of these situations one by one.

TransitionManager.go()

First, let's introduce the Scene class and see the official explanation.

A scene represents the collection of values ​​that various properties in the View hierarchy will have when the scene is applied. A Scene can be configured to automatically run a Transition when it is applied, which will animate the various property changes that take place during the scene change.

The popular explanation is that this class stores the properties of various views under a root view. The instance is usually obtained by getSceneForLayout (ViewGroup sceneRoot, int layoutId, Context context).

  • sceneRoot

The location where the scene changes and the animation is executed

  • layoutId

That is the root view mentioned above

Maybe this explanation is a bit weak, so let me give you an example.

[[185335]]

  1. private Scene scene1;
  2.  
  3. private Scene scene2;
  4.  
  5. private boolean isScene2;
  6.  
  7. @Override
  8.  
  9. protected void onCreate(Bundle savedInstanceState) {
  10.  
  11. super.onCreate(savedInstanceState);
  12.  
  13. setContentView(R.layout.activity_scene);
  14.  
  15. initToolbar();
  16.  
  17. initScene();
  18.  
  19. }
  20.  
  21.  
  22. private void initScene() {
  23.  
  24. ViewGroup sceneRoot= (ViewGroup) findViewById(R.id.scene_root);
  25.  
  26. scene1=Scene.getSceneForLayout(sceneRoot,R.layout.scene_1,this);
  27.  
  28. scene2=Scene.getSceneForLayout(sceneRoot,R.layout.scene_2,this);
  29.  
  30. TransitionManager.go(scene1);
  31.  
  32. }
  33.  
  34.  
  35. /**
  36.  
  37. * Switch between scene1 and scene2 and play animation * @param view  
  38.  
  39. */
  40.  
  41. public void change( View   view ){
  42.  
  43. TransitionManager.go(isScene2?scene1:scene2,new ChangeBounds());
  44.  
  45. isScene2=!isScene2;
  46.  
  47. }

scene1:

scene2:

Note that in the two scene layouts, 1 and 4, 2 and 3 have the same ID except for the different positions and sizes of the images. They can be considered as one view because the different creation animations of the starting scene are for the same view.

The above simple example triggers the animation through the first method TransitionManager.go(). That is, when entering the Activity, manually set the start scene to scene1 through TransitionManager.go(scene1). Click the button to switch to the end scene state through TransitionManager.go(scene2, new ChangeBounds()): scene2.Transition The framework analyzes the differences between the start scene and the end scene through the ChangeBounds class to create and play animations. Since the ChangeBounds class analyzes and compares the position boundaries of the views in the two scenes to create movement and scaling animations. It is found that from scene1->scene2 is actually 1->4, 2->3. So the corresponding animation is executed, which is the following effect:

There are several other classes similar to the ChangeBounds class, all of which inherit the Transiton class.

  • ChangeBounds

Detect the view's position boundaries to create moving and scaling animations

  • ChangeTransform

Detect the view's scale and rotation to create scaling and rotation animations

  • ChangeClipBounds

Detects the position boundary of the view's clipping area, similar to ChangeBounds. However, ChangeBounds targets the view while ChangeClipBounds targets the view's clipping area (rect in setClipBound(Rect rect)). If not set, there will be no animation effect

  • ChangeImageTransform

Detect the size, position, and ScaleType of ImageView (specifically ImageView) and create corresponding animations.

  • Fade, Slide, Explode

These three create fade-in, sliding, and explosion animations respectively according to the visibility of the view.

The implementation effects of the above animation classes are as follows:

  • AutoTransition

If TransitionManager.go(scene1) does not specify an animation, the default animation is the AutoTransition class. It is actually an animation collection. Looking at the source code, we can see that the Fade and ChangeBounds classes are actually added to the animation collection.

  1. private void init() {
  2.  
  3. setOrdering(ORDERING_SEQUENTIAL);
  4.  
  5. addTransition(new Fade(Fade. OUT )).
  6.  
  7. addTransition(new ChangeBounds()).
  8.  
  9. addTransition(new Fade(Fade. IN ));
  10.  
  11. }

Speaking of animation collections, in fact, animation classes can be created not only through methods like new ChangeBounds(), but also through XML files. And if it is an animation collection, the XML method may be more convenient.

It only takes two steps. The first step is to create an XML file in res/transition as follows:

res/transition/changebounds_and_fade.xml:

  1. <?xml version= "1.0" encoding= "utf-8" ?>
  2.  
  3. <transitionSet xmlns:android= "http://schemas.android.com/apk/res/android" >
  4.  
  5. <changeBounds />
  6.  
  7. <fade />
  8.  
  9. </transitionSet>

Then call it in the code:

  1. Transition sets=TransitionInflater. from (this).inflateTransition(R.transition.changebounds_and_fade);

Finally, I would like to add that TransitionManager.go(scene2) actually calls scene1.exit() of the current scene(scene1) and scene2.enter() of the next scene(scene2).

They will trigger scene1.setExitAction() and scene1.setEnterAction() respectively. You can customize some special effects in these two methods.

beginDelayedTransition()

Next, let's introduce the next trigger method. If you understand the above, the following is very simple. The previous TransitionManager.go() has always created the start scene and end scene according to the XML file, which is a bit troublesome.

The principle of beginDelayedTransition() is to change the properties of the view through code, and then create the animation by analyzing the differences between the start scene and the end scene through the previously introduced ChangeBounds and other classes.

Let’s take an example again:

[[185338]]

  1. @Override
  2.  
  3. protected void onCreate(Bundle savedInstanceState) {
  4.  
  5. super.onCreate(savedInstanceState);
  6.  
  7. setContentView(R.layout.activity_begin_delayed);
  8.  
  9. initToolBar();
  10.  
  11. initView();
  12.  
  13. }
  14.  
  15.  
  16. @Override
  17.  
  18. public void onClick( View v) {
  19.  
  20. //start scene is the current scene
  21.  
  22. TransitionManager.beginDelayedTransition(sceneRoot, TransitionInflater. from (this).inflateTransition(R.transition.explode_and_changebounds));
  23.  
  24. // next scene At this point the scene statue has been changed through the code
  25.  
  26. changeScene(v);
  27.  
  28. }
  29.  
  30.  
  31. private void changeScene( View   view ) {
  32.  
  33. changeSize( view );
  34.  
  35. changeVisibility(cuteboy,cutegirl,hxy,lly);
  36.  
  37. view .setVisibility( View .VISIBLE);
  38.  
  39. }
  40.  
  41.  
  42. /**
  43.  
  44. * Switch between 1.5 times the width and height of the view and the original size * Use ChangeBounds to achieve the zoom effect * @param view  
  45.  
  46. */
  47.  
  48. private void changeSize( View   view ) {
  49.  
  50. isImageBigger=!isImageBigger;
  51.  
  52. ViewGroup.LayoutParams layoutParams = view .getLayoutParams();
  53.  
  54. if(isImageBigger){
  55.  
  56. layoutParams.width=( int )(1.5*primarySize);
  57.  
  58. layoutParams.height=( int )(1.5*primarySize);
  59.  
  60. } else {
  61.  
  62. layoutParams.width=primarySize;
  63.  
  64. layoutParams.height=primarySize;
  65.  
  66. }
  67.  
  68. view .setLayoutParams(layoutParams);
  69.  
  70. }
  71.  
  72.  
  73. /**
  74.  
  75. * VISIBLE and INVISIBLE state switching * @param views
  76.  
  77. */
  78.  
  79. private void changeVisibility( View ...views){
  80.  
  81. for ( View   view :views){
  82.  
  83. view .setVisibility( view .getVisibility()== View .VISIBLE? View .INVISIBLE: View .VISIBLE);
  84.  
  85. }
  86.  
  87. }

When a click event is triggered, the current scene status is recorded, and then the size of the clicked view is changed, and the visibility of other views is changed, and then the changed scene status is recorded. In this example, the second parameter of beginDelayedTransition() is a set of ChangeBounds and Explode animations, so the scale animation is executed when the size is changed, and the explosion effect is executed when the visibility is changed. The overall effect is as follows:

Interface switching animation

After talking so much, we finally come to the highlight: the switching effect between Activity/Fragment. There are two types of interface switching, one is Content Transition without shared elements and the other is Shared Element Transition with shared elements.

Content Transition

Let me explain a few important concepts first:

  • A.exitTransition(transition)

The Transition framework will first traverse the A interface to determine the view (non-shared element view) on which the animation is to be executed. Before executing A.exitTransition(), the A interface will obtain the start scene of the interface (the view is in the VISIBLE state), and then set all the views to be animated to INVISIBLE, and obtain the end scene at this time (the view is in the INVISIBLE state). Create execution animations based on the differences in transition analysis.

  • B.enterTransition()

The Transition framework will first traverse the B interface, determine the view to be animated, and set it to INVISIBLE. Before executing B.enterTransition(), get the start scene at this time (the view is in the INVISIBLE state), then set all the views to be animated to VISIBLE, and get the end scene at this time (the view is in the VISIBLE state). Create execution animations based on the differences in transition analysis.

According to the above explanation, the interface transition animation is based on the change of visibility, so the parameters in getWindow().setEnterTransition(transition); are generally instances of the Fade, Slide, and Explode classes (because these three classes create animations by analyzing different visibilities). Usually, writing a complete Activity Content Transiton has the following steps:

  • Add in style
  1. <item name = "android:windowActivityTransitions" > true </item>

Automatically set to true when Material themes are applied.

  • Set up the corresponding A leaves/B enters/B leaves/A re-enters animations.
  1. //A is not set by default to null  
  2.  
  3. getWindow().setExitTransition(transition);
  4.  
  5. //B is not set to Fade by default
  6.  
  7. getWindow().setEnterTransition(transition);
  8.  
  9. //B is not set to default to EnterTransition
  10.  
  11. getWindow().setReturnTransition(transition);
  12.  
  13. //A is not set by default to ExitTransition
  14.  
  15. getWindow().setReenterTransition(transition);

Of course, you can also set it in the theme

  1. <item name = "android:windowEnterTransition" >@transition/slide_and_fade</item>
  2.  
  3. <item name = "android:windowReturnTransition" >@transition/return_slide</item>
  • Jump interface

The jump interface here cannot just startActivity(intent), it needs

  1. Bundle bundle=ActivityOptionsCompat.makeSceneTransitionAnimation(activity).toBundle;
  2.  
  3. startActivity(intent,bundle)

Ok, now you can run the switching animation between activities.

But you will find that when the interface is switched, after a while, B enters after A exits (it's really too much, not showing A the complete ExitTransition). If you want to wait until A completely exits before B enters, you can set setAllowEnterTransitionOverlap(false) (the default is true), which can also be set in xml:

  1. <item name = "android:windowAllowEnterTransitionOverlap" > false </item>
  2. <item name = "android:windowAllowReturnTransitionOverlap" > false </item>

Having said so much, I think I need to give another simple example.

A.Activity:

  1. @Override
  2.  
  3. protected void onCreate(Bundle savedInstanceState) {
  4.  
  5. super.onCreate(savedInstanceState);
  6.  
  7. setContentView(R.layout.activity_main);
  8.  
  9. initToolBar();
  10.  
  11. getWindow().setExitTransition(TransitionInflater. from (this).inflateTransition(R.transition.slide));
  12.  
  13. //If setReenterTransition() is not set, the default is the same as setExitTransition
  14.  
  15. }
  16.  
  17.  
  18. public void goContentTransitions( View   view ){
  19.  
  20. Intent intent = new Intent(this, ContentTransitionsActivity.class);
  21.  
  22. ActivityOptionsCompat activityOptionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this);
  23.  
  24. startActivity(intent,activityOptionsCompat.toBundle());
  25.  
  26. }

res/translation/slide.xml:

  1. <transitionSet xmlns:android= "http://schemas.android.com/apk/res/android" >
  2.  
  3. <slide android:duration= "1000" ></slide>
  4.  
  5. </transitionSet>

B.Activity:

  1. @Override
  2.  
  3. protected void onCreate(Bundle savedInstanceState) {
  4.  
  5. super.onCreate(savedInstanceState);
  6.  
  7. setContentView(R.layout.activity_content_transitions);
  8.  
  9. initToolbar();
  10.  
  11.  
  12. Slide slide=new Slide();
  13.  
  14. slide.setDuration(500);
  15.  
  16. slide.setSlideEdge(Gravity. LEFT );
  17.  
  18. getWindow().setEnterTransition(slide);
  19.  
  20. getWindow().setReenterTransition(new Explode().setDuration(600));
  21.  
  22. }

The achieved effects are as follows:

If you look closely at the animation, you can actually find that the status bar of A is also pulled down and up, and there is a certain distance between it and the view below. Virgo said he couldn't stand it.

In fact, in principle, the switching animation of Activity targets the visibility of the view of the entire interface, but is there any way to make the Transition framework focus on only a certain view or ignore a certain view? Of course, transition.addTarget() and transition.excludeTarget() can achieve the above functions respectively.

Conveniently, this attribute can also be set in XML, so what we need to do now is to exclude the statusBar, which can be written in slide.xml like this:

  1. <transitionSet xmlns:android= "http://schemas.android.com/apk/res/android" >
  2.  
  3. <slide android:duration= "1000" >
  4.  
  5. <targets >
  6.  
  7. <! --Except the status bar-->  
  8.  
  9. <target android:excludeId= "@android:id/statusBarBackground" />
  10.  
  11. <! --Only for the status bar-->  
  12.  
  13. <! --<target android:targetId="@android:id/statusBarBackground"/>--> </targets>  
  14.  
  15. </slide>
  16.  
  17. </transitionSet>

It's done. I won't post the effect, you can imagine it...

Shared Element Transition

Content Transition and Shared Element Transition often exist at the same time in interface switching. Different from Content Transition, there are mainly the following differences:

  • startActivity()
  1. Bundle bundle=ActivityOptionsCompat.makeSceneTransitionAnimation(activity, pairs).toBundle;
  2.  
  3. startActivity(intent,bundle)

Here pairs is a collection of instances of the Pair<View, String> class, which stores the shared view and name between the two activities. The name here should be consistent with the transitionName of the shared view of interface B. Like this:

  1. Intent intent = new Intent(this, WithSharedElementTransitionsActivity.class);
  2. ActivityOptionsCompat activityOptionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this
  3. ,new Pair< View , String>(shared_image, "shared_image_" )
  4. ,new Pair< View , String>(shared_text, "shared_text_" ));
  5. startActivity(intent,activityOptionsCompat.toBundle());//xml<TextView
  6. android:text= "withShared"  
  7. android:transitionName= "shared_text_"  
  8. style= "@style/MaterialAnimations.TextAppearance.Title.Inverse"  
  9. android:layout_width= "wrap_content"  
  10. android:layout_height= "wrap_content" />
  11.  
  12.  
  13. ><de.hdodenhof.circleimageview.CircleImageView
  14. android:id= "@+id/icon_gg"  
  15. android:layout_centerInParent= "true"  
  16. android:src= "@mipmap/xkl"  
  17. android:transitionName= "shared_image_"  
  18. android:layout_width= "150dp"  
  19. android:layout_height= "150dp" />
  • setSharedElementEnterTransition()/setSharedElementReturnTransition()

If not set, the default is @android:transition/move animation. The defaults for setExitTransition() and setEnterTransition() are null and Fade.

In fact, the principle of Shared Element Transition is similar to Content Transition, both of which create animations based on the different status of the scene.

The difference is that Content Transition creates animation by changing the visibility of the view to change the state of the scene, while Shared Element Transition creates animation by analyzing the size, position, and style of the shared view of the A and B interfaces. Therefore, the former usually sets Transitions such as Fade, while the latter usually sets Transitions such as ChangeBounds.

Finally, let's analyze how to achieve the gif effect at the beginning of the article.

  1. The entire animation includes Content Transition and Shared Element Transition. However, the setExitTransition() of interface A is not set to null.
  2. When entering interface B, the shared view here is simply moved, so setSharedElementEnterTransition(transition) does not need to be set, and the default is move. At the same time, a water ripple expansion animation will be executed, which can be achieved through the ViewAnimationUtils.createCircularReveal() method. After the Shared Element Transition ends, Content Transition is executed, and it can be seen that it is a Slide animation. So it can be completed by setting setExitTransition(new Slide()). Note that Slide here only acts on the bottom item (target must be set), otherwise it acts on the entire view.
  3. The most important thing is that when B exits, you can see the upper part of the screen slides up and the lower part slides down. A visual effect of being torn in the middle. So you can split the layout into two and specify it as a target with two different directions of Slide, like this:
  1. <transitionSet
  2.  
  3. android:duration= "800" xmlns:android= "http://schemas.android.com/apk/res/android" >
  4.  
  5. <slide android:slideEdge= "top" >
  6.  
  7. <targets >
  8.  
  9. <target android:targetId= "@id/viewGroup_top" ></target>
  10.  
  11. </targets>
  12.  
  13. </slide>
  14.  
  15. <slide android:slideEdge= "bottom" >
  16.  
  17. <targets >
  18.  
  19. <target android:targetId= "@id/viewGroup_bottom" ></target>
  20.  
  21. </targets>
  22.  
  23. </slide>
  24.  
  25. </transitionSet>

There is actually a pit here. Let's first look at the isTransitionGroup() method:

  1. public boolean isTransitionGroup() {
  2.  
  3. if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) {
  4.  
  5. return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0);
  6.  
  7. } else {
  8.  
  9. final ViewOutlineProvider outlineProvider = getOutlineProvider();
  10.  
  11. return getBackground() != null || getTransitionName() != null ||
  12.  
  13. (outlineProvider != null && outlineProvider != ViewOutlineProvider.BACKGROUND);
  14.  
  15. }
  16.  
  17. }

The return value is true, which means that the ViewGroup executes the Activity Transition as a whole, and false, which means that each subview in the ViewGroup executes its own. If the ViewGroup sets background or TransitionName, or setTransitionGroup(true), the return value is true, which means that the animation is executed as a whole.

So it is best to set setTransitionGroup(true) for viewGroup_bottom and viewGroup_top.

The effect is as follows, I added some other special effects

I won't post the specific code. All the codes in this article have been uploaded to Github (I am the link), including the switching of Fragments which is not introduced in this article but is included in the code. I hope everyone can give me a star.

If you still don't understand what it means after reading it once, I strongly recommend that you read it again while running the code. It is actually quite simple once you understand it.

Finally, I want to say that there are still some things about this Transition Framework that I haven’t finished talking about. It may take some time to update. Next, I will also write related articles about Dagger 2 and the enhanced version of NavigationBar, so stay tuned.

<<:  The Ultimate Guide to Creating and Publishing Android Libraries

>>:  Summary of circular progress bar in Android custom View

Recommend

Chat Weapon Arsenal, teach you how to make him/her talk to you endlessly 1PDF

Chat Weapon Arsenal,, teach you how to make him/h...

The most detailed video promotion method!

Introduction: With the vigorous development of mo...

Can advertising on TikTok bring in traffic?

TikTok has become popular, and many marketers are...

A collection of Olympic advertising and marketing methods!

The Tokyo Olympics has been underway for ten days...

Turning water into "milk" is a basic skill for chefs!

Can water be turned into "milk"? Of cou...

71 private domain traffic entry points such as Douyin, Bilibili, Kuaishou, etc.

It has become a consensus that traffic is becomin...

【Original】Salute to the product manager!

Author: Zhou Paopi You think this is an article a...

How to load local video cover in Android

There are many ways to load the cover (usually ca...

Heat stroke? Summer cold? ——Tell you how to identify and prevent it

This is the 4402nd article of Da Yi Xiao Hu "...