动画系列文章
带你走一波Android自定义Animator属性动画相关事项(一)html
Transition
能够简单理解为一个过渡框架方便在开始场景到结束场景(不局限于Activity
跟Fragment
等页面跳转过程,页面中的控件的变化过程也是场景)设置转场动画(例如,淡入/淡出视图或更改视图尺寸)的一个API。 在Andorid 4.4.2
引入的Transition
框架,Andorid 5.0
以上的版本跳转过渡则创建在该功能上。java
有两个关键概念:场景scene
跟转场transition
。android
scene
:定义给定应用程序的UI。transition
:定义两个场景之间的动态变化。当
scene
开始时,Transition
有两个主要职责: git
- 在开始和结束的
scene
捕捉每一个视图的状态。- 建立一个
Animator
根据视图,将动画的差别从一个场景到另外一个。
将
Scene
和Transition
联系起来,提供了几个设置场景跟转场的设置方法。github
系统有实现了部分的转场动画的类,本身根据需求去处理,我这里就简单演示一下里面的几个类,其它的你们本身去试试api
transition
的建立1.1. 使用布局的方式:在res
下建立transition
目录,接着建立.xml
文件bash
建立单一转场效果res/transition/slide_transition.xml
app
<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:slideEdge="top" />
复制代码
建立 多转场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>
复制代码
载入.xml
文件(多转场跟单一转场都是使用同一方法)ide
val transition =TransitionInflater.from(this).inflateTransition(R.transition.fade_transition)
复制代码
1.2. 使用代码建立translation
的方式
//------------------------------- 建立单一转场效果
val translation = Slide().apply {
duration = 500
interpolator = AccelerateDecelerateInterpolator()
slideEdge = Gravity.TOP
}
//------------------------------- 建立多转场效果
val transitionSet = TransitionSet()
transitionSet.addTransition(Fade())
transitionSet.addTransition(Slide())
transitionSet.setOrdering(ORDERING_TOGETHER)
复制代码
//root_view是本布局中的最底层的布局,本身能够指定 可是要包含将要进行动画的控件
//单转场
TransitionManager.beginDelayedTransition(root_view, translation)
toggleVisibility(view_text,view_blue, view1_red, view_yellow)
//多转场
TransitionManager.beginDelayedTransition(root_view, transitionSet) //多转场
toggleVisibility(view_text,view_blue, view1_red, view_yellow)
/**
* 四个有颜色的方块的隐藏跟显示
*/
private fun toggleVisibility(vararg views: View?) {
for (view in views) {
view!!.visibility =
if (view!!.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE
}
}
复制代码
效果图:
这里你能够略清楚转场动画的用意,就是你指定两个场景 好比例子中的开始是
View
都显示,第二个场景是View
都隐藏,设置的transitionSet
或者translation
就是用于中间变化的过程使用的动画。实际上也是里面使用了属性动画进行处理的。(下面自定义转场动画的时候会说到)
//点击按钮
R.id.btn_change_bounds -> {
TransitionManager.beginDelayedTransition(root_view, ChangeBounds())
var lp = view1_red.layoutParams
if (lp.height == 500) {
lp.height = 200
} else {
lp.height = 500
}
view1_red.layoutParams = lp
}
//红框剪切的
R.id.btn_change_clip_bounds -> {
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
}
}
// 蓝色方块中的字内部滑动
R.id.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)
}
}
复制代码
配置Transition
能够给一些特殊目标的View
或者去掉目标View
指定Transitions
.
增长动画目标:
addTarget(View target)
addTarget(int targetViewId)
addTarget(String targetName)
: 与TransitionManager.setTransitionName
方法设定的标识符相对应。
addTarget(Class targetType)
: 类的类型 ,好比android.widget.TextView.class
。
移除动画目标:
removeTarget(View target)
removeTarget(int targetId)
removeTarget(String targetName)
removeTarget(Class target)
排除不进行动画的view
:
excludeTarget(View target, boolean exclude)
excludeTarget(int targetId, boolean exclude)
excludeTarget(Class type, boolean exclude)
excludeTarget(Class type, boolean exclude)
排除某个 ViewGroup 的全部子View
:
excludeChildren(View target, boolean exclude)
excludeChildren(int targetId, boolean exclude)
excludeChildren(Class type, boolean exclude)
主要三个方法,跟属性定义。
属性定义:官网提醒咱们避免跟其余的属性名同名,建议咱们命名规则:
package_name:transition_class:property_name
三个方法:
captureStartValues()
、captureEndValues()
、createAnimator()
captureStartValues(transitionValues: TransitionValues)
开始场景会屡次调用,在这里你调用transitionValues.values[你定义的属性名]
并将此时属性的值赋值给它captureEndValues(transitionValues: TransitionValues)
结束场景会屡次调用,在这里你调用transitionValues.values[你定义的属性名]
并将此时属性的值赋值给它createAnimator( sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues? ): Animator?
重点是这个函数,咱们在这里根据开始的场景跟结束的场景值,定义对应的属性动画,并经过监听属性动画addUpdateListener
的方法,进行对应的属性改变。
- 补充说明:
captureStartValues()
、captureEndValues()
其实是用于将此时的改变的属性值,存储到TransitionValues
中的hashMap
中(定义的属性名为key
属性值为对应的value
),方便咱们在后面createAnimator
根据存储的值进行属性动画的建立。
/**
* Create by ldr
* on 2019/12/23 16:02.
*/
class ChangeColorTransition : Transition() {
companion object {
/**
* 根据官网提供的命名规则 package_name:transition_class:property_name,避免跟与其余 TransitionValues 键起冲突
* 将颜色值存储在TransitionValues对象中的键
*/
private const val PROPNAME_BACKGROUND = "com.mzs.myapplication:transition_colors:background"
}
/**
* 添加背景Drawable的属性值到目标的TransitionsValues.value映射
*/
private fun captureValues(transitionValues: TransitionValues?) {
val view = transitionValues?.view ?: return
//保存背景的值,供后面使用
transitionValues.values[PROPNAME_BACKGROUND] = (view.background as ColorDrawable).color
}
//关键方法一 :捕获开始的场景值,屡次调用
override fun captureStartValues(transitionValues: TransitionValues) {
if (transitionValues.view.background is ColorDrawable)
captureValues(transitionValues)
}
//关键方法二 :捕获结束的场景值,屡次调用。
// 将场景中的属性值存储到transitionValues的
override fun captureEndValues(transitionValues: TransitionValues) {
if (transitionValues.view.background is ColorDrawable)
captureValues(transitionValues)
}
//关键方法三:根据 override fun createAnimator(
sceneRoot: ViewGroup?,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
//存储一个方便的开始和结束参考目标。
val view = endValues!!.view
//存储对象包含背景属性为开始和结束布局
var startBackground = startValues!!.values[PROPNAME_BACKGROUND]
var endBackground = endValues!!.values[PROPNAME_BACKGROUND]
//若是没有背景等的直接忽略掉
if (startBackground != endBackground) {
//定义属性动画。
var animator = ValueAnimator.ofObject(ArgbEvaluator(), startBackground, endBackground)
//设置监听更新属性
animator.addUpdateListener { animation ->
var value = animation?.animatedValue
if (null != value) {
view.setBackgroundColor(value as Int)
}
}
return animator
}
return null
}
}
复制代码
代码中使用
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)
}
复制代码
Scene
的建立
sceneRoot
是要进行场景变化的根布局,不用非得是整个布局的根布局,只要是包含了场景变化的根布局能够了。R.layout.scene_layout0
与R.layout.scene_layout1
中的要进行转场动画的控件id
一致
Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)
方法。var scene0 = Scene.getSceneForLayout(sceneRoot,R.layout.scene_layout0,this)
var scene1 = Scene.getSceneForLayout(sceneRoot,R.layout.scene_layout1,this)
复制代码
Scene()
构造函数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)
复制代码
这里有一点须要注意:
LayoutInflater.from(this).inflate(R.layout.scene_layout0,sceneRoot,false)
,最后一个参数要传false
,否则一旦你的view
添加到sceneRoot
中,你去调用TransitionManager.go()
传入参数就会报错IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
----------------------------scene_layout0的布局----------------------------------
<?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的布局----------------------------------
与scene_layout0同样,只是ImageView的位置更换了一下。
复制代码
两个场景要进行转场变化的控件
id
是一致的。我经过实践发现了一个问题:当多个转场控件放到不一样的
ViewGroup
下面,而不是在同一个ViewGroup
的布局下面,产生的动画会有不一致的状况。上面的全部
ImageView
都放在RelativeLayout
的布局下面,与使用Linearlayout
为纵向根布局再加上两个子横向Linearlayout
,再将ImageView
两两放置到子横向Linearlayout
中,你会看到位置变化的转场效果可能不是你所指望的。(这里应该是由于不在同一个ViewGroup
下致使的)
//场景1:
val transition = TransitionInflater.from(this).inflateTransition(R.transition.explore_transtion)
TransitionManager.go(scene0,transition)
//场景2:
val transition = TransitionInflater.from(this).inflateTransition(R.transition.explore_transtion)
TransitionManager.go(scene1,transition)
复制代码
window.enterTransition
: 进入时候的转场效果window.exitTransition
: 退出时候的转场效果window.reenterTransition
: 从新进入的转场效果window.returnTransition
: 回退的时候的转场效果对应样式下的
<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>
复制代码
explore
: 将视图移入场景中心或从中移出。
slide
: 将视图从场景的其中一个边缘移入或移出。
fade
: 经过更改视图的不透明度,在场景中添加视图或从中移除视图。
系统支持将任何扩展 Visibility
类的过渡做为进入或退出过渡。
在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
}
}
}
}
复制代码
跳转的时候,startActivity
增长bundle
val intent = Intent(this@MainActivity, SampleTranslateActivity::class.java)
val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this).toBundle()//Androidx提供的类
//val bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle()//不是Andoridx的时候使用ActivityOptions
startActivity(intent,bundle)
复制代码
setWindowAllowEnterTransitionOverlap(false)
setWindowAllowReturnTransitionOverlap(false)
复制代码
或者在Activity
或者Application
对应的样式下面增长
<item name="android:windowAllowEnterTransitionOverlap">false</item>
<item name="android:windowAllowReturnTransitionOverlap">false</item>
复制代码
对应各方法进入时候的转场效果,跟上面的转场动画的api是相对的
window.sharedElementEnterTransition
window.sharedElementExitTransition
window.sharedElementReenterTransition
window.sharedElementReturnTransition
对应样式下的<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>
复制代码
注意:版本要大于android5.0以上的,才有提供共享元素场景动画,用的时候记得作一下版本兼容
// 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 } 复制代码
2.1. 先在xml
样式中开启
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowContentTransitions">true</item>
</style>
复制代码
或者代码中开启
requestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS)
复制代码
2.2. 定义两个布局都要设置android:transitionName
跳转布局一
<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="这个是我等下转场伪装变大的数据~~~~"
/>
复制代码
布局二
<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" />
复制代码
2.3 在两个Activity中分别设置共享元素的转场动画
window.sharedElementEnterTransition = ChangeBounds()
window.sharedElementExitTransition = ChangeBounds()
复制代码
2.3 跳转开始
val intent = Intent(this@MainActivity, ShareElementActivity2::class.java)
// 构造多个Pair 一个Pair对应一个共享元素
val pair = Pair(image_blue as View, image_blue.transitionName)
val pair1 = Pair(text1 as View, text1.transitionName)
// 将多个共享元素传入
val options = ActivityOptions.makeSceneTransitionAnimation(
this@MainActivity,
pair, pair1
)
startActivity(intent, options.toBundle())
复制代码
Android
版本在 4.0(API Level 14)
到4.4.2(API Level 19)
使用 Android Support Library’s
应用于 SurfaceView
的动画可能没法正确显示。 SurfaceView
实例是从非界面线程更新的,所以这些更新与其余视图的动画可能不一样步。
当应用于 TextureView
时,某些特定过渡类型可能没法产生所需的动画效果。
扩展 AdapterView
的类(例如 ListView
)会以与过渡框架不兼容的方式管理它们的子视图。若是您尝试为基于 AdapterView
的视图添加动画效果,则设备显示屏可能会挂起。
若是您尝试使用动画调整 TextView
的大小,则文本会在该对象彻底调整过大小以前弹出到新位置。为了不出现此问题,请勿为调整包含文本的视图的大小添加动画效果。