美文网首页Android1-Android开发知识Android技术集锦
使用Transition FrameWork实现有意义的转场动画

使用Transition FrameWork实现有意义的转场动画

作者: DamonZh | 来源:发表于2016-04-04 16:37 被阅读7153次

    原文

    Android Transition Framework可以实现三种效果:

    1. 不同Activity之间切换时,Activityc的内容(contentView)转场动画
    2. 不同Activity之间切换时,如果使用了Shared Element动画,也可以使用Transition FrameWork来实现不同的过渡动画效果
    3. 同一个Activity内View变化的过渡动画(Scene)

    1. Activity之间切换的过渡动画

    通过这种方法可以使activity切换时,他们的布局内容有过度动画

    当从Activity A切换到Activity B的时候,Activity布局的内容会按照预先定义好的动画来执行过渡动画。在android.transition包中,已经有三种现成的动画可以用:Explode,Slide和Fade。所有这些过渡都会跟踪activity布局中可见的目标Views,驱动这些Views按照过渡的规则产生响应的动画效果。

    Explode Slide Fade

    你可以在xml中创建这些过渡效果,也可以通过代码来创建。对于Fade过渡效果来说,它看起来是这样子的:

    xml中创建

    过渡效果定义在xml中,目录是res/transition

    res/transition/activity_fade.xml

    <?xml version="1.0" encoding="utf-8"?>
    <fade xmlns:android="http://schemas.android.com/apk/res/"
        android:duration="1000"/>
    

    res/transition/activity_slide.xml

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

    要使用这些xml'中定义的过渡动画,你需要使用TransitionInflater来实例化它们。

    MainActivity.java

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_transition);
            setupWindowAnimations();
        }
    
        private void setupWindowAnimations() {
            Slide slide = TransitionInflater.from(this).inflateTransition(R.transition.activity_slide);
            getWindow().setExitTransition(slide);
        }
    
    

    TransitionActivity.java

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_transition);
            setupWindowAnimations();
        }
    
        private void setupWindowAnimations() {
            Fade fade = TransitionInflater.from(this).inflateTransition(R.transition.activity_fade);
            getWindow().setEnterTransition(fade);
        }
    
    

    在代码中创建

    MainActivity.java

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_transition);
            setupWindowAnimations();
        }
    
        private void setupWindowAnimations() {
            Slide slide = new Slide();
            slide.setDuration(1000);
            getWindow().setExitTransition(slide);
        }
    
    

    TransitionActivity.java

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_transition);
            setupWindowAnimations();
        }
    
        private void setupWindowAnimations() {
            Fade fade = new Fade();
            fade.setDuration(1000);
            getWindow().setEnterTransition(fade);
        }
    
    

    不管哪种创建方法都会产生如下的效果:

    transition_fadetransition_fade

    那么这里面一步一步的到底发生了什么:

    1. Activity A 启动 Activity B

    2. Transition Framework 发现 A 中定义了Exit Transition (slide) 然后就会对它的所有可见的View使用这个过渡动画.

    3. Transition Framework 发现 B 中定义了Enter Transition (fade) 然后机会对它所有可见的Views使用这个过渡动画.

    4. On Back Pressed(按返回键) Transition Framework 会执行把 Enter and Exit过渡动画反过来执行(但是如果定义了 returnTransitionreenterTransition,那么就会执行这些定义的动画)

    译注:

    • Exit Transition:可以理解为activity进入后台的过渡动画
    • Enter Transition:可以理解为创建activity并显示时的过渡动画
    • Return Transition:可以理解为销毁activity时的过渡动画
    • Reenter Transition:可以理解为activity从后台进入前台时的过渡动画
    • 要使这些过渡动画生效,我们需要调用startActivity(intent,bundle)方法来启动activity。bundle需要通过ActivityOptionsCompat.makeSceneTransitionAnimation().toBundle()的方式来生成

    ReturnTransition & ReenterTransition

    Return and Reenter Transitions是与进入和退出动画相对应的.

    • EnterTransition <--> ReturnTransition
    • ExitTransition <--> ReenterTransition

    如果Return or Reenter没有创建, Android 会把Enter and Exit Transitions反过来执行. 但是如果你创建了Return or Reenter,那Android就会执行你创建的动画,并且这些动画可以不同.

    b back ab back a

    我们可以修改下前面Fade的例子,给 TransitionActivity创建 ReturnTransition。这里我们就拿Slide过渡效果来举例子。这样,如果我们从B返回到A的时候,B就会执行一个Slide的过渡效果。

    TransitionActivity.java

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_transition);
            setupWindowAnimations();
        }
    
        private void setupWindowAnimations() {
            Fade fade = new Fade();
            fade.setDuration(1000);
            getWindow().setEnterTransition(fade);
            
            Slide slide = new Slide();
            slide.setDuration(1000);
            getWindow().setReturnTransition(slide);        
        }
    
    

    可以看到,如果没有创建Return Transition,退出的时候会执行Enter Transtion相反的动画。如果创建了Return Transition,那么就会执行这个创建的动画效果。

    没有Return Transition 有Return Transition
    Enter: Fade In Enter: Fade In
    Exit: Fade Out Exit: Slide out
    transition_fadetransition_fade transition_fade2transition_fade2

    2. Activity之间共享元素(Share Elements)

    这里的思想就是通过动画的形式将两个不同布局中的两个不同的View联系起来。
    然后Transition framework就会在用户从一个View切换到另一个View的时候给用户展现一些必要的动画。
    但你要记住:发生动画的View并不是从一个布局中移动到另一个布局。他们是两个独立的View。

    A Start B with sharedA Start B with shared

    a) 设置Window Content Transition属性

    你需要在app的 styles.xml中进行设置.[译]我没有设置也没问题

    values/styles.xml

    <style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">
        ...
        <item name="android:windowContentTransitions">true</item
        ...
    </style>
    

    如果你愿意的话,你也可以给整个app设置一个默认的转场动画和共享元素动画。

    <style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">
        ...
        <!-- specify enter and exit transitions -->
        <item name="android:windowEnterTransition">@transition/explode</item>
        <item name="android:windowExitTransition">@transition/explode</item>
    
        <!-- specify shared element transitions -->
        <item name="android:windowSharedElementEnterTransition">@transition/changebounds</item>
        <item name="android:windowSharedElementExitTransition">@transition/changebounds</item>
        ...
    </style>
    

    b)设置相同的transition name

    为了使共享元素动画生效,你需要给共享元素的两个View设置相同的android:transitionName属性值。不过他们的id和其他属性可以不同。

    layout/activity_a.xml

    <ImageView
            android:id="@+id/small_blue_icon"
            style="@style/MaterialAnimations.Icon.Small"
            android:src="@drawable/circle"
            android:transitionName="@string/blue_name" />
    

    layout/activity_b.xml

    <ImageView
            android:id="@+id/big_blue_icon"
            style="@style/MaterialAnimations.Icon.Big"
            android:src="@drawable/circle"
            android:transitionName="@string/blue_name" />
    

    c) 用共享元素来启动activity

    使用ActivityOptions.makeSceneTransitionAnimation() 方法指定要共享元素的View和android:transitionName属性的值

    MainActivity.java

    
    blueIconImageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent i = new Intent(MainActivity.this, SharedElementActivity.class);
    
            View sharedView = blueIconImageView;
            String transitionName = getString(R.string.blue_name);
    
            ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, sharedView, transitionName);
            startActivity(i, transitionActivityOptions.toBundle());
        }
    });
    
    

    这样就可以有下面漂亮的过渡效果了:

    a to b with shared elementa to b with shared element

    可以看到, Transition framework 创建并执行了一个动画。动画的视觉效果就是一个View从一个activity移动到另一个activity中并伴随着形状的变化。

    在fragment之间实现Shared elements过渡动画

    这个activity中的做法几乎是一样的。

    a)b)完全一样, 只有c)有点区别

    a) 设置Window Content Transition属性

    values/styles.xml

    <style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">
        ...
        <item name="android:windowContentTransitions">true</item>
        ...
    </style>
    

    b) 设置相同的transition name

    layout/fragment_a.xml

    <ImageView
            android:id="@+id/small_blue_icon"
            style="@style/MaterialAnimations.Icon.Small"
            android:src="@drawable/circle"
            android:transitionName="@string/blue_name" />
    

    layout/fragment_b.xml

    <ImageView
            android:id="@+id/big_blue_icon"
            style="@style/MaterialAnimations.Icon.Big"
            android:src="@drawable/circle"
            android:transitionName="@string/blue_name" />
    

    c) 启动带有共享元素的fragment

    在你使用FragmentTransaction启动fragment的时候,你需要同时带上共享元素过渡动画的先关信息。

    FragmentB fragmentB = FragmentB.newInstance(sample);
    
    // Defines enter transition for all fragment views
    Slide slideTransition = new Slide(Gravity.RIGHT);
    slideTransition.setDuration(1000);
    sharedElementFragment2.setEnterTransition(slideTransition);
    
    // Defines enter transition only for shared element
    ChangeBounds changeBoundsTransition = TransitionInflater.from(this).inflateTransition(R.transition.change_bounds);
    fragmentB.setSharedElementEnterTransition(changeBoundsTransition);
    
    getFragmentManager().beginTransaction()
            .replace(R.id.content, fragmentB)
            .addSharedElement(blueView, getString(R.string.blue_name))
            .commit();
    

    最终的效果就是这样的:

    shared_element_no_overlapshared_element_no_overlap

    允许过渡效果之间的重叠

    You can define if enter and exit transitions can overlap each other.
    你可以设置一个activity的退出效果和另一个activity的进入效果产生重叠部分。

    Android文档这么说的:

    当设置为true,enter transition会立马执行>
    当设置为false,enter transition会等到退出exit transition结束后再执行.

    这对Fragments和Activities的共享元素过渡效果都是有用的。

    FragmentB fragmentB = FragmentB.newInstance(sample);
    
    // Defines enter transition for all fragment views
    Slide slideTransition = new Slide(Gravity.RIGHT);
    slideTransition.setDuration(1000);
    sharedElementFragment2.setEnterTransition(slideTransition);
    
    // Defines enter transition only for shared element
    ChangeBounds changeBoundsTransition = TransitionInflater.from(this).inflateTransition(R.transition.change_bounds);
    fragmentB.setSharedElementEnterTransition(changeBoundsTransition);
    
    // Prevent transitions for overlapping
    fragmentB.setAllowEnterTransitionOverlap(overlap);
    fragmentB.setAllowReturnTransitionOverlap(overlap);
    
    getFragmentManager().beginTransaction()
            .replace(R.id.content, fragmentB)
            .addSharedElement(blueView, getString(R.string.blue_name))
            .commit();
    

    可以看到,效果还是挺明显的:

    Overlap True Overlap False
    Fragment_2 出现在Fragment_1的上面 Fragment_2 等到Fragment_1消失后才出现
    shared_element_overlapshared_element_overlap shared_element_no_overlapshared_element_no_overlap

    3. 布局元素动画

    Scenes

    也可以用来驱动一个activity中的布局发生变化时,让其中的View产生过渡动画。

    过渡效果发生在场景之间。场景就是一个定义了静态UI的普通布局。Transition Framework可以在两个场景之间添加切换过渡动画。

    scene1 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene1, this);
    scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene2, this);
    scene3 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene3, this);
    scene4 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene4, this);
    
    (...)
    
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button1:
                TransitionManager.go(scene1, new ChangeBounds());
                break;
            case R.id.button2:
                TransitionManager.go(scene2, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds));
                break;
            case R.id.button3:
                TransitionManager.go(scene3, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential));
                break;
            case R.id.button4:
                TransitionManager.go(scene4, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential_with_interpolators));
                break;  
        }
    }
    

    上面的代码会在同一个activity中的4个场景切换时产生过渡动画。每一次切花的动画都是不一样的。

    Transition Framework会考虑当前场景内所有可见的View并计算需出需要的动画来安排两个场景之间的view的位置。

    scenes_animscenes_anim

    布局的改变

    也可以用来给布局属性的变化加上过渡效果。你只需要做你想要的改变然后其他的就交给Transition Framework,它会为你添加动画。

    a) 开启演示过渡效果

    下面这行代码告诉framework我们将要对UI进行一些改变,请你给我加上过渡效果。

    TransitionManager.beginDelayedTransition(sceneRoot);
    

    b) 改变布局参数

    ViewGroup.LayoutParams params = greenIconView.getLayoutParams();
    params.width = 200;
    greenIconView.setLayoutParams(params);
    
    

    改变View的宽度属性让他变小,这会触发layoutMeasure。这个点上,Transition framework会记录开始和结束时的相关值,并给这个变化加上过渡效果。

    view layout animationview layout animation

    4. (彩蛋) 共享元素(share elements)+圆形展现(Circular Reveal)

    圆形展现仅仅一个现实和隐藏一组view的动画而已。API21+可以通过ViewAnimationUtils来使用它。

    Circular Reveal动画可以结合共享元素过渡效果来创建一些有意义的动画来告诉用户app在发生中什么。

    reveal_shared_animreveal_shared_anim

    这又是怎么回事呢:

    • 橙色的圆是发生在MainActivityRevealActivity之间的共享元素动画。
    • RevealActivity中有一个共享元素动画结束的监听器。当动画结束时它做了两件事:
      • 给Toolbar执行一个Circular Reveal动画
      • 使用ViewPropertyAnimatorRevealActivity的其他View执行一个放大的动画

    监听共享元素动画的结束

    Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.changebounds_with_arcmotion);
    getWindow().setSharedElementEnterTransition(transition);
    transition.addListener(new Transition.TransitionListener() {
        @Override
        public void onTransitionEnd(Transition transition) {
            animateRevealShow(toolbar);
            animateButtonsIn();
        }
        
        (...)
    
    });
            
    

    显示Toolbar

    private void animateRevealShow(View viewRoot) {
        int cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2;
        int cy = (viewRoot.getTop() + viewRoot.getBottom()) / 2;
        int finalRadius = Math.max(viewRoot.getWidth(), viewRoot.getHeight());
    
        Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, cx, cy, 0, finalRadius);
        viewRoot.setVisibility(View.VISIBLE);
        anim.setDuration(1000);
        anim.setInterpolator(new AccelerateInterpolator());
        anim.start();
    }
    

    放大activity的内部view

    private void animateButtonsIn() {
        for (int i = 0; i < bgViewGroup.getChildCount(); i++) {
            View child = bgViewGroup.getChildAt(i);
            child.animate()
                    .setStartDelay(100 + i * DELAY)
                    .setInterpolator(interpolator)
                    .alpha(1)
                    .scaleX(1)
                    .scaleY(1);
        }
    }
    

    更多circular reveal动画

    有很多种方式来实现一个view的显示动画,但重要的是要让这些动画有意义,它可以告诉用户app中正在发生着什么。

    从目标View的中间产生一个Circular Reveal动画

    reveal_greenreveal_green
    int cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2;
    int cy = viewRoot.getTop();
    int finalRadius = Math.max(viewRoot.getWidth(), viewRoot.getHeight());
    
    Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, cx, cy, 0, finalRadius);
    viewRoot.setBackgroundColor(color);
    anim.start();
    

    从目标View的顶部产生一个Circular Reveal动画并且结合了其他的动画

    reveal_bluereveal_blue
    int cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2;
    int cy = (viewRoot.getTop() + viewRoot.getBottom()) / 2;
    int finalRadius = Math.max(viewRoot.getWidth(), viewRoot.getHeight());
    
    Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, cx, cy, 0, finalRadius);
    viewRoot.setBackgroundColor(color);
    anim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            animateButtonsIn();
        }
    });
    anim.start();
    

    从触摸点产生一个Circular Reveal动画

    reveal_yellowreveal_yellow
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
            if (view.getId() == R.id.square_yellow) {
                revealFromCoordinates(motionEvent.getRawX(), motionEvent.getRawY());
            }
        }
        return false;
    }
    
    private Animator animateRevealColorFromCoordinates(int x, int y) {
        float finalRadius = (float) Math.hypot(viewRoot.getWidth(), viewRoot.getHeight());
    
        Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, x, y, 0, finalRadius);
        viewRoot.setBackgroundColor(color);
        anim.start();
    }
    

    动画和展现

    reveal_redreveal_red
    Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.changebounds_with_arcmotion);
    transition.addListener(new Transition.TransitionListener() {
        @Override
        public void onTransitionEnd(Transition transition) {
            animateRevealColor(bgViewGroup, R.color.red);
        }
        (...)
       
    });
    TransitionManager.beginDelayedTransition(bgViewGroup, transition);
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
    btnRed.setLayoutParams(layoutParams);
    

    Sample source code

    https://github.com/lgvalle/Material-Animations

    More information

    相关文章

      网友评论

      • 陆仁丙:棒棒哒~ 好好学习一下
      • d8b16493a9d5:LZ英语好叼,我去看英文看不懂,百度一下,就来到你这里了,哈哈。
      • f9d3d9cdfec1:兼容4.0用什么方法呢
      • Lollipop小良:感谢分享,请问一下,你知道ToolBar与NavigationDrawer配合时,左端的菜单按钮变化吗?
        从菜单按钮变化为返回按钮。我记得以前看过,有篇文章说是关键帧的动画,没找到源码。
      • 空城新月:很棒,值得学习一阵子了!
      • 68768b474bfc:这个动效最低支持API是多少
        DamonZh:@爱编程的八戒 transitions-everywhere
        爱编程的八戒:@DamonZh 具体的库叫什么呢
        DamonZh:@TellH 有个everywherexxx的库 你可以看看
      • __huazhou:博主的第一个动画标错啦,都标成了Explode
        DamonZh:@Sausure 谢谢提醒,已改正
      • ebb7f2a77196:3KS,学习了。
      • 2eb56199844d:感谢分享
        DamonZh:@fewwind :relaxed:
      • 05310ca6a6e0:很不错,感谢分享
        DamonZh:@bigFlyingBird 不谢 拿去用:relieved:
      • 553c84894183:写的很好,一直想要这个效果
        DamonZh:@553c84894183 原作者写的好 我仅仅是搬运下而已 :smile:

      本文标题:使用Transition FrameWork实现有意义的转场动画

      本文链接:https://www.haomeiwen.com/subject/teaulttx.html