前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 动画总结(8) - Activity 转场动画

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

作者头像
三流编程
发布2018-09-11 16:11:59
3.3K0
发布2018-09-11 16:11:59
举报

对于 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。不太清楚到底是不是这个原因。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.04.01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ActivityOptions
    • makeCustomAnimation
      • makeScaleUpAnimation
        • makeThumbnailScaleUpAnimation
          • makeClipRevealAnimation
            • makeSceneTransitionAnimation 单个 View
              • makeSceneTransitionAnimation 多个 View
              • 有共享元素时的动画效果
                • 使用共享元素时 Activity 的效果
                  • 共享元素间的效果
                  • Overlap
                  • 关于主题
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档