This article has been published exclusively by guolin_blog, an official wechat account

Android custom Animator Animator

One, a brief introduction

Transition is an API that allows you to set Transition animations (e.g., fade in/out views or change the size of a view) from the start scene to the end scene. The Transition framework introduced in Android 4.4.2 builds on this feature in versions 5.0 and up.

Key concepts

There are two key concepts: scene and transition.

  • scene: Defines the UI for a given application.
  • transition: Defines the dynamic changes between two scenarios.

As the scene begins, Transition has two main responsibilities:

  1. At the beginning and at the endsceneCapture the state of each view.
  2. To create aAnimatorAnimate differences from one scene to another, depending on the view.

Key class TransitionManager

Combining Scene and Transition, we provide several methods for setting the Scene and Transition.

4. Transition

The system has realized part of the transition animation class, according to their own needs to deal with, I here a simple demonstration of the inside of a few classes, others we go to try

1.transitionThe creation of

1.1. Using the layout: Create the Transition directory under RES, and then create the.xml file

Create a single transition effect res/transition/slide_transition.xml

<slide  xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:slideEdge="top" />
Copy the code

Create multi-transition res/transition/mulity_transition

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="together">
    <explode
        android:duration="1000"
        android:interpolator="@android:interpolator/accelerate_decelerate" />
    <fade
        android:duration="1000"
        android:fadingMode="fade_in_out"
        android:interpolator="@android:interpolator/accelerate_decelerate" />
    <slide
        android:duration="500"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:slideEdge="top" />
</transitionSet>
Copy the code

Load the.xml file (use the same method for multiple transitions and single transitions)

val transition =TransitionInflater.from(this).inflateTransition(R.transition.fade_transition)
Copy the code

1.2. Use code to create translation

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to create a single transitions val translation = Slide (). The apply {duration interpolator = = 500 AccelerateDecelerateInterpolator () slideEdge = Gravity. TOP} / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to create more transitions val transitionSet  = TransitionSet() transitionSet.addTransition(Fade()) transitionSet.addTransition(Slide()) transitionSet.setOrdering(ORDERING_TOGETHER)Copy the code

2. Use & common APIS

  • The basic use
//root_view is the lowest level of the layout, Your can be specified But contains the control to make animation / / single transitions TransitionManager. BeginDelayedTransition (root_view, translation) toggleVisibility(view_text,view_blue, view1_red, View_yellow) / / ferry TransitionManager. BeginDelayedTransition (root_view, TransitionSet) // toggleVisibility(view_text,view_blue, view1_red, */ Private fun toggleVisibility(vararg views: View?) {for (view inviews) { view!! .visibility =if(view!! .visibility == View.VISIBLE) View.INVISIBLEelse View.VISIBLE
   }
}
Copy the code

Effect:

Here you can get a sense of the purpose of the transition animation, which is that you specify two scenes, such as in the example where the View is displayed at the beginning and the View is hidden at the second, and the transitionSet or translation is the animation used for the transition process. In fact, it uses property animation for processing. (It will be mentioned below when customizingtransitions)

/ / click on the button R.i db tn_change_bounds - > {TransitionManager. BeginDelayedTransition (root_view, ChangeBounds()) var lp = view1_red.layoutParamsif (lp.height == 500) {
    lp.height = 200
    } else{lp.height = 500} view1_red.layoutParams = lp} TransitionManager.beginDelayedTransition(root_view, ChangeClipBounds()) val r = Rect(20, 20, 100, 100)if (r == view1_red.clipBounds) {
    view1_red.clipBounds = null
  } else{view1_red.clipbounds = r}} // Blue box inside the word slide r.i.btn_change_scroll -> {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    val t = ChangeScroll()
    TransitionManager.beginDelayedTransition(root_view, t)
  }
  if(view_text.scrollx == -50 && view_text.scrolly == -50){view_text.scrollto (0,0)}else{
    view_text.scrollTo(-50,-50)
  }
}
Copy the code

3. translation.Targets

Configuring Transition allows you to specify Transitions for specific target views or to drop target views.

Add animation target: addTarget(View target) addTarget(int targetViewId) addTarget(String targetName) With TransitionManager. SetTransitionName method identifier corresponding to the set. AddTarget (Class targetType) : the Class type, such as android. The widget. The TextView. Class. Remove animation target: removeTarget(View target) removeTarget(int targetId) removeTarget(String targetName) removeTarget(Class target) Exclude views that do not animate: excludeTarget(View target, boolean exclude) excludeTarget(int targetId, boolean exclude) excludeTarget(Class type, Boolean exclude) excludeTarget(Class type, Boolean exclude) exclude all children of a ViewGroup: excludeChildren(View target, boolean exclude) excludeChildren(int targetId, boolean exclude) excludeChildren(Class type, boolean exclude)

4. Custom Transition animation

There are three main methods, along with attribute definitions.

  1. Property_name: Package_name :transition_class:property_name :package_name:transition_class:property_name

  2. Three methods: captureStartValues(), captureEndValues(), createAnimator()

  • captureStartValues(transitionValues: TransitionValues)The opening scene is called multiple times, so here you callTransitionvalues. values[attribute name you defined]And assign it the value of that property
  • captureEndValues(transitionValues: TransitionValues)The end scene is called multiple times, and here you callTransitionvalues. values[attribute name you defined]And assign it the value of that property
  • createAnimator( sceneRoot: ViewGroup? , startValues: TransitionValues? , endValues: TransitionValues? ) : Animator?The key is this function, where we define the corresponding property animation based on the starting and ending scene values, and listen for the property animationaddUpdateListenerTo change the corresponding properties.
  1. Supplementary notes:captureStartValues(),captureEndValues()It’s actually used to store the value of the property that’s changed at this point toTransitionValuesIn thehashMapThe attribute name defined inkeyThe property value is the correspondingvalue) in case we are behindcreateAnimatorCreate property animations based on stored values.
  • Example: Custom background color property transition animation
   /**
 * Create by ldr
 * on 2019/12/23 16:02.
 */
class ChangeColorTransition : TransitionPackage_name :transition_class:property_name () {companion object {/** * package_name:transition_class:property_name Avoid collisions with other TransitionValues keys */ private const val PROPNAME_BACKGROUND ="com.mzs.myapplication:transition_colors:background"} /** * Add the background Drawable attribute value to the target transitionsvalues. value mapping */ private fun captureValues(transitionValues: transitionValues?) { val view = transitionValues? .view ? :returnValues [PROPNAME_BACKGROUND] = (view.background as ColorDrawable).color} // Capture the starting scene value by calling Override Fun captureStartValues(transitionValues: transitionValues) {if(transitionValues. View. Background is ColorDrawable) captureValues (transitionValues)} / / key method 2: value capture the end of the scene, called multiple times. Override fun captureEndValues(transitionValues: transitionValues) {override fun captureEndValues(transitionValues: transitionValues) {if(transitionValues. View. Background is ColorDrawable) captureValues (transitionValues)} / / three key method: Override fun createAnimator(sceneRoot: ViewGroup? , startValues: TransitionValues? , endValues: TransitionValues? ) : Animator? {// Store a convenient start and end reference target. val view = endValues!! .view // Store objects containing background properties for start and end layouts var startBackground = startValues!! .values[PROPNAME_BACKGROUND] var endBackground = endValues!! Values [PROPNAME_BACKGROUND] // If there is no background, ignore itif(startBackground ! = endBackground) {// Define property animation. var animator = ValueAnimator.ofObject(ArgbEvaluator(), startBackground, EndBackground) / / set to monitor updates property animator. AddUpdateListener {animation - > var value = animation? .animatedValueif(null ! = value) { view.setBackgroundColor(value as Int) } }return animator
        }
        return null
    }
}
Copy the code

Use in code

var changeColorTransition = ChangeColorTransition()
changeColorTransition.duration = 2000
TransitionManager.beginDelayedTransition(root_view, changeColorTransition)
val backDrawable = view1_red.background as ColorDrawable
if (backDrawable.color == Color.RED) {
  view1_red.setBackgroundColor(Color.BLUE)
} else {
  view1_red.setBackgroundColor(Color.RED)
}
Copy the code

What’s the matter with the Scene

1.SceneThe creation of

SceneRoot is the root layout for the scene changes. It doesn’t have to be the root layout of the entire layout, just the root layout that includes the scene changes. R.layout.scene_layout0 is the same as the id of the control in r.layout. scene_layout1 to animate the transition

  • throughScene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)Methods.
var scene0 = Scene.getSceneForLayout(sceneRoot,R.layout.scene_layout0,this)
var scene1 = Scene.getSceneForLayout(sceneRoot,R.layout.scene_layout1,this)
Copy the code
  • throughScene()The constructor

val view =  LayoutInflater.from(this).inflate(R.layout.scene_layout0,sceneRoot,false)
val scene0 = Scene(sceneRoot,view)
val view1 =  LayoutInflater.from(this).inflate(R.layout.scene_layout1,sceneRoot,false)
val scene1 = Scene(sceneRoot,view1)
Copy the code

One thing to note here: LayoutInflater. From (this).inflate(r.layout.scene_layout0,sceneRoot,false), the last parameter should be passed false, otherwise once your view is added to sceneRoot, If you call transitionManager.go (), IllegalStateException will be reported: The specified child already has a parent. You must call removeView() on the child’s parent first.

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- scene_layout0 layout -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- <? 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/black_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="18dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="48dp"
        android:src="@drawable/shape_black_circle" />

    <ImageView
        android:id="@+id/yellow_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="48dp"
        android:layout_marginEnd="40dp"
        android:layout_marginRight="10dp"
        android:src="@drawable/shape_yellow_circle" />

    <ImageView
        android:id="@+id/red_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_below="@+id/black_circle"
        android:layout_alignParentStart="true"
        android:layout_marginStart="13dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="39dp"
        android:src="@drawable/shape_red_circle" />

    <ImageView
        android:id="@+id/blue_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="241dp"
        android:layout_marginEnd="45dp"
        android:layout_marginRight="10dp"
        android:src="@drawable/shape_blue_circle"/ > < / RelativeLayout > -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- scene_layout1 layout -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- like scene_layout0, It's just that the ImageView is in a different position.Copy the code

The control IDS for transition changes are the same for both scenarios.

One problem I have found through practice is that when multiple transitions are placed under different viewgroups, instead of the same ViewGroup layout, the resulting animation is inconsistent.

All of the imageViews above are placed underneath the layout within a RelativeLayout. Instead of using the Linearlayout for the vertical root layout plus two sub-horizontal LinearLayouts, place the ImageView in pairs into the sub-Horizontal Linearlayout. You will see that the transition effect of the position change may not be what you expected. (not in the same ViewGroup)

2. Use

// Scenario 1:  val transition = TransitionInflater.from(this).inflateTransition(R.transition.explore_transtion) Transitionmanager. go(scene0,transition) //  val transition = TransitionInflater.from(this).inflateTransition(R.transition.explore_transtion) TransitionManager.go(scene1,transition)Copy the code

Animate transitions between activities

1. Basic main API

  • window.enterTransition: Transition effect when entering
  • window.exitTransition: Transition effect when exiting
  • window.reenterTransition: Re-entry transition effect
  • window.returnTransition: Transition effect during rollback

Corresponding to the style

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  <item name="android:windowEnterTransition"></item>
  <item name="android:windowExitTransition"></item>
  <item name="android:windowReenterTransition"></item>
  <item name="android:windowReturnTransition"></item>
 </style>
Copy the code

2. Android supports the following entry and exit transitions:

Explore: Moves the view into or out of the center of the scene. Slide: Moves the view in or out of one of the edges of the scene. Fade: Adds or removes a view from a scene by changing the view’s opacity. The system supports any transition that extends a Visibility class as an entry or exit transition.

3. Basic use

Set the transition animation in onCreate()

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setUpWindow()
    }
    private fun setUpWindow() {
       window.let {
            it.exitTransition = TransitionInflater.from(this).inflateTransition(R.transition.fade_transtion)
            it.enterTransition = Explode().apply {
                duration = 500
            }
            it.reenterTransition = Explode().apply {
                duration = 500
            }
            it.returnTransition = Slide().apply {
                duration = 500
            }
        }
        }
    }
Copy the code

When jumping, startActivity adds the bundle

val intent = Intent(this@MainActivity, SampleTranslateActivity::class.java) val bundle = ActivityOptionsCompat. MakeSceneTransitionAnimation (this). ToBundle () / / Androidx class / / val bundle = ActivityOptions. MakeSceneTransitionAnimation (this). ToBundle () / / not use ActivityOptions Andoridx startActivity(intent,bundle)Copy the code

setWindowAllowEnterTransitionOverlap(false)
setWindowAllowReturnTransitionOverlap(false)
Copy the code

Or add it under the Activity or Application style

<item name="android:windowAllowEnterTransitionOverlap">false</item>
<item name="android:windowAllowReturnTransitionOverlap">false</item>
Copy the code

Share transitions between activities

1. The basic API

The transition effect of each method is relative to the transition animation API

  • window.sharedElementEnterTransition
  • window.sharedElementExitTransition
  • window.sharedElementReenterTransition
  • window.sharedElementReturnTransitionCorresponding to the style
 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowSharedElementEnterTransition"></item>
        <item name="android:windowSharedElementExitTransition"></item>
        <item name="android:windowSharedElementReenterTransition"></item>
        <item name="android:windowSharedElementReturnTransition"></item>
   </style>
Copy the code

2. Basic usage

Note: the version should be greater than android5.0 or above, only to provide shared element scene animation, remember to do version compatibility when using

    // Check if we're running on Android 5.0 or higher if (build.version.sdk_int >= build.version_codes.lollipop) {// Apply activity transition } else { // Swap without transition }Copy the code

2.1. Open in XML style first

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
 <item name="android:windowContentTransitions">true</item>
</style>
Copy the code

Or in code

requestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS)
Copy the code

2.2. To define both layouts, set Android :transitionName jump layout one

    <ImageView
        android:id="@+id/image_blue"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:src="@drawable/shape_blue_circle"
        android:transitionName="blue_name"
        />
    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:transitionName="textName"
        android:text="Here's the data I'm going to turn around and pretend to get bigger ~~~~"
        />
Copy the code

Layout 2

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginTop="68dp"
        android:src="@drawable/shape_blue_circle"
        android:transitionName="blue_name"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="68dp"
        android:text="TextView"
        android:transitionName="textName"
        android:textSize="23sp"
        android:textColor="@color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
Copy the code

2.3 Set transitions for shared elements in the two activities respectively

window.sharedElementEnterTransition = ChangeBounds()
window.sharedElementExitTransition = ChangeBounds()
Copy the code

2.3 Jump Start

val intent = Intent(this@MainActivity, ShareElementActivity2: : / / class. Java) constructing a Pair of more than one Pair corresponding to a Shared elements val Pair = Pair (image_blue as the View, image_blue.transitionName) val pair1 = Pair(text1 as View, Text1. TransitionName) / / multiple Shared elements into the val options. = ActivityOptions makeSceneTransitionAnimation (this @ MainActivity, pair, pair1 ) startActivity(intent, options.toBundle())Copy the code

Restrictions (from Android documentation)

  • Android versions 4.0(API Level 14) through 4.4.2(API Level 19) use Android Support Library’s

  • Animations applied to SurfaceView may not display correctly. SurfaceView instances are updated from non-interface threads, so these updates may be out of sync with other views’ animations.

  • When applied to TextureView, certain transition types may not produce the desired animation.

  • Classes that extend AdapterView, such as ListView, manage their subviews in ways that are incompatible with the transition framework. If you try to animate an AdapterView-based view, the device display may hang.

  • If you try to resize a TextView using animation, the text pops up to the new position before the object has been fully resized. To avoid this problem, do not animate a view that resizes text.

Source code for this chapter:

Github.com/lovebluedan…

Thanks to:

Android official documentation github.com/lgvalle/Mat… Github.com/codepath/an…