Android 属性动画(五)为 ViewGroup 布局更改添加动画效果

    属性动画系统不但能轻松为视图对象本身添加动画效果,而且提供了对 ViewGroup 布局的更改添加动画效果的功能。

    可使用 LayoutTransition 类为ViewGroup 内的布局更改添加动画效果。当向 ViewGroup 中添加或删除视图时,或者使用 setVisibility() 方法改变视图的可见性(VISIBLEINVISIBLEGONE)时,这些视图会经历出现和消失动画,或者以动画的形式移动到新的位置。

一、使用视图默认布局动画

    使用视图默认的布局动画,相对比较简单,只需要在 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 添加布局动画

    使用系统默认的布局动画配置简单,但是可能会没有布局动画,也不一定是开发者想要的效果。可以通过获取 ViewGroupLayoutTransition 对象,然后调用 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 布局变更可以概括为两类变更(出现和消失),这些变更伴随着四种变更动画(APPEARINGCHANGE_APPEARINGDISAPPEARINGCHANGE_DISAPPEARING)。在默认情况下,DISAPPEARING 动画和 CHANGE_APPEARING 都会立即开始,另一类动画将会根据动画的默认时间延迟开始。这样一来,就形成了这样一条动画序列:当一个项目添加到布局中时,容器的其他子项目就会先移动(为了腾出空间给新项目),然后新增的项目就会伴随着 APPEARING 动画添加进来。相反地,当一个项目从容器中移除时,该项目会先伴随 DISAPPEARING 动画从容器中移除,然后其他项目就会伴随动画移动(为了填充移除项目产生的间隙) 。如果你不想使用这种默认的编排顺序,可以调用 setDuration(int, long)setStartDelay(int, long) 方法适当地调整某些(或者全部)动画。

特别注意:任何情况下,如果在 DISAPPEARING 动画完成之前就开始 APPEARING 动画, DISAPPEARING 动画就会立即停止,并且恢复 DISAPPEARING 动画产生的所有效果。相反地,如果在 APPEARING 动画完成之前就开始 DISAPPEARING 动画, APPEARING 动画也会立即停止,并且恢复 APPEARING 动画产生的效果。(此处说的是对于一个项目的状态,即中断了当前动画反操作的情况)

    过渡指定的所有动画,包括默认的和用户自定义的,都只是模板。也就是说,这些动画的存在是为了保留基本的动画属性,例如时长、开始延时和添加动画的属性。但是,实际的目标对象以及这些属性的始值和结束值,都会在每次运行过渡时自动设置。每一个动画都是从原始副本中克隆,然后向克隆中填充需要动画对象的动态值(例如:由布局事件而移动的布局容器中的子项)以及变化的值(例如:对象的位置、大小)。推送给动画的实际值取决于动画指定的属性。例如:默认的 CHANGE_APPEARING 动画指定动画属性有 lefttoprightbottomscrollXscrollY。在过度开始时,这些属性的值就会使用布局前和布局后的值进行更新。假如使用已知目标对象和属性名称的 ObjectAnimator 对象自定义动画,这些动画也会以同样的方式填充目标和动画值。

    LayoutTransition 类以及相关的 XML 标记 animateLayoutChanges ="true",为直接情况下的自动变更提供了一个简单实用的工具。由于布局多个层级之间的相互关系,因此无法在多层次嵌套视图上使用 LayoutTransition。另外,在添加或者移除项目时同时滚动的容器,也可能不适用这个工具,因为在动画运行时容器也正在滚动,当动画结束之后,由 LayoutTransition 计算出来的动画前后的位置可能跟实际位置不匹配。你可以在这种特殊情况下通过设置 CHANGE_APPEARINGCHANGE_DISAPPEARING 动画对象为 null 的方法禁用这类变更动画,并且适当的设置其他动画的 startDelay

更多关于 LayoutTransition 相关接口介绍请参考Google 官方文档: LayoutTransition

3.1 关于自定义布局变更动画需要注意的事项

    在 使用 LayoutTransition 类为 ViewGroup 添加布局动画 章节简单介绍了使用 LayoutTransition 自定义布局变更动画,笔者在尝试过程中发现, APPEARINGDISAPPEARING l两个动画跟 CHANGE_APPEARINGCHANGE_DISAPPEARING 这两个动画不一样。前者适用类似缩放、渐变这类属性动画即可,但是后者却不行,必须包含 lefttoprightbottomscrollXscrollY 这些属性,大家在自定义布局变更动画的时候,一定要注意这点,否则设置了无效的动画,会无动画效果。笔者查看了下 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 实现多属性动画效果

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值