★20.转场动画

普通转场动画

1. 准备工作

方式一

Activity.onCreate()setContentView()前调用以下代码。

getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

方式二

AndroidManifest.xml 里:

<application
        ...
        android:windowContentTransitions="true"/>

2. 创建Transition

方式一:通过XML创建

  1. 创建 res/transition/details_window_return_transition.xml 文件:
    <?xml version="1.0" encoding="utf-8"?>
    <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
                   android:transitionOrdering="together"
                   android:duration="500">
        <fade>
            <targets>
                <target android:excludeId="@android:id/statusBarBackground"/>
                <target android:excludeId="@android:id/navigationBarBackground"/>
            </targets>
        </fade>
        <slide android:slideEdge="top">
            <targets>
                <target android:targetId="@id/details_header_container"/>
            </targets>
        </slide>
        <slide android:slideEdge="bottom">
            <targets>
                <target android:targetId="@id/details_text_container"/>
            </targets>
        </slide>
    </transitionSet>
    
  2. 在代码中载入 XML 文件定义的Transition
    TransitionInflater transitionInflater = TransitionInflater.from(this);
    Transition transition = transitionInflater.inflateTransition(R.transition.details_window_return_transition);
    

方式二:通过代码创建

Explode explode = new Explode();
explode.addTarget(android.R.id.statusBarBackground);
explode.excludeTarget(android.R.id.navigationBarBackground, true);
Fade fade = new Fade();
Slide slide = new Slide();
TransitionSet transitionSet = new TransitionSet()
        .setDuration(500)
        .addTransition(explode)
        .addTransition(slide)
        .addTransition(fade);

3. 设置Transition

代码设置

设置方法

  • ActivityonCreate()onCreateView()中使用getWindow()设置动画。
  • FragmentonCreate()onCreateView()中使用getActivity().getWindow()设置动画。

动画种类

  • setExitTransition():当 A 启动 B 时,使 A 中的View退出场景的transition
  • setEnterTransition():当 A 启动 B 时,使 B 中的View进入场景的transition
  • setReturnTransition():当 B 返回 A 时,使 B 中的View退出场景的transition
  • setReenterTransition():当 B 返回 A 时,使 A 中的View进入场景的transition

XML设置

AndroidManifest.xml 里:

<activity
        ...
        android:theme="@style/AppTheme.Details"/>

res/values/theme.xml 里:

<resources>
    <style name="AppTheme.Details" parent="android:Theme.Material.Light.NoActionBar">
        <item name="android:statusBarColor">@android:color/black</item>
        <item name="android:windowExitTransition">@transition/details_window_enter_transition</item>
        <item name="android:windowEnterTransition">@transition/...</item>
        <item name="android:windowReenterTransition">@transition/...</item>
        <item name="android:windowReturnTransition">@transition/...</item>
    </style>
</resources>

4. 针对ViewGroup处理

  • 默认情况下,无法将ViewGroup当做一个view来处理,需要在ViewGroup的对应 XML 文件中开启TransitionGroup属性。设置ViewGroup背景色属性也有同样的效果,即便背景是透明的。
  • 若一个Transition中包含了没有开启TransitionGroup属性的ViewGrouptargetId,则此Transition不会运行。

5. 启动Activity

startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), (Pair<View, String>[]) null).toBundle());

监听Transition动画

工具类:TransitionListenerAdapter

getWindow().getEnterTransition().addListener(new TransitionListenerAdapter() { });

常见问题

  • 如果没有任何效果,检查是否把 动画种类 弄错了。

共享元素转场动画

简单方式

1. 为共享元素设置TransitionName

为两个场景想要共享的View调用setTransitionName()为相同可标识的字符串。

view.setTransitionName(/* 可标识字符串 */);

2. 启动Activity

ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), view, view.getTransitionName());
startActivity(intent, optionsCompat.toBundle());

常见问题

  • 状态栏导航栏 显示不正常:可以把 导航栏状态栏 作为共享元素。
View statusBar = getActivity().findViewById(android.R.id.statusBarBackground);
View navigationBar = getActivity().findViewById(android.R.id.navigationBarBackground);
List<Pair<View, String>> pairs = new ArrayList<>();
if (statusBar != null) {
    pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
}
if (navigationBar != null) {
    pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
}
// noinspection unchecked
startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), pairs.toArray(new Pair[pairs.size()])).toBundle());

复杂方式

  • 以下场景需要使用复杂方式来实现共享元素动画:
    • 当共享的View延迟加载时,如网络请求、FragmentViewPager等场景,需要复杂方式来处理。
    • 共享的ViewA启动BB返回A 发生了改变,导致不是同一个View时。

0. 执行流程

工具类:SharedElementTransitionHelper

1. 为共享元素设置TransitionName

为两个场景想要共享的View设置setTransitionName()为相同可标识的字符串。

view.setTransitionName(/* 可标识字符串 */);

2. 手动设置共享元素

方法简介

  • setEnterSharedElementCallback()
    • 设置的是 A->B 时, B 的动画。
    • 同时也是 B->A 时, B 的动画,虽然动画是相反的,但是会自动做倒序处理,可以看 执行流程
    • BActivity,则是由Activity.makeSceneTransitionAnimation()触发的。若 BFragment,则是在Fragmentattached()detached()触发的。
  • setExitSharedElementCallback()
    • 设置的是 A->B 时, A 的动画。
    • 同时也是 B->A 时, A 的动画,虽然动画是相反的,但是会自动做倒序处理,可以看 执行流程
    • AActivity,则是由Activity.makeSceneTransitionAnimation()触发的。若 AFragment,则是在Fragmentattached()detached()触发的。

步骤

  1. 定义SharedElementCallback对象。重写SharedElementCallback.onMapSharedElements()
  2. Activity或者Fragment中调用setEnterSharedElementCallback()setExitSharedElementCallback(),重新设置SharedElementCallback对象以实现重新设置 共享元素

简单示例

背景
  • AB 均为Activity
  • B 返回 A 时, 共享元素 发生了变化。
A.java

必须调用子ActivitysetResult()才会调用父ActivityonActivityReenter()

// 在Activity.onActivityReenter()中设置
SharedElementTransitionHelper.setExitSharedElementCallbackOnce(this, new SharedElementCallback() {
    @Override
    public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
        View newSharedElement = /* 获取新共享元素 */;
        String newTransitionName = /* 获取新TransitionName */;
        names.clear();
        sharedElements.clear();
        names.add(newTransitionName);
        sharedElements.put(newTransitionName, newSharedElement);
    }
});
B.java

此处不要在Activity.supportFinishAfterTransition()中设置,而应该在Activity.finishAfterTransition()

// 在Activity.finishAfterTransition()中设置
// 必须调用setResult()才会调用父Activity的onActivityReenter()
setResult(RESULT_OK);
SharedElementTransitionHelper.setEnterSharedElementCallbackOnce(this, new SharedElementCallback() {
    @Override
    public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
        View newSharedElement = /* 获取新共享元素 */;
        String newTransitionName = /* 获取新TransitionName */;
        names.clear();
        sharedElements.clear();
        names.add(newTransitionName);
        sharedElements.put(newTransitionName, newSharedElement);
    }
});

4. 延迟和开始共享元素动画

  • 用于处理在 转场动画 开始时, 共享元素 尚未加载的情况,此时延迟 转场动画 ,直至 共享元素 加载完毕。当有多个 共享元素 的时候也要确保所有这些 共享元素 全部加载完毕。
  • 在场景刚刚开始的地方,如Activity.onCreate()
    SharedElementTransitionHelper.pauseEnterTranstion(/* Activity */);
    
  • 在能访问到 共享元素共享元素 加载完毕的地方,如SharedElementTransitionHelper中监听了ViewPreDraw 阶段:
    SharedElementTransitionHelper.startEnterTranstionWhenViewIsReady(getActivity(), /* 共享元素 */);
    

5. 启动Activity

ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), view, view.getTransitionName());
startActivity(intent, optionsCompat.toBundle());

监听共享元素动画

getWindow().getSharedElementExitTransition().addListener(/* SharedElementTransitionHelper#TransitionListenerAdapter */);
getWindow().getSharedElementEnterTransition().addListener(/* SharedElementTransitionHelper#TransitionListenerAdapter */);

重叠属性

控制两个场景是否允许重叠。

代码

getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);

效果

自定义共享元素动画【Todo】

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <explode/>
    <changeBounds/>
    <changeTransform/>
    <changeClipBounds/>
    <changeImageTransform/>
</transitionSet>
  • 路径:changeBounds
  • 大小、缩放:changeTransform
  • 图片矩阵变换:ChangeImageTransform
  • 裁剪区域:ChangeClipBounds

教程

Material-Animations
Android Transition Framework
Postponed Shared Element Transitions

旧版转场动画【Todo】

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

推荐阅读更多精彩内容