Android Material Design Animation(二)


1 View transitions(View转换效果)

1.1 什么是 Transition

Transition 的中文翻译是 “过渡”、“转换”、“转变”,这里对于 View Transition 个人粗浅的理解是:View 的场景切换,即 View 的入场和出场。

那么 View transition Aniamtion 就很好理解了,就是当 view 入场或出场时触发的动画。

1.2 如何实现 View Transition 动画

不管是自己实现,还是 Android 系统已经帮我们实现了的机制,大概都能总结为 3 步:

  1. 捕获view在开始场景和结束场景中的状态

  2. 基于view从一个场景到另一个场景的状态改变,创建动画

  3. 执行动画

1.3 Android Transition 框架是如何实现动画的呢?

  1. beginDelayedTransition(), 传入 viewFade,调用captureStartValues() 记录场景中各个 view 的可见性

  2. 设置 view 为不可见

  3. 在下一帧的时候,Framework 调用 captureEndValues(),记录场景中每个 view 的可见性

  4. Framework 根据可见性发生变化的 view 的前后值,创建并返回 AnimatorSet

  5. Framework 运行 Animator,触发 view 逐渐进入或退出场景

  6. 其中 Android 已经预定义了 Fade, Slide, Explode

  7. 如果需要自定义动画的话,需要派生自 Visibility, 并重载 onAppearonDisappear,分别返回 View 入场和出场的动画 Animator

  • 示例代码如下:

    TransitionManager.beginDelayedTransition(mRootView, new Fade());
    toggleVisibility(mRedBox, mGreenBox, mBlueBox, mPurpleBox, mOrangeBox);
    
    private static void toggleVisibility(View... views) {
        for (View view : views) {
            boolean isVisible = view.getVisibility() == View.VISIBLE;
            view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE);
        }
    }
    

    说明:这里通过设置 view 的 visibility 来触发 view 的出场和入场

  • 效果如下:

image

说明:红色点击对应 Fade, 绿色点击对应 Slide, 蓝色点击对应 Explode,黄色点击对应自定义的圆形消失动画

1.4 基于 Transition Framework 创建动画的优点

简单查看如下:

  1. 抽象了 Animator 的概念,使用者并不需要知道底层是通过 Animator 实现的

  2. 减少了代码量,仅需要指定view的前后不同状态,Transition会自动创建动画

  3. 有利于代码复用,自定义的 View Transition Visibility 可以直接给其他模块复用,且能使代码结构清晰

2 Activity Transitions(Activity 转换效果)

2.1 版本区别

  • Android 5.0 之前

    可以通过 Activity.overridePendingTransition();FragmentTransaction#setCustomAnimation(); 来实现 Activity 之间的切换效果。

    示例代码如下:

    activity.finish();
    overridePendingTransition(R.anim.anim_fade_in, R.anim.anim_fade_out);
    

    缺点:只能对一个 ActivityFragment 的整个 view 做动画

  • Android 5.0

    • 允许对Activity/Fragment的单个view做动画
    • 允许定义共享view并创建动画

2.2 分类

  • 按触发时间划分

    1. Exit transition (A启动B, A发生exit)

    2. Enter transition (A启动B, B发生enter)

    3. Return transition (B返回A, B发生return)

    4. Reenter transition (B返回A, A发生reenter)

  • 按内容划分

    1. Content Transition (内容切换)

    2. Shared Element Transition (共享元素切换)

2.3 创建 Activity Transition 的步骤

  1. 在调用和被调用的 Activity 中,通过设定 Window.FEATURE_ACTIVITY_TRANSITIONSWindow.FEATURE_CONTENT_TRANSITIONS 来启动 transition api

    具体代码如下:

    java 代码中设置

    getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITION);
    

    style.xml 文件中设置

    <item name=“android:windowContentTransitions”>true</item>
    

    :使用 Material Design 主题的 app 默认已启动

  2. 分别在调用与被调用的 Activity 中设置 exitenter transition

    • Material 主题默认会将 exittransition 设置为 null, 而 entertransition 设置成 Fade

    • 如果 reenterreturn transition 没有明确设置,则将用 exitentertransition 替代

  3. 分别在调用与被调用的 Activity 中设置 exitenter 共享元素的 transition

    • Material 主题会默认将 exit 的共享元素 transition 设置成 null,而 enter 的共享元素 transition 设置成 @android:transition/move

    • 如果 reenterreturn transition 没有明确设置,则将用 exitenter 的共享元素 transition 替代

  4. 调用 startActivity(Context, Bundle) 启动 Activity

    • 第二个参数 ActivityOptions.makeSceneTransitionAnimation(activity, pairs).toBundle();

    • 其中 pairs 参数是一个数组:Pair<View, String>,该数组列出了 activity 中共享的 viewview 的名称

  5. 调用 finishAfterTransition() 触发返回动画,而不是 finish()

  6. 默认情况下,Material 主题的 app 中 enter / returncontent transition 会在 exit / reentercontent transitions 结束之前开始播放(只是稍微早于

    • 可调用 setWindowAllowEnterTransitionOverlap()setWindowAllowReturnTransitionOverlap() 方法屏蔽

2.4 创建 Fragment Transition 的步骤

  1. 页面的 exit, enter, reenter, 和 return transition 需要调用 fragment 的相应方法来设置,或者通过fragment 的 xml 属性来设置。

  2. 共享元素的 enterreturn transition 也需要调用 fragment 的相应方法来设置,或者通过 fragment 的 xml 属性来设置。

  3. 虽然在 Activity 中 transition 是被 startActivity()finishAfterTransition() 触发的,但是 fragment 的 transition 却是在其被 FragmentTransaction 执行下列动作的时候自动发生的。added, removed, attached, detached, shown, hidden

  4. 在 fragment commit 之前,共享元素需要通过调用 addSharedElement(View, String) 方法来成为 FragmentTransaction 的一部分

3 Acvitivity - Content Transitions

Content Transition 的概念:Activity 或 Fragment 切换的时候,定义非共享 view 进入或离开场景的方式(动画)

相关的 api 有:setExitTransition(), setEnterTransition(), setReturnTransition(), setReenterTransition()

3.1 Content transition 如何触发

假设从 Activity A 启动 B,则过程如下:

  • 当 Activity A 调用 startActivity

    1. Framework 遍历 A 的场景树,并判断哪些 view 会离开场景

    2. A 的 exit transition 捕获 transition view 的初始状态

    3. Framework 设置全部的 transition viewINVISIBLE

    4. 在下一帧的时候,A 的 exit transition 捕获 transition view 的终止状态

    5. A 的 exit transition 根据前后的状态改变,创建 Animator 后并运行

  • 当 Activity B 启动

    1. Framework 遍历 B 的场景树,并判断哪些 view 会进入,将这些 view 初始化为 INVISIBLE

    2. B 的 enter transition 捕获 transition view 的初始状态

    3. Framework 设置全部的 transition viewVISIBLE

    4. 在下一帧的时候,B 的 enter transition 捕获 transition view 的终止状态

    5. B 的 enter transition 根据前后的状态改变,创建 Animator 后并运行

3.2 演示效果 1

image

3.3 Content Transition 的对象

看到这里,相信大家已经对 Activity Content Transition 已经有了比较清晰的理解了。不过如果仔细查看演示效果,发现还是有问题,那就是页面中的 WebView 并没有做动画。那么问题来了:

  • 什么样的对象可以当做 Transition Objects

  • 能否把 ViewGroup 和它的 children 当做一个整体做动画

查看源码:

/** @hide */
@Override
public void captureTransitioningViews(List<View> transitioningViews) {
    if (getVisibility() != View.VISIBLE) {
        return;
    }
    if (isTransitionGroup()) {
        transitioningViews.add(this);
    } else {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            child.captureTransitioningViews(transitioningViews);
        }
    }
}

我们会发现如果接口 isTransitionGroup() 返回 true 的话,那这个 ViewViewGroup 就可以当做一个整体来对待。然后再查看下 WebView.isTransitionGroup() 的原始返回值,发现是 false。到这里,我们就发现前面页面中的 WebView 并没有做动画的原因了,由于 Framework 捕获 WebView 的时候,isTransitionGroup() 返回 false, 由此开始遍历 WebView 的子控件,然后 getChildCount() 返回 0,那这里可以理解为 WebView 被忽略过去了,导致我们设置的 Content Transition 动画无法在 WebView 上生效。我们只需调用 WebView.setTransitionGroup(true) 就能让系统捕获住 WebView

3.4 演示效果 2

image

说明:当点击最下面中间的按钮之后,调用了 setTransitionGroup 之后,WebView 也开始跟着做动画了

4 Acvitivity - Shared Element Transition

4.1 概念及 api

Shared Element Transition概念 :定义的共享 view,在场景切换的时候,触发从场景1运动到场景2的对应的位置的动画

相关的 api 有:

  • setSharedElementEnterTransition()

  • setSharedElementReturnTransition()

  • setSharedElementExitTransition()

  • setSharedElementReenterTransition()

4.2 Shared Element Transition 触发过程

  1. 当 Activity A 启动 B,B 被创建,并且 B 的场景树被 measured 和 laid out 在一个透明的窗口

  2. Framework 将 B 中的 shared element 放置在 A 对应的位置上( resize和reposition )

  3. B enter transition 捕获 shared element 在 B 上的终止状态

  4. B enter transition 比较 shared element 的初始和终止状态,创建 Animator

  5. Framework 隐藏 A 上的 shared element,并运行 Animator

  6. Animator 结束或者将要结束的时候,B 的窗口背景色逐渐转为不透明,并触发 B 的 enter content transition

:默认情况下,在 transition 运行过程中 shared element 在整个场景树的顶层绘制。可以通过调用 Window#setSharedElementsUseOverlay(false) 来禁用默认效果

4.3 Shared Element Transition 对象

Content Transition 不同的是,Shared Element 需要主动设置。

为了将上面的演示结果中悬浮按钮指定为一个 Shared Element,程序猿需要写如下代码:

Transition1Activity.java

ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(
        Transition1Activity.this,
        Pair.create(mFabButton, "fab"));

Transition1Activity.xml

<Button
    android:id="@+id/fab_button"
    android:layout_width="@dimen/fab_big_size"
    android:layout_height="@dimen/fab_big_size"
    android:transitionName="fab" />

Transition2Activity.xml

<Button
    android:id="@+id/fab_button"
    android:layout_width="@dimen/fab_size"
    android:layout_height="@dimen/fab_big_size"
    android:transitionName="fab" />

说明:对于 mFabButton 来说,需要在当前 Activity 的 xml 文件中指定 transitionName,和后面跳转的 Activity 的 xml 文件中也指定相同的 transitionName。在 java 代码中,明确指定控件对象和设定的 transitionName

4.4 Shared Element Transition 延迟启动

  • 为什么需要延迟启动

    1. shared element 在 Fragment 中,FragmentTransactions 不会被立马执行

    2. shared element 是大尺寸高分辨率的图片时,会触发一次额外的排版

    3. shared element 依赖于异步加载的数据时,导致排版不及时

  • 如何延迟启动

Android 给我们提供了 2 个接口 postponeEnterTransition()startPostponedEnterTransition()

具体使用代码如下:

postponeEnterTransition();
mSharedElement.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        mSharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
        mSharedElement.requestLayout();
        startPostponedEnterTransition();
        return true;
    }
});

4.5 演示效果

image

4.6 注意点

  1. 在调用 postponeEnterTransition 之后,别忘记调用 startPostponeEnterTransition,忘记调用将导致 app 进入一种死锁状态,无法进入下一个 activity/Fragment

  2. 延迟时间最好不要超过1秒钟

参考资料

  1. http://www.androiddesignpatterns.com/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html

  2. http://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html

  3. http://www.androiddesignpatterns.com/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html

  4. http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html

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

推荐阅读更多精彩内容