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:
- At the beginning and at the end
scene
Capture the state of each view.- To create a
Animator
Animate 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.transition
The 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.
Property_name: Package_name :transition_class:property_name :package_name:transition_class:property_name
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 propertycaptureEndValues(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 propertycreateAnimator( 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 animationaddUpdateListener
To change the corresponding properties.
- Supplementary notes:
captureStartValues()
,captureEndValues()
It’s actually used to store the value of the property that’s changed at this point toTransitionValues
In thehashMap
The attribute name defined inkey
The property value is the correspondingvalue
) in case we are behindcreateAnimator
Create 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.Scene
The 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
- through
Scene.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
- through
Scene()
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 enteringwindow.exitTransition
: Transition effect when exitingwindow.reenterTransition
: Re-entry transition effectwindow.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.sharedElementReturnTransition
Corresponding 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…