赶紧给你的 layout 变化加上动画吧!

什么是给 layout 变化加上动画?

其实很好理解,就是在 layout,也就是我们说的布局内容发生变化的时候,给它添加上一个过渡的动画,使其看起来更显得自然一些。
其实 Android 自带的 framework 已经为我们提供了这么一个能力,它可以在两个 layout 变化的时候加上动画,使 layout 的变化更加自然。这么好的功能为什么不用呢?而且实际上,它的使用也并不复杂,只需短短的几行代码,就可以提升用户的操作体验,何乐而不为。

about transition framework

  • Group-level animations: Apply one or more animation effects to all of the views in a view hierarchy.
  • Built-in animations: Use predefined animations for common effects such as fade out or movement.
  • Resource file support: Load view hierarchies and built-in animations from layout resource files.
  • Lifecycle callbacks: Receive callbacks that provide control over the animation and hierarchy change process.

根据官网文档所示,transition framework 有几个重要的特性:

  1. 组级别动画:支持运用一个或多个动画到视图层级上。
  2. 内建动画:framework 已经为我们提供了很多的内置动画,比如你可以使用淡入淡入、位移动画等。
  3. 支持资源文件:构建场景 scene 时,支持资源文件直接导入,当然,同时它也是支持 view 导入的。
  4. 生命周期回调:可以监听到动画执行的声明周期,提供 start、resume、pause 等。

那么,transition framework 是怎么如何实现 layout 变化动画效果的?layout、scene、transition 他们之间的关系又是什么样的?我们看一张官方贴图。


来源于官方文档.png

首先,我们将起始 layout 以及结束 layout 转化成相对应的场景。
然后,创建想要执行的 transition。
最后,通过 TransitionManager 将场景和 transition 绑定在一起执行。
这样三步,就可以实现从场景一到场景二的动画转变了。

如何给 layout 变化加上动画

scene.gif

今天就以这个为例子说一下如何给 layout 变化加上平滑的动画。
从图中可以看到,点击机器人的时候,layout 改变了,机器人变大,并且名字也移动到右边,同时,详情介绍也出现了。返回的时候,layout 中的元素也平滑的回到之前的状态。这就是用 scene 和 transition 实现的。

1. 创建场景 scene

  • 创建 layouts

layout_first.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/layout_container"
        android:layout_width="wrap_content" android:layout_height="wrap_content">

    <ImageView
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/image_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    <TextView
            app:layout_constraintLeft_toRightOf="@id/image_icon"
            app:layout_constraintTop_toTopOf="@id/image_icon"
            app:layout_constraintBottom_toBottomOf="@id/image_icon"
            android:layout_marginLeft="16dp"
            android:textSize="20sp"
            android:id="@+id/text_name"
            android:text="头像"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

</android.support.constraint.ConstraintLayout>

layout_second.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/layout_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <ImageView
            android:layout_marginTop="20dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:minHeight="200dp"
            android:minWidth="200dp"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/image_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    <TextView
            app:layout_constraintLeft_toLeftOf="@id/image_icon"
            app:layout_constraintRight_toRightOf="@id/image_icon"
            app:layout_constraintTop_toBottomOf="@id/image_icon"
            android:layout_marginTop="16dp"
            android:textSize="28sp"
            android:id="@+id/text_name"
            android:text="头像"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    <TextView
            android:id="@+id/text_detail"
            android:layout_marginTop="20dp"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/text_name"
            android:layout_width="wrap_content"
            android:textSize="18sp"
            android:padding="16dp"
            android:text="这是详情这是详情这是详情这是详情
            这是详情这是详情这是详情这是详情这是详情这是详情
            这是详情这是详情这是详情这是详情这是详情这是详情
            这是详情这是详情这是详情这是详情这是详情这是详情
            这是详情这是详情这是详情"
            android:layout_height="wrap_content"/>


</android.support.constraint.ConstraintLayout>

注意:这里两个场景 layout 的相应控件的 id 需要设置成一样的。

activity.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".LayoutWithSActivity">

    <FrameLayout
            android:id="@+id/layout_container"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_width="match_parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_height="0dp">
        
    </FrameLayout>
    
</android.support.constraint.ConstraintLayout>
  • 根据 layout 生成相应的 scene

生成 scene 的方式有两种,可以通过 view 生成一个场景 scene,也可以直接加载布局文件生成 scene。

通过布局文件直接生成 scene

val sceneFirst = Scene.getSceneForLayout(layout_container, R.layout.scene_first, this)
val sceneSecond = Scene.getSceneForLayout(layout_container, R.layout.scene_second, this)

通过 view 直接生成 scene

val firstView = layoutInflater.inflate(R.layout.scene_first, null)
val secondView = layoutInflater.inflate(R.layout.scene_second, null)

val sceneFirst = Scene(layout_container, firstView)
val sceneSecond = Scene(layout_container, secondView)

第一种方式方便,直接加载布局文件就可以了,但是不易控制里面的元素。拿本例来说,因为要控制文字的改变,所以采取第二种通过 view 的方式来生成。

2. 创建 transition

创建 transition 有两种方式,可以在 xml 中定义,也可以只直接在代码中定义。

  • 在 xml 中定义 transition

在 res/transition 下定义 transition.xml

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeBounds/>
    <fade/>
    <slide android:slideEdge="right"/>
</transitionSet>

transitionSet 是一个组合,可以同时定义多个 transition
然后再代码中加载:

val inflateTransition = TransitionInflater.from(this).inflateTransition(R.transition.layout_transform)
  • 在代码中直接生成
al transition = ChangeBounds()

内置的 transition 大概有这么多个:


内置 transition

AutoTransition 是默认的transition,Fade out,move and resize,and fade in views。
ChangeBounds 改变边界,栗子中我们用的就是这种。
其他的大家自己可以去查看下源码。

2. 应用 transition

应用 transition 非常的简单

TransitionManager.go(scene, transition)

在栗子中,点击机器人就会转变到场景 2,并且文字也发生变化,那么我们可以这么写

firstView.setOnClickListener {
            secondView.findViewById<TextView>(R.id.text_name).text = "从前置过来"
            TransitionManager.go(sceneSecond, transition)
        }

One More Thing

事实上,还有一种,不需要借助 scene 来实现的变化,那就是通过TransitionManager.beginDelayedTransition()。在执行TransitionManager.beginDelayedTransition()之后,系统会保存一个当前视图树状态的场景,之后当我们改变了View的属性之后(比如重新设置了View位置、缩放、clipe等等)。在下一次绘制时,系统会自动对比之前保存的视图树,然后执行相应动画。

实现的代码很简单

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_transiton_without_s)

        val inflateTransition = TransitionInflater.from(this).inflateTransition(R.transition.layout_transform)

        btn_add.setOnClickListener {
            val text = TextView(this).apply {
                text = "I am new"
            }

            TransitionManager.beginDelayedTransition(layout_items_container, Fade())
            layout_items_container.addView(text)
        }

        btn_remove.setOnClickListener {
            TransitionManager.beginDelayedTransition(layout_items_container, inflateTransition)
            if (layout_items_container.childCount == 0) return@setOnClickListener
            layout_items_container.removeViewAt(0)
        }
    }

写在最后

文中若有表述不恰当的地方,请合理指出,共勉。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容