Android 动画总结(8) - Activity 转场动画

对于 Activity,在 startActivity 或 finish 后调用

overridePendingTransition(R.anim.activity_in, R.anim.activity_out)

对于 Fragment:

supportFragmentManager.beginTransaction().setCustomAnimations(R.anim.fragment_enter, R.anim.fragment_exit)

ActivityOptions

从 Android 5.0 之后,可以用 ActivityOptions 来实现,ActivityOptionsCompat 是 support v4 的兼容实现,可以支持到 4.1(SDK 16),它有几个 make 开头的方法

  1. makeCustomAnimation(Context context, int enterResId, int exitResId)
  2. makeScaleUpAnimation(View source, int startX, int startY, int startWidth, int startHeight)
  3. makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY)
  4. makeClipRevealAnimation(View source, int startX, int startY, int width, int height)
  5. makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)
  6. makeSceneTransitionAnimation(Activity activity, Pair<View, String>... sharedElements)

makeCustomAnimation

custom.onClick {
    val compat = ActivityOptionsCompat.makeCustomAnimation(ctx, R.anim.activity_in, R.anim.activity_out)
    start(it!!, compat)
}

private fun start(view: View, compat: ActivityOptionsCompat) {
    val intent = Intent(ctx, OptionAfterActivity::class.java)
    intent.putExtra("from", (view as Button).text)
    // SDK 16 以下会忽略 compat.toBundle()
    ActivityCompat.startActivity(ctx, intent, compat.toBundle())
}

最普通的,效果和过去的 overridePendingTransition 一样。

OptionAfterActivity 的布局就只有一个 TextView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".OptionAfterActivity"
    android:background="#5500f2f0">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#000000"
        android:textSize="18sp"
        android:layout_centerInParent="true"/>
</RelativeLayout>
class OptionAfterActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_option_after)
        // 接收前一个方法传过来的名字显示出来
        tv.text = intent.extras["from"].toString()
    }

    override fun onBackPressed() {
        ActivityCompat.finishAfterTransition(this)
    }
}

发现 ActivityCompat.finishAfterTransition(this) 并没什么用。看源码

public static void finishAfterTransition(Activity activity) {
    if (Build.VERSION.SDK_INT >= 21) {
        activity.finishAfterTransition();
    } else {
        activity.finish();
    }
}

好吧,ActivityCompat.startActivitySDK >= 16 就生效,这退出的必须 >= 21 才行。再继续往下走

public void finishAfterTransition() {
    if (!mActivityTransitionState.startExitBackTransition(this)) {
        finish();
    }
}
public boolean startExitBackTransition(final Activity activity) {
    if (mEnteringNames == null || mCalledExitCoordinator != null) {
        return false;
    } else {
        if (!mHasExited) {
            // 能判断对这里才真正执行页面返回的动画
        }
        return true;
    }
}

而这个 mEnteringNames 的定义是:

The shared elements that the calling Activity has said that they transferred to this

很可惜,此时的 mEnteringNames 是 null,直接返回 false 调用 finish 了。下面会讲到什么是 shared elements

makeScaleUpAnimation

参照 Activity 上的某个 View,新 Activity 从指定大小放大到最大显示。

动画速度太快了,也没找到可以控制时间的地方,查了许多资料,包括看源码注释,其实还不是很明白这个 View 到底有没有放大。

使用的一个场景是可能点击一个小 View,然后第二个页面某个位置显示的放大版的,这样看着好像是点击放大到另一个页面似的。

scaleUp.onClick {
    val compat = ActivityOptionsCompat.makeScaleUpAnimation(image, image.width / 2, image.height / 2, 0, 0)
    start(it!!, compat)
}

看下它的 5 个参数:

  1. View source - 参照物
  2. int startX - 相对于 source,新 Activity 开始的位置
  3. int startY - 同 startX,只不过这是 Y 轴方向上的
  4. int startWidth - 第二个 Activity 在做放大动画前一开始的初始宽度
  5. int startHeight - 这当然就是初始高度了

makeThumbnailScaleUpAnimation

和 makeScaleUpAnimation 的区别是,不再是放大页面上的一个 View,而是指定一张图,在转场时,放大这张图片。不过也许是太快了,根本看不见,也不知理解是否正确。

thumbnailScaleUp.onClick {
    val compat = ActivityOptionsCompat.makeThumbnailScaleUpAnimation(text, BitmapFactory.decodeResource(resources, R.drawable.timg), 0, 0)
    start(it!!, compat)
}

参数:

  • View source - 图片放大的参照物
  • Bitmap thumbnail - 要放大的图片
  • int startX - 相对于参照物 source,这张图片开始放大的 X 轴位置
  • int startY - 图片开始放大相对于 source 的 Y 轴位置

makeClipRevealAnimation

说是一个点圆形渐变到全部显示,参数含义和 makeScaleUpAnimation 的一样。也是太快,没看出什么特别的。

clipReveal.onClick {
    val compat = ActivityOptionsCompat.makeClipRevealAnimation(text, text.getWidth() / 2, text.getHeight() / 2, 0, 0)
    start(it!!, compat)
}

makeSceneTransitionAnimation 单个 View

Scene 就是场景,两个 Activity 中的某些 View 协同完成过渡动画。

在两个 Activity 的布局文件中,要协同做动画的 View 要有一个属性 android:transitionName 并将值设为一样的。

第一个 Activity 的 xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp">
    ...
    <ImageView
        android:id="@+id/image1"
        android:layout_width="230dp"
        android:layout_height="78dp"
        android:layout_gravity="center_horizontal"
        android:background="@drawable/timg"
        android:transitionName="image_name" />
    ...
</LinearLayout>

要跳转的新 Activity 的 xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <ImageView
        android:id="@+id/image2"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@drawable/timg"
        android:layout_alignParentBottom="true"
        android:transitionName="image_name" />
</LinearLayout>

两个要协同的 ImageView 都有一句 android:transitionName="image_name"

sceneTransitionSingleView.onClick {
    val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, image1, "image_name")
    start(it!!, compat)
}

参数:

  1. Activity activity - 当前所在 Activity
  2. View sharedElement - 要协同过渡的 View,就是共享元素
  3. String sharedElementName - 就是 xml 里定义的 transitionName

上面说到 ActivityCompat.finishAfterTransition(this) 没生效是在一处判断 shared elements 为 null 就返回执行普通的 finish 了,现在这里有 shared elements 了,反现返回原来页面确实也有动画效果了。


这种协同过渡用同类型甚至内容都差不多的 View 来做看着效果好,但就算让两个完全不一样的 View 做协同过渡,也是可以的,乳第一个 Activity 的一个 Button,点击就跳转到新 Activity,就让这个 Button 和新 Activity 里的一个 TextView 做过渡,也是可以,效果还好,就是返回时有个突变。

<Button
    android:id="@+id/sceneTransitionDifferent"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="makeSceneTransitionAnimation(不同类型View过渡)"
    android:textAllCaps="false"
    android:transitionName="transition" />
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textColor="#ff0000"
    android:textSize="12sp"
    android:background="#ffffff"
    android:padding="10dp"
    android:layout_margin="10dp"
    android:text="共享元素"
    android:transitionName="transition" />
sceneTransitionDifferent.onClick {
    val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, sceneTransitionDifferent, "transition")
    start(it!!, compat)
}

makeSceneTransitionAnimation 多个 View

修改 xml 再给其它 View 也加上 android:transitionName。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp">
    ...
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:background="#669812"
        android:text="我欲乘风归去"
        android:layout_gravity="center_horizontal"
        android:gravity="center"
        android:textSize="14sp"
        android:transitionName="text_name" />
        
    <ImageView
        android:id="@+id/image1"
        android:layout_width="230dp"
        android:layout_height="78dp"
        android:layout_gravity="center_horizontal"
        android:background="@drawable/timg"
        android:transitionName="image_name" />
    ...
</LinearLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="30dp"
        android:background="#669812"
        android:text="我欲乘风归去"
        android:layout_gravity="center_horizontal"
        android:gravity="center"
        android:textSize="26sp"
        android:transitionName="text_name" />
        
    <ImageView
        android:id="@+id/image2"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@drawable/timg"
        android:layout_alignParentBottom="true"
        android:transitionName="image_name" />
</LinearLayout>
sceneTransitionMultiView.onClick {
    val textPair = android.support.v4.util.Pair<View, String>(text, "text_name")
    val imagePair = android.support.v4.util.Pair<View, String>(image1, "image_name")

    val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, textPair, imagePair)
    start(it!!, compat)
}

activity_option1.gif

有共享元素时的动画效果

使用共享元素时 Activity 的效果

有三种:

  • explode - 爆裂,从场景中间移动视图进入或者退出屏幕
  • slide - 滑动,视图从场景的一个边缘进入或者退出屏幕
    • android:slideEdge 属性控制滑动方向,取值可以是 LEFT, TOP, RIGHT, BOTTOM, START, END
  • fade - 淡入淡出,从场景添加或者移除一个视图时改变他的透明

可以指定 target,只在某个 View 或排除某个 View 上做动画。

主题中可以配置

  • android:windowEnterTransition - 当 A start B 时,B 页面进入场景的 transition
  • android:windowExitTransition - 当 A start B 时,A 页面退出场景的 transition
  • android:windowReturnTransition - 当 B 返回 A 时,B 页面退出场景的 transition
  • android:windowReenterTransition - 当 B 返回 A 时,A 页面进入场景的 transition

如果不在主题配置,在 Activity 的代码设置,如 getWindow().setEnterTransition(new Explode());,那么

  • setEnterTransition - B 中设置
  • setExitTransition() - A 中设置
  • setReturnTransition() - B 中设置
  • setReenterTransition() - A 中设置

res/transiton 目录创建两个文件,可以定义其时间和插值器

transition_slide.xml

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

transition_set.xml

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="together" >
    <explode
        android:duration="2000"
        android:interpolator="@android:interpolator/accelerate_decelerate" />
    <fade
        android:duration="2000" />
</transitionSet>

然后配置主题

<item name="android:windowEnterTransition">@transition/transition_set</item>
<item name="android:windowReturnTransition">@transition/transition_slide</item>

如果不配置主题,也可以在 B 页面(不是跳转前的 A)代码设置

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    val set = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_set)
    val slide = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_slide)
    window.enterTransition = set
    window.returnTransition = slide
}

也可以不用 xml,全部代码配置

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

    val set = TransitionSet()
    set.ordering = TransitionSet.ORDERING_TOGETHER

    val explode = Explode()
    explode.duration = 2000
    explode.interpolator = AccelerateInterpolator()

    val fade = Fade()
    fade.duration = 2000
    fade.interpolator = AccelerateInterpolator()

    set.addTransition(explode)
    set.addTransition(fade)

    window.enterTransition = set

    val slide = Slide()
    slide.duration = 2000
    slide.slideEdge = Gravity.RIGHT
    window.returnTransition = slide
}

共享元素间的效果

  • changeBounds - 改变目标视图的布局边界
    • android:resizeClip
  • changeClipBounds - 裁剪目标视图边界
  • changeTransform - 改变目标视图的缩放比例和旋转角度
    • android:reparent 是否追踪父容器的变化
    • android:reparentWithOverlay 默认 true。 When the parent change doesn't use an overlay, it affects the transforms of the child
  • changeImageTransform - 改变目标图片的大小和缩放比例
  • ChangeScroll

res/transition 目录下创建 transition_elements_enter.xml

<?xml version="1.0" encoding="utf-8"?>
<changeBounds xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:resizeClip="true" />

创建 transition_elements_return_set.xml

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="together">
    <changeTransform xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="2000" />
    <changeClipBounds android:duration="2000"
        android:resizeClip="true" />
</transitionSet>

配置主题

<item name="android:windowSharedElementEnterTransition">@transition/transition_elements_enter</item>
<item name="android:windowSharedElementReturnTransition">@transition/transition_elements_return_set</item>

也可以在 Activity B 中代码设置

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {   
    // val set = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_elements_return_set)
    // val enter = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_elements_enter)
    val set = TransitionSet()
    set.ordering = TransitionSet.ORDERING_TOGETHER
    
    val changeTransform = ChangeTransform()
    changeTransform.duration = 2000
    
    val changeClipBounds = ChangeClipBounds()
    changeClipBounds.duration = 2000
    
    set.addTransition(changeTransform)
    set.addTransition(changeClipBounds)
    //
    val enter = ChangeBounds()
    enter.duration = 2000
    enter.resizeClip = true
        
    window.sharedElementEnterTransition = enter
    window.sharedElementReturnTransition = set
}

activity_option2.gif

Overlap

To start an enter transition as soon as possible, use the Window.setAllowEnterTransitionOverlap() method on the called activity. This lets you have more dramatic enter transitions.

更鲜活的动画效果?没看出什么来。在 Activity B 中设置

window.allowEnterTransitionOverlap = true
window.allowReturnTransitionOverlap = true

或者配置主题

<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>

关于主题

看网上有些文章说必须在主题里设置 <item name="android:windowContentTransitions">true</item> 或者代码里在 setContentView() 之前 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);,可我没设置什么问题都没有,都生效了啊。然后去看官方文档 https://developer.android.com/training/transitions/start-activity.html,又出现一个新的配置 <item name="android:windowActivityTransitions">true</item>

First, enable window content transitions with the android:windowActivityTransitions attribute when you define a style that inherits from the material theme.

这意思是说用 material theme 时才需要设置这个属性吗?而我 Demo 用的主题是 Theme.AppCompat.Light.DarkActionBar。不太清楚到底是不是这个原因。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android小菜鸡

EditText与仿钉钉的搜索功能

  仿钉钉的通讯录搜索,点击搜索框后,跳页面,直接让输入框获取焦点并且弹出软键盘,当用户输入内容后,点击软键盘上的搜索,隐藏软键盘并且显示列表。 总结需求如下...

24930
来自专栏李蔚蓬的专栏

Material Design 实战 之第一弹——Toolbar详解

本模块共有六篇文章,参考郭神的《第一行代码》,对Material Design的学习做一个详细的笔记,大家可以一起交流一下:

11520
来自专栏Venyo 的专栏

WinForm 为 DataGridViewCell 绑定 DataGridView

先上效果图。 ? ? 对外提供一个 public 的方法: // 正常 Type1 是 JSON 的数据集或者 BSON 的数据集 public void ...

26150
来自专栏向治洪

android的Drawable详解

Drawable简介 Drawable有很多种,用来表示一种图像的概念,但他们又不完全是图像,他们是用过颜色构建出来的各种图像的表现形式。Drawable一般都...

21650
来自专栏上善若水

035android初级篇之[转]android的ViewGroup与View

Android中的View包含了用户交互和显示,类似于Windows操作系统中的window。

10530
来自专栏Winter漫聊技术

使TextView消失的10种方法

但是,这在某些情况下无法满足业务需求,比如说某控件既有图像又有文字,而我只想让文字消失,那上面的方法显然不行,这时可以使用

13030
来自专栏向治洪

React Native控件之ListView

概述 ListView作为核心组件之一,主要用于高效地显示一个可以垂直滚动的变化的数据列表。经过自定义组装,我们还可以用它实现九宫格等页面效果。 在Reac...

29170
来自专栏向治洪

Android WindowManager详解

概述 WindowManager是Android中一个重要的服务(Service )。WindowManager Service 是全局的,是唯一的。它将用户的...

30590
来自专栏飞雪无情的博客

Android中为图标加上数字--用于未读短信数提醒,待更新应用数提醒等

在我们开发一些如短消息、应用商店等应用时,会考虑在短消息的图标上加上未读短信的数量,在应用商店上加上可以升级的应用数量,这样不占太大空间还能达到提示的目的。

12640
来自专栏移动开发

杂记

1.EditText自动获取焦点弹出软键盘 为了防止上述情况,可以在其父级控件中添加如下属性:

13430

扫码关注云+社区

领取腾讯云代金券