属性动画系统不但能轻松为视图对象本身添加动画效果,而且提供了对 ViewGroup
布局的更改添加动画效果的功能。
可使用 LayoutTransition
类为ViewGroup
内的布局更改添加动画效果。当向 ViewGroup
中添加或删除视图时,或者使用 setVisibility()
方法改变视图的可见性(VISIBLE
、INVISIBLE
或 GONE
)时,这些视图会经历出现和消失动画,或者以动画的形式移动到新的位置。
一、使用视图默认布局动画
使用视图默认的布局动画,相对比较简单,只需要在 XML 布局文件中,在需要添加布局动画的 ViewGroup
定义代码中添加 android:animateLayoutChanges="true"
属性配置,属性值为 true
。那么,在这个 ViewGroup
内的视图变更时,就会有系统默认的布局动画了。
- 示例代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:animateLayoutChanges="true">
<Button
android:id="@+id/btnShow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="隐藏"/>
<Button
android:id="@+id/btnHide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/btnShow"
app:layout_constraintTop_toTopOf="parent"
android:text="隐藏按钮"/>
<Button
android:id="@+id/btnAnimator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/btnHide"
app:layout_constraintTop_toTopOf="parent"
android:text="加载动画"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="200dp"
android:minHeight="200dp"
android:src="@mipmap/ic_launcher"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnShow"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:clickable="true"
android:stateListAnimator="@animator/selector_animate_scale"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- 实现效果
注意事项:也可以在代码中使用
ViewGroup.LayoutTransition()
方法启用布局变更动画。
二、使用 LayoutTransition
类为 ViewGroup
添加布局动画
使用系统默认的布局动画配置简单,但是可能会没有布局动画,也不一定是开发者想要的效果。可以通过获取 ViewGroup
的 LayoutTransition
对象,然后调用 LayoutTransition
对象的 setAnimator()
方法设置相应的动画。可设置的布局动画有四类,用 LayoutTransision
类的常量定义,分别是:
APPEARING
- 该动画在容器中出现的项上运行。CHANGE_APPEARING
- 该动画在因某个新项目在容器中出现而变化的项上运行。DISAPPEARING
- 该动画在从容器中消失的项上运行。CHANGE_DISAPPEARING
- 该动画在因某个项从容器中消失而变化的项上运行。
- 示例
res/animator/animator_view_appearing.xml
: APPEARING 动画
<?xml version="1.0" encoding="utf-8"?>
<!-- 项目出现动画,透明度0 -> 1,X和Y方向缩放从 0 -> 1 -->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300">
<propertyValuesHolder android:propertyName="alpha" android:valueType="floatType" android:valueFrom="0.0" android:valueTo="1.0" />
<propertyValuesHolder android:propertyName="scaleX" android:valueType="floatType" android:valueFrom="0.0" android:valueTo="1.0" />
<propertyValuesHolder android:propertyName="scaleY" android:valueType="floatType" android:valueFrom="0.0" android:valueTo="1.0" />
</objectAnimator>
res/animator/animator_view_disappearing.xml
: DISAPPEARING 动画
<?xml version="1.0" encoding="utf-8"?>
<!-- 项目消失动画,透明度1 -> 0,X和Y方向缩放从 1 -> 0 -->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300">
<propertyValuesHolder android:propertyName="alpha" android:valueType="floatType" android:valueFrom="1.0" android:valueTo="0.0" />
<propertyValuesHolder android:propertyName="scaleX" android:valueType="floatType" android:valueFrom="1.0" android:valueTo="0.0" />
<propertyValuesHolder android:propertyName="scaleY" android:valueType="floatType" android:valueFrom="1.0" android:valueTo="0.0" />
</objectAnimator>
- 在代码中为
ViewGroup
设置布局动画
findViewById<ViewGroup>(R.id.container).apply {
if(null == layoutTransition) {
layoutTransition = LayoutTransition()
}
layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, AnimatorInflater.loadAnimator(this@AnimActivity, R.animator.animator_view_disappearing))
layoutTransition.setAnimator(LayoutTransition.APPEARING, AnimatorInflater.loadAnimator(this@AnimActivity, R.animator.animator_view_appearing))
动画的定义也可以在代码中进行.
- 效果
注意事项:
1. 修改默认布局变更动画的前提是先启用布局变更动画(请参考:使用视图默认布局动画);
2. 布局更改动画是分类型设置,没有设置的将以系统默认动画;
3. 布局变更的动画播放时长不能超过系统默认值,超过默认值将以默认值为动画时长;
4. 对于CHANGE_APPEARING
动画和CHANGE_DISAPPEARING
动画,请参考:LayoutTransition 详解。
三、 LayoutTransition
详解
LayoutTransition
类是用来为 ViewGroup
对象内的布局发生变更自动添加动画的。前面介绍的两种为 ViewGroup
启用布局变化动画都是同样的原理(XML 布局中配置和代码中配置),就是为 ViewGroup
设置一个 LayoutTransition
对象,设置之后会使用系统默认的动画,开发者也可以指定自定义的动画。
ViewGroup
布局变更可以概括为两类变更(出现和消失),这些变更伴随着四种变更动画(APPEARING
、 CHANGE_APPEARING
、DISAPPEARING
和 CHANGE_DISAPPEARING
)。在默认情况下,DISAPPEARING
动画和 CHANGE_APPEARING
都会立即开始,另一类动画将会根据动画的默认时间延迟开始。这样一来,就形成了这样一条动画序列:当一个项目添加到布局中时,容器的其他子项目就会先移动(为了腾出空间给新项目),然后新增的项目就会伴随着 APPEARING
动画添加进来。相反地,当一个项目从容器中移除时,该项目会先伴随 DISAPPEARING
动画从容器中移除,然后其他项目就会伴随动画移动(为了填充移除项目产生的间隙) 。如果你不想使用这种默认的编排顺序,可以调用 setDuration(int, long)
和 setStartDelay(int, long)
方法适当地调整某些(或者全部)动画。
特别注意:任何情况下,如果在
DISAPPEARING
动画完成之前就开始APPEARING
动画,DISAPPEARING
动画就会立即停止,并且恢复DISAPPEARING
动画产生的所有效果。相反地,如果在APPEARING
动画完成之前就开始DISAPPEARING
动画,APPEARING
动画也会立即停止,并且恢复APPEARING
动画产生的效果。(此处说的是对于一个项目的状态,即中断了当前动画反操作的情况)
过渡指定的所有动画,包括默认的和用户自定义的,都只是模板。也就是说,这些动画的存在是为了保留基本的动画属性,例如时长、开始延时和添加动画的属性。但是,实际的目标对象以及这些属性的始值和结束值,都会在每次运行过渡时自动设置。每一个动画都是从原始副本中克隆,然后向克隆中填充需要动画对象的动态值(例如:由布局事件而移动的布局容器中的子项)以及变化的值(例如:对象的位置、大小)。推送给动画的实际值取决于动画指定的属性。例如:默认的 CHANGE_APPEARING
动画指定动画属性有 left
、top
、right
、bottom
、scrollX
和 scrollY
。在过度开始时,这些属性的值就会使用布局前和布局后的值进行更新。假如使用已知目标对象和属性名称的 ObjectAnimator
对象自定义动画,这些动画也会以同样的方式填充目标和动画值。
LayoutTransition
类以及相关的 XML 标记 animateLayoutChanges ="true"
,为直接情况下的自动变更提供了一个简单实用的工具。由于布局多个层级之间的相互关系,因此无法在多层次嵌套视图上使用 LayoutTransition
。另外,在添加或者移除项目时同时滚动的容器,也可能不适用这个工具,因为在动画运行时容器也正在滚动,当动画结束之后,由 LayoutTransition
计算出来的动画前后的位置可能跟实际位置不匹配。你可以在这种特殊情况下通过设置 CHANGE_APPEARING
和 CHANGE_DISAPPEARING
动画对象为 null
的方法禁用这类变更动画,并且适当的设置其他动画的 startDelay
。
更多关于 LayoutTransition
相关接口介绍请参考Google 官方文档: LayoutTransition
3.1 关于自定义布局变更动画需要注意的事项
在 使用 LayoutTransition 类为 ViewGroup 添加布局动画 章节简单介绍了使用 LayoutTransition
自定义布局变更动画,笔者在尝试过程中发现, APPEARING
和 DISAPPEARING
l两个动画跟 CHANGE_APPEARING
和 CHANGE_DISAPPEARING
这两个动画不一样。前者适用类似缩放、渐变这类属性动画即可,但是后者却不行,必须包含 left
、top
、right
、bottom
、scrollX
和 scrollY
这些属性,大家在自定义布局变更动画的时候,一定要注意这点,否则设置了无效的动画,会无动画效果。笔者查看了下 LayoutTransition
源码,其定义默认动画的代码是这样的,大家在自定义自己的布局变更动画时可以参考下:
public LayoutTransition() {
if (defaultChangeIn == null) {
// "left" is just a placeholder; we'll put real properties/values in when needed
PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
defaultChangeIn.setDuration(DEFAULT_DURATION);
defaultChangeIn.setStartDelay(mChangingAppearingDelay);
defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
defaultChangeOut = defaultChangeIn.clone();
defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
defaultChange = defaultChangeIn.clone();
defaultChange.setStartDelay(mChangingDelay);
defaultChange.setInterpolator(mChangingInterpolator);
defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
defaultFadeIn.setDuration(DEFAULT_DURATION);
defaultFadeIn.setStartDelay(mAppearingDelay);
defaultFadeIn.setInterpolator(mAppearingInterpolator);
defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
defaultFadeOut.setDuration(DEFAULT_DURATION);
defaultFadeOut.setStartDelay(mDisappearingDelay);
defaultFadeOut.setInterpolator(mDisappearingInterpolator);
}
mChangingAppearingAnim = defaultChangeIn;
mChangingDisappearingAnim = defaultChangeOut;
mChangingAnim = defaultChange;
mAppearingAnim = defaultFadeIn;
mDisappearingAnim = defaultFadeOut;
}
四、动画效果对界面性能的影响
所有的效果都是以性能为代价的,用于更新界面的 Animator
会使动画运行的每一帧都进行额外的渲染。因此,使用资源密集型动画可能会对应用的性能产生负面影响,所以开发者要合理使用动画。
上一篇:Android 属性动画(四)使用动画插值器
下一篇:Android 属性动画(六)使用 ViewPropertyAnimator 实现多属性动画效果