I don’t know if you’ve noticed, but the Android version of Nuggets has this little animation: Clicking on an author’s avatar takes you to the author’s details page, and the author’s avatar animates from the current screen to the details page.


Lack of knowledge limited my vision, really can not think how to achieve this?

While writing an article on animation recently, I found the answer online: “Transition from Activity to shared elements in animation”.

The original intention of this article is to eliminate illiteracy with everyone. If it is useful to you, “welcome to like”, so that more partners learn more knowledge. Small animation, hidden huge knowledge; No wonder the interview builds the rocket, the job turns the screw, this is the knowledge reserve, although may never use.

【 “Series of good articles recommended” 】

Android property animation, read this article enough

Android vector animation: each person will get a gold digger yellow cart

1. Transition animation for Activity switching

The Activity transition animation includes the “Enter transition” and “Exit transition” and “Shared Element Transition” animations, which also only support Android 5.0+ version.

1) Shared element transition animation

Shared element transitions refer to how views shared by two activities transition between the two activities. For example, in the Gif image above, the shared view is the ImageView.

Shared elements can also have one element and multiple elements.

Define shared element transition effects “steps” as follows:

  1. In the twoActivityDefine two views of the same type;
  2. Give twoViewSet the sametransitionNameProperties;
  3. throughActivityOptions.makeSceneTransitionAnimation()Function to generateBundleObject;
  4. startActivity()You passbundleObject.

Chestnut explanation, clear and easy to understand:

  1. Respectively inactivity_first.xmlandactivity_second.xmlLayout file definitionImageViewComponent and willtransitionNameAttribute is set toactivityTransform.
<! --activity_first.xml file contents -->
<? xml version="1.0" encoding="utf-8"? ><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
 android:layout_height="match_parent"  android:background="@color/white"  android:orientation="vertical">   <ImageView  android:id="@+id/ivImage"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:src="@mipmap/ic_one"  android:transitionName="activityTransform" />   <TextView  android:id="@+id/tvText"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_marginTop="10dp"  android:gravity="center"  android:text="I'm the first Activity"  android:textColor="@color/c_333"  android:textSize="18sp" /> </LinearLayout>  <! -- Activity_second. XML file contents --><? xml version="1.0" encoding="utf-8"? ><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical">   <ImageView  android:id="@+id/ivImage"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_alignParentBottom="true"  android:adjustViewBounds="true"  android:src="@mipmap/ic_one"  android:transitionName="activityTransform" />   <TextView  android:id="@+id/tvText"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_above="@id/ivImage"  android:layout_marginBottom="10dp"  android:gravity="center"  android:text="I'm the second Activity"  android:textColor="@color/c_333"  android:textSize="18sp" /> </RelativeLayout> Copy the code

preview activityTransformProperties can also be set through code.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    ivImage.transitionName="activityTransform"
}
Copy the code
  1. inFirstActivityTo give inImageViewSet the click event to jump to the second Activity.
ivImage.setOnClickListener {
    if(build.version.sdk_int >= build.version_codes.lollipop) {// Determine the Android VERSION        val bundle =
            ActivityOptions.makeSceneTransitionAnimation(this, ivImage, "activityTransform")
                .toBundle()
 startActivity(Intent(this, SecondActivity::class.java), bundle)  } else {  startActivity(Intent(this, SecondActivity::class.java))  } } Copy the code

In the code, first determine whether the current Android version is greater than or equal to Android 5.0. If it is greater than or equal to Android 5.0, set the shared element animation. If it is less than 5.0, start the second Activity normally.

Through ActivityOptions. MakeSceneTransitionAnimation () to create the Activity transition of some parameters, makeSceneTransitionAnimation () function first parameter as the object of the Activity; The second parameter is the shared element component, which is set to the ImageView view whose ID is ivImage; The third parameter is the value of the transitionName property, in this case activityTransform. Call AcivityOptions toBundle to wrap the Bundle object.

Effect:

Multiple shared element transitions

Multiple Shared element transition is also very simple, only need to call the makeSceneTransitionAnimation () function of another overloaded functions.

  1. Based on the previous XML layout, giveTextViewincreasetransitionNameProperties:textTransform.
<! --activity_first.xml file contents --><? xml version="1.0" encoding="utf-8"? ><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
 android:background="@color/white"  android:orientation="vertical">   <ImageView  android:id="@+id/ivImage"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:src="@mipmap/ic_one"  android:transitionName="activityTransform" />   <TextView  android:id="@+id/tvText"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_marginTop="10dp"  android:gravity="center"  android:transitionName="textTransform"  android:text="I'm the first Activity"  android:textColor="@color/c_333"  android:textSize="18sp" /> </LinearLayout>  <! -- Activity_second. XML file contents --><? xml version="1.0" encoding="utf-8"? ><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical">   <ImageView  android:id="@+id/ivSecondImage"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_alignParentBottom="true"  android:adjustViewBounds="true"  android:src="@mipmap/ic_one"  android:transitionName="activityTransform" />   <TextView  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:transitionName="textTransform"  android:layout_above="@id/ivSecondImage"  android:layout_marginBottom="10dp"  android:gravity="center"  android:text="I'm the second Activity"  android:textColor="@color/c_333"  android:textSize="18sp" /> </RelativeLayout> Copy the code
  1. Build multiplePairObject and passed tomakeSceneTransitionAnimation()Function, startActivity.
ivImage.setOnClickListener {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {             
    
    val imagePair=Pair<View,String>(ivImage,"activityTransform")
    val textPair=Pair<View,String>(ivImage,"textTransform")
  val bundle =  ActivityOptions.makeSceneTransitionAnimation(this,  imagePair,textPair).toBundle()   startActivity(Intent(this, SecondActivity::class.java), bundle)  } else {  startActivity(Intent(this, SecondActivity::class.java))  } } Copy the code

This is done primarily by wrapping the values of the shared view and transitionName attribute into the Pair, and the other steps are no different from those of a shared element.

Effect:


A deep pit remind

Sometimes enter the detail page from RecyclerView interface, due to the loading delay of the detail page, there may be no effect. For example, if ImageView loads an image from the network, it might not work from A to B, but from B to A.

Solution steps:

  1. insetContentViewAfter adding the following code, lazily loading the transition animation.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    postponeEnterTransition()
}
Copy the code
  1. After the shared element view is loaded, or the image is loaded, call the following code to start loading the transition animation.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    startPostponedEnterTransition()
}
Copy the code

For example, I call it after Glide loading:

 Glide.with(mContext)
                    .asBitmap()
.load(value? .avatar ? :"")
                    .listener(object : RequestListener<Bitmap> {
override fun onResourceReady(resource: Bitmap? , model: Any? , target: Target<Bitmap>? , dataSource: DataSource? , isFirstResource: Boolean): Boolean {animatorCallback? .invoke()// The invoke callback starts loading the transition animation return false  }  override fun onLoadFailed(e: GlideException? , model: Any? , target: Target<Bitmap>? , isFirstResource: Boolean): Boolean {animatorCallback? .invoke()// The invoke callback starts loading the transition animation return false  }  })  .apply(RequestOptions.circleCropTransform())  .placeholder(R.mipmap.ic_default)  .error(R.mipmap.ic_default) .into(authorBinding!! .ivAvatar)Copy the code

You can also consider the following code:

shareElement.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
                override fun onPreDraw(): Boolean {
shareElement!! .viewTreeObserver.removeOnPreDrawListener(this)                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animatorCallback? .invoke() }  return true  }  }) Copy the code

Enter and exit the transition animation

In contrast to shared elements, there are transitions between activities entering and exiting, where the two activities switch animations without a shared view. Let’s look at three animation renderings: “Explosion effect” and “fade in and out effect”, “slide effect”.

  • “Explosive” : Move the view into or out of the center of the scene
  • “Sliding” : Move the view in and out of one edge of the scene;
  • “Explosive” : Add or remove views from a scene by changing the view’s opacity;

The first screen uses Fade and the second screen uses Explode.

Both front and back interfaces are usedSlideSlide in slide out effect.

Using Android’s existing transition framework, it is very simple to implement. The steps are as follows:

  1. inActivitytheonCreate()Method callsetContentView()Enable window transition properties before setting;
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
Copy the code
  1. Create a transition effect objectSlide,Explode,Fade;
val slide=Slide()
slide.slideEdge=Gravity.START
Slide. duration=300// Effect duration. Generally, the Activity switching time is very short, so it is not recommended to set it too longCopy the code

For Slide effects, you can set the slideEdge property to specify the Slide direction (Gravity.BOTTOM by default).

  1. Set the transition effect to the window property, set;
Exit the transition animation for the current interfacewindow.exitTransition = slide
// Enter the transition animation of the current interfacewindow.enterTransition = slide
// Re-enter the interface transition animationwindow.reenterTransition = slide Copy the code
  1. Call the second oneActivityInterface, using transition effects.
 startActivity(
        Intent(this, SecondActivity::class.java),
        ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
Copy the code

So the Activity’s OnCreate() method looks something like this.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
            window.allowEnterTransitionOverlap=false
 Slide().apply {  duration = 300  excludeTarget(android.R.id.statusBarBackground, true)  excludeTarget(android.R.id.navigationBarBackground, true)  }.also {  window.exitTransition = it  window.enterTransition = it  window.reenterTransition = it  }  }   setContentView(R.layout.activity_first)   ivContent.setOnClickListener {  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  startActivity(  Intent(this, SecondActivity::class.java),  ActivityOptions.makeSceneTransitionAnimation(this).toBundle()  )  }  }  } Copy the code

The excludeTarget() method is called in the above code to exclude the status bar and navigation bar from the transition animation effect. Otherwise it will follow the animation effect, not very beautiful.

Normal, exit and enter the transition animation will have a short cross process, and the window. The allowEnterTransitionOverlap = false is prohibited cross, only to quit after the transition animation will display into the transition animations.

If you want to transition back to the first Activity after the second Activity finishes, instead of manually calling Finish (), you can call the finishAfterTransition () method.

Iii) Compatible with Android 5.0 before

What if You want toggle animations before Android 5.0?

  1. inres/animCreate the desired effect under the folder:
<alpha 
    xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@interpolator/decelerate_quad"
        android:fromAlpha="0.0"
        android:toAlpha="1.0"
 android:duration="@android:integer/config_longAnimTime" /> Copy the code
  1. In the startActivityAfter the calloverridePendingTransition()Methods.
val intent = Intent(this, TestActivity2::class.java)
startActivity(intent)
overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
Copy the code

OverridePendingTransition () method first argument for the next interface into the animation, the second parameter for the current interface from the animation.

At this point, the Activity’s transition animation is basically over. Some friends may ask, can only the Activity switch to apply the transition effect?

Second, layout change transition animation

In the last section, you learned one concept: scenarios. The display and hiding of the layout can be understood as a scene respectively, and the transition animation is to solve the stiff visual feeling brought by the scene switch. An Activity transition animation is a transition between two activities, and a layout change animation is a transition animation between views of the same Activity.

Create Scene manually

To create a scene manually, we need to create our own start and end scenes and use the existing transition effects to switch between the two scenes. By default, the current screen is the starting scene.

  1. For creating the start and end scenariosxmlLayout. The start scene and the end scene need to have the same root element, as in the following codeidforflConatenttheFrameLayoutLayout.
<? xml version="1.0" encoding="utf-8"? ><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
  <TextView  android:id="@+id/tvText"  android:text="Content transition animation"  android:gravity="center"  android:textSize="18sp"  android:layout_width="match_parent"  android:layout_height="50dp"/>   <FrameLayout  android:id="@+id/flContent"  android:layout_weight="1"  android:layout_width="match_parent"  android:layout_height="0dp">  <include layout="@layout/layout_first_scene"/>  </FrameLayout>  </LinearLayout> Copy the code

Initial view, first scene, layout layout_first_scene.xml:

<? xml version="1.0" encoding="utf-8"? ><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
 <TextView  android:id="@+id/tvFirst"  android:textSize="18sp"  android:layout_marginTop="100dp"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:gravity="center_horizontal|top"  android:text="Thank you for reading this article." /> </LinearLayout> Copy the code

The second scenario, layout_second_scene.xml:

<? xml version="1.0" encoding="utf-8"? ><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
  <TextView  android:textSize="18sp"  android:layout_marginTop="100dp"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:gravity="center_horizontal|top"  android:text="I am the new little dream \ N welcome everyone to like and support." /> </LinearLayout> Copy the code
  1. Create the start and end scenarios.
val firstScene = Scene.getSceneForLayout(flContent, R.layout.layout_first_scene, this)
val secondScene = Scene.getSceneForLayout(flContent, R.layout.layout_sencod_scene, this)
Copy the code

By default, the transition animation applies to the entire scene. If a View of the scene does not participate, it can be removed using the removeTarget() method of the transition object.

Slide(Gravity.TOP).removeTarget(tvNoJoin)
Copy the code
  1. When clicked, the scene transitions.
tvText.setOnClickListener {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        if (isFirst) {
            TransitionManager.go(secondScene, Slide(Gravity.TOP))
        }else{
 TransitionManager.go(firstScene, Slide(Gravity.TOP))  } isFirst=! isFirst } } Copy the code

The first parameter of transitionManager.go () indicates the end of the scene, and the second parameter indicates the exit of the current scene, which is the initial scene.

Effect:


Ii. The system automatically creates the Scene

This kind of situation, we call TransitionManager. BeginDelayedTransition (sceneRoot) function, the system will automatically record the current sceneRoot nodes under all must carry on the view of animation as a starting node, In the next frame, the view of the animation state of all the starting scenes under the sceneRoot child node is recorded again as the end scene. This is typically used to change the properties of the View and then animate transitions, such as the width and height of the View.

Chestnuts:

Define a View with only one square, and see the animation effect by changing the width and height of the square to double.

  1. activity_text.xmlLayout file, definitionidforsceneRootIs the root node of the scene.
<? xml version="1.0" encoding="utf-8"? ><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/sceneRoot"
 android:background="@color/colorPrimary">   <View  android:id="@+id/vSquare"  android:layout_width="100dp"  android:layout_height="100dp"  android:layout_centerHorizontal="true"  android:layout_marginTop="50dp"  android:background="@color/white" /> </RelativeLayout> Copy the code
  1. inTestActivitytheOnCreateMethod to set the width and height of the square to 200dp.
vSquare.setOnClickListener {
   TransitionManager.beginDelayedTransition(sceneRoot)
   vSquare.layoutParams.apply {
       width = dp2px(200f, this@TestActivity)
       height = dp2px(200f, this@TestActivity)
 }.also {  vSquare.layoutParams = it  } } Copy the code

Effect:


Three, transition animation effect

The above animation effects are all built in the system. What are the specific animation effects, or do you support customization?

The Transition effects classes inherit from the Transition class, which holds information about the scene cutting animation. The main purpose of subclasses is to capture property values (such as start and end values) and how to play the animation. It can also be seen from here that transition animation is also an extension and application of attribute animation.

I. Built-in transition animation

The system supports any transition from an extended Visibility class as an entry or exit transition. The built-in classes include Explode, Slide, and Fade. Shared element transitions are supported by:

  • changeScrollAdds an animation effect for the target view slide
  • changeBoundsAnimates changes in the layout boundaries of the target view
  • changeClipBoundsAnimates changes in clipping boundaries for the target view
  • changeTransformAnimates the zoom and rotation changes for the target view
  • changeImageTransformAnimates changes in the size and scaling of the target image

Code examples:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    TransitionSet().apply {
        addTransition(ChangeImageTransform())
        addTransition(ChangeBounds())
        addTransition(Fade(Fade.MODE_IN))
 }.also {  window.sharedElementEnterTransition=it  } } Copy the code

The TransitionSet object is a collection of animations that can organize multiple transition effects.

This can also be done with an XML layout, created in the RES/Transition folder:

<? xml version="1.0" encoding="utf-8"? ><transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:transitionOrdering="together">
    <changeImageTransform />
 <changeBounds />  <fade /> </transitionSet> Copy the code

TransitionSet and fade… Some of the attributes mentioned in this article are similar and will not be repeated here.

Code call:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    TransitionInflater.from(this).inflateTransition(R.transition.transition_set).also {
        window.sharedElementEnterTransition=it
    }
}
Copy the code

Effect:

Inheritance can be used when the existing transition effects do not meet daily requirementsTransitionCustomize your own animation effects.

2) Custom transition animation

The subclass inherits the Transition class and overwrites its three methods.

class MyTransition : Transition() {
   override fun captureStartValues(transitionValues: TransitionValues?) {}

   override fun captureEndValues(transitionValues: TransitionValues?) {}

 override fun createAnimator( sceneRoot: ViewGroup? .startValues: TransitionValues? . endValues: TransitionValues?  ): Animator {  return super.createAnimator(sceneRoot, startValues, endValues)  }  } Copy the code

The captureStartValues() and captureEndValues() methods must be implemented to capture the start and end values of the animation, while the createAnimator() method is used to create custom animations.

The TransitionValues parameter can be understood to store some property values of the View, and the sceneRoot parameter is the root View.

Custom Transition animation for Android

Ok, so much for transition animation

Reference article:

Official document

Cool Activity switch animation to create a better user experience

My lot

[code word is not easy, click a like, good view in the future]

This article is formatted using MDNICE