Android转场动画

假设,两个有Activity A和B

  • A启动B: A发生exit动画,B发生enter动画
  • B返回A:B发生return动画,A发生reenter动画

1、使用overridePendingTransition

activity之间切换可以使用overridePendingTransition

 

overridePendingTransition

 

其中,在A启动B时:

  • enterAnim:是B进入的动画
  • exitAnim:是A退出的动画

使用起来比较简单,如下:

startActivity(intent);
overridePendingTransition(R.anim.bottom_top_anim, R.anim.alpha_hide);

---

finish();
overridePendingTransition(R.anim.alpha_show, R.anim.top_bottom_anim);

注意:
1、overridePendingTransition方法必须在startActivity()或者finish()方法的后面。
2、如果参数是0,表示没有动画

2、使用windowAnimationStyle

可以在主题(Theme)中定义windowAnimationStyle,来实现转场动画,如下:

@style/ActivityAnim
---

  • 在A启动B时:
    activityOpenEnterAnimation:B进入的动画
    android:activityOpenExitAnimation:A退出的动画
  • 在B后退回A时:
    activityCloseEnterAnimation:A重新进入的动画
    activityCloseExitAnimation:B退出的动画

关于activityOpenEnterAnimation和windowEnterAnimation?
Activity就是一个可视的人机交互界面。 每一个Activity都有一个默认的Window,一般来讲,这个Window都是全屏的,当然也有例外,比如Dialog的Window就是非全屏的。他们分别设置的是activity和window的动画
定义Window进入和退出效果

@anim/enter_anim



    


---

@anim/exit_anim



---

@anim/close_enter_anim



---

@anim/close_exit_anim

  

注意:
1、windowAnimationStyle需要继承的Animation.Activity,不然一些动画效果就没有了
2、 以上方法(12)设置的动画只能针对页面中的所有元素

转场动画

3、内容变换(Content Transition)

content transition决定了非共享view元素,在activity和fragment切换期间是如何进入或者退出场景的(通过改变transitioning view的visibility实现)。在5.0之后content transition可以通过调用如下代码来设置:

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

也可以在theme中定义如下style:
android:windowExitTransition、android:windowEnterTransition、android:windowReturnTransition、android:windowReenterTransition

1、Content Transition内部揭秘
在Content transition动画(a)创建之前,framework必须通过设置transitioning view的visibility将动画需要的状态信息告诉a。具体来说,当Activity A启动B之时,发生了如下的事件:
一、Activity A 调用startActivity().
1.framework遍历A的View树,确定当A的exit transition运行时哪些view会退出场景(即哪些view是transitioning view)。
2.A的exit transition捕获A中transitioning view的开始状态。
3.framework将A中所有的transitioning view设置为INVISIBLE。
4.A的exit transition捕获到A中transitioning view的结束状态。
5.A的exit transition比较每个transitioning view的开始和结束状态,然后根据前后状态的区别创建一个Animator。Animator开始运行,同时transitioning view退出场景。
二、Activity B启动.
1.framework遍历B的View树,确定当B的enter transition运行时哪些view会进入场景,transitioning view会被初始化为INVISIBLE。
2.B的enter transition捕获B中transitioning view的开始状态。
3.framework将B中所有的transitioning view设置为VISIBLE。
4.B的enter transition捕获到B中transitioning view的结束状态。
5.B的enter transition比较每个transitioning view的开始和结束状态,然后根据前后状态的区别创建一个Animator。Animator开始运行,同时transitioning view进入场景。

通过在每个transitioning view中来回切换INVISIBLE 和VISIBLE,framework确保content transition得到创建animation(期望的animation)所需的状态信息。显然content Transition对象需要在开始和结束场景中都能记录到transitioning view的visibility。 非常幸运的是抽象类Visibility已经为你做了这些工作:Visibility的子类只需要实现onAppear() 和 onDisappear() 两个工厂方法,在这两个工厂方法中创建并返回一个进入或者退出场景的Animator对象。在api 21中,有三个现成的Visibility的实现:Fade, Slide, 和 Explode,他们都可以用在Activity 和 Fragment中创建content transition。如果必要,还可以自定义Visibility

来自于:深入理解Content Transition

2、怎么使用Content Transition

1、开启内容过渡效果

    true

还可以设置是否覆盖执行,即是否同步执行还是顺序执行

    
    true
    
    true

同样,也有对应的setWindowAllowEnterTransitionOverlap() 、setWindowAllowReturnTransitionOverlap()方法。

 

windowAllowReturnTransitionOverlap=true,同步进行.gif

默认情况下,material主题的应用中enter/return的content transition会在exit/reenter的content transitions结束之前开始播放(只是稍微早于),这样会看起来更加连贯???

2、启动activity B

    ActivityOptionsCompat optionsCompat=ActivityOptionsCompat.makeSceneTransitionAnimation(this);
    ActivityCompat.startActivity(this, intent, optionsCompat.toBundle());

3、在B中使用内容变换

        Slide slide=new Slide(Gravity.BOTTOM);
        slide.setDuration(500);
        //内容变换,不包括底部导航栏和状态栏
        slide.excludeTarget(android.R.id.navigationBarBackground, true);
        slide.excludeTarget(android.R.id.statusBarBackground, true);
        slide.excludeTarget(R.id.appBarLayout, true);
        getWindow().setEnterTransition(slide);
        getWindow().setReturnTransition(slide);

也可以在xml文件设置transition,使用TransitionInflater得到Transition,如下:

Transition slide=TransitionInflater.from(this).inflateTransition(R.transition.slide_anim);
slide.setDuration(500);
slide.excludeTarget(R.id.appBarLayout, true);
getWindow().setEnterTransition(slide);
getWindow().setReturnTransition(slide);

也可以直接在style中设置动画


    @transition/slide_anim
    @transition/slide_anim

其中@transition/slide_anim如下:







    
    
        
        
    

    

    


4、退出时调用finishAfterTransition()

内容变换

注意:
1、Material主题默认会将exit的transition设置成null,enter的transition设置成Fade 。
2、如果reenter 或者 return transition没有明确设置,则将用exit 和enter的共享元素transition替代
4、在定义style时,对content transition使用的是android:windowEnterTransition,以前使用的是android:activityOpenEnterAnimation

3、自定义Visibility
继承Visibility,覆盖两个方法:

  • onAppear() :创建并返回一个进入场景的Animator对象。
  • onDisappear():创建并返回一个退出场景的Animator对象。

public class FABTransition extends Visibility {

    private View fab;
    private Context context;
    private static final String BOTTOM_TRANSITION_Y = "FABTransition:change_transY:transitionY";


    public FABTransition(View fab, Context context) {
        this.fab = fab;
        this.context = context;
    }

    /**
     * 收集动画的开始信息
     * @param transitionValues 只有两个成员变量view和values, view指的是我们要从哪个view上收集信息, values是用来存放我们收集到的信息的
     *                         比如: 在captureStartValues里, transitionValues.view指的就是我们在开始动画的界面上的那个view,
     *                         在captureEndValues指的就是在目标界面上的那个view
     */
    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        super.captureStartValues(transitionValues);
        int transY= (int) (context.getResources().getDisplayMetrics().density*56*2);
        transitionValues.values.put(BOTTOM_TRANSITION_Y,transY);

    }

    /**
     * 收集动画结束的信息
     */
    @Override
    public void captureEndValues(TransitionValues transitionValues) {
        super.captureEndValues(transitionValues);
        transitionValues.values.put(BOTTOM_TRANSITION_Y, 0);
    }

    /**
     * 创建一个Animator
     */
    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
        return super.createAnimator(sceneRoot, startValues, endValues);
    }

    @Override
    public Animator onAppear(ViewGroup sceneRoot, final View view, TransitionValues startValues, TransitionValues endValues) {
        if (null == startValues || null == endValues) {
            return null;
        }
        int startY= (int) startValues.values.get(BOTTOM_TRANSITION_Y);
        int endY= (int) endValues.values.get(BOTTOM_TRANSITION_Y);
        if(view==fab && startY!=endY){
            ValueAnimator valueAnimator=ValueAnimator.ofInt(startY,endY);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    Object transY= animation.getAnimatedValue();
                    if(transY!=null){
                        view.setTranslationY((Integer) transY);
                    }

                }
            });
            return valueAnimator;
        }
        return null;
    }

    @Override
    public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues, TransitionValues endValues) {
        if (null == startValues || null == endValues) {
            return null;
        }
        int startY= (int) endValues.values.get(BOTTOM_TRANSITION_Y);
        int endY= (int) startValues.values.get(BOTTOM_TRANSITION_Y);
        if(view==fab && startY!=endY){
            ValueAnimator valueAnimator=ValueAnimator.ofInt(startY,endY);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    Object transY= animation.getAnimatedValue();
                    if(transY!=null){
                        view.setTranslationY((Integer) transY);
                    }

                }
            });
            return valueAnimator;
        }

        return null;
    }
}

使用自定义的FABTransition ,为它添加一个Target:

        TransitionSet cotentTransition=new TransitionSet();
        Slide slide=new Slide(Gravity.LEFT);
        slide.setDuration(500);
        slide.excludeTarget(android.R.id.navigationBarBackground, true);
        slide.excludeTarget(android.R.id.statusBarBackground, true);
        slide.excludeTarget(R.id.appBarLayout, true);
        slide.excludeTarget(R.id.fab, true);
        cotentTransition.addTransition(slide);
        //fab进入动画
        FABTransition fabTransition=new FABTransition(fab,this);
        fabTransition.addTarget(R.id.fab);
        fabTransition.setDuration(500);
        cotentTransition.addTransition(fabTransition);
        getWindow().setEnterTransition(cotentTransition);

自定义Vilibiliyy fab

4、 共享元素动画

定义共享的view元素从一个Activity/Fragment 到另一个Activity/Fragment t是如何变化的。共享元素在进入和返回时的变换效果,可以通过window和Fragment的如下方法来设置:

  • 进入:
    setSharedElementEnterTransition()
    设置在B进入时播放的动画,共享元素以A中的位置作为起始,B中的位置为结束来播放动画。
  • 返回:
    setSharedElementReturnTransition()
    设置在B返回A时的动画,共享元素以B中的位置作为起始,A中的位置为结束来播放动画。

1、共享元素变换揭秘
我们知道,一个变换(Transition )主要有两方面的职责:

  • 捕获view开始和结束状态
  • 创建一能在两个状态间渐变的动画。

共享元素变换没有什么不同。在共享元素变换开始之前,必须首先捕获每个共享元素的开始和结束状态(调用activity以及被调用activity中的位置、大小、外观),有了这些信息才能决定每个共享元素的入场动画。

framework的共享元素变换是通过运行时改变其属性实现的,当Activity A 调用 Activity B ,发生的事件流如下:

  • Activity A调用startActivity(), Activity B被创建,测量,同时初始化为半透明的窗口和透明的背景颜色。
  • framework重新分配每个共享元素在B中的位置与大小,使其跟A中一模一样。之后,B的进入变换(enter transition)捕获到共享元素在B中的初始状态。
  • framework重新分配每个共享元素在B中的位置与大小,使其跟B中的最终状态一致。之后,B的进入变换(enter transition)捕获到共享元素在B中的结束状态。
  • B的进入变换(enter transition)比较共享元素的初始和结束状态,同时基于前后状态的区别创建一个Animator(属性动画对象)。
  • framework 命令A隐藏其共享元素,动画开始运行。随着动画的进行,framework 逐渐将B的activity窗口显示出来,当动画完成,B的窗口才完全可见。

与内容变换(content transition)取决于view的可见性不同(visibility),共享元素变换取决于每个共享元素的位置、大小以及外观。在api 21中,框架层提供了几个Transition 的实现,可以用于定义共享元素在场景中的切换效果。
ChangeBounds -捕获共享元素的layout bound,然后播放layout bound变化动画。ChangeBounds 是共享元素变换中用的最多的,因为前后两个activity中共享元素的大小和位置一般都是不同的。
ChangeTransform - 捕获共享元素的缩放(scale)与旋转(rotation)属性 ,然后播放缩放(scale)与旋转(rotation)属性变化动画。
ChangeClipBounds - 捕获共享元素clip bounds,然后播放clip bounds变化动画。
ChangeImageTransform - 捕获共享元素(ImageView)的transform matrices 属性,然后播放ImageViewtransform matrices 属性变化动画。与ChangeBounds相结合,这个变换可以让ImageView在动画中高效实现大小,形状或者ImageView.ScaleType 属性平滑过度。
@android:transition/move - 将上述所有变换同时进行的一个TransitionSet 。就如在第一章中所讲的一样,如果共享元素的进入和返回变换没有特别声明,框架将使用它作为默认的变换。

我们可以看到,共享元素变换并不是真正实现了两个activity或者Fragment之间元素的共享,实际上我们看到的几乎所有变换效果中(不管是B进入还是B返回A),共享元素都是在B中绘制出来的。Framework没有真正试图将A中的某个元素传递给B,而是采用了不同的方法来达到相同的视觉效果。A传递给B的是共享元素的状态信息。B利用这些信息来初始化共享View元素,让它们的位置、大小、外观与在A中的时候完全一致。当变换开始的时候,B中除了共享元素之外,所有的其他元素都是不可见的。随着动画的进行,framework 逐渐将B的activity窗口显示出来,当动画完成,B的窗口才完全可见。

来自于 深入理解共享元素变换(Shared Element Transition)

2、怎么使用共享元素动画

1、开启内容过渡效果

2、在activity A和activity B中,为需要共享的元素设置transitionName

android:transitionName="imageView"

或者

ViewCompat.setTransitionName(view,"imageView");

3、启动activity B

ActivityOptionsCompat options=ActivityOptionsCompat.makeSceneTransitionAnimation(this,view,ViewCompat.getTransitionName(view));
ActivityCompat.startActivity(this, intent, options.toBundle());

4、在B中设置共享元素动画

        TransitionSet transitionSet=new TransitionSet();
        
        //改变view的位置
        ChangePositionTransition changePositionTransition=new ChangePositionTransition();
        ColorTransition colorTransition=new ColorTransition(getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorAccent));
        //view做RevealTransition
        CircleShareElemEnterTransition shareElemEnterRevealTransition=new CircleShareElemEnterTransition(rightTop);

        transitionSet.addTransition(shareElemEnterRevealTransition);
        transitionSet.addTransition(colorTransition);
        transitionSet.addTransition(changePositionTransition);

        transitionSet.addTarget(R.id.rightTop);
        transitionSet.setDuration(500);
        getWindow().setSharedElementEnterTransition(transitionSet);

共享元素 动画

3、自定义Transition
继承Transition,重新以下方法:

  • void captureStartValues(TransitionValues transitionValues):收集动画的开始信息
    transitionValues:有两个成员变量view和values, transitionValues.view指的就是我们在开始动画的界面上的那个view,是从这个view上收集开始信息的, values是用来存放我们收集到的信息的
  • void captureEndValues(TransitionValues transitionValues):收集动画结束的信息
    transitionValues.view:在结束动画的界面上的那个view,是从这个view上收集结束信息的, transitionValues.values是用来存放收集到的信息的
  • Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) :创建一个Animator

例子如下:


public class ColorTransition extends Transition {

    private static final String COLOR_BACKGROUND = "ColorTransition:change_color:background";
    private int mStartColor;
    private int mEndColor;

    public ColorTransition(int mStartColor, int mEndColor) {
        this.mStartColor = mStartColor;
        this.mEndColor = mEndColor;
    }

    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        transitionValues.values.put(COLOR_BACKGROUND,mStartColor);
    }

    @Override
    public void captureEndValues(TransitionValues transitionValues) {
        transitionValues.values.put(COLOR_BACKGROUND,mEndColor);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
        if (null == startValues || null == endValues) {
            return null;
        }

        final View view = endValues.view;

        int startColor = (int) startValues.values.get(COLOR_BACKGROUND);
        int endColor = (int) endValues.values.get(COLOR_BACKGROUND);

        if (startColor != endColor) {
            ValueAnimator animator = ValueAnimator.ofArgb(startColor, endColor);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    Object value = animation.getAnimatedValue();
                    if (null != value) {
                        view.setBackgroundColor((Integer) value);
                    }
                }
            });
            return animator;
        }
        return null;
    }
}

4、有时候为了实现一些特殊的效果,需要将activity默认的背景色设置为透明

    

Activity B(蓝色)的style如上

5、使用SharedElementCallback
监控共享元素动画,在共享元素进入和退出的时候都会回调,调用方法如下:

setEnterSharedElementCallback(SharedElementCallback)

常用的方法如下:

  • onMapSharedElements:装载共享元素
  • onSharedElementStart :共享元素开始时候回调,一般是进入的时候使用
  • onSharedElementEnd :共享元素结束的时候回调,一般是退出的时候使用

一般系统会帮我们默认实现,但是,当从activity B中返回的view不是进入activity B的共享元素时,需要手动实现 ,如下图效果

 

共享元素

1、在activity A中调用SharedElementCallback,

ActivityCompat.setExitSharedElementCallback(MainActivity.this,sharedElementCallback);

在onMapSharedElements重新封装SharedElement(从activity B中返回的两个view不同)


    private SharedElementCallback sharedElementCallback=new SharedElementCallback() {
        @Override
        public void onMapSharedElements(List names, Map sharedElements) {
             //mTmpReenterState!=null,是reenter的时候调用的;mTmpReenterState==null,是exit的时候调用的
            if(mTmpReenterState!=null){
                int startingPosition = mTmpReenterState.getInt(EXTRA_STARTING_ALBUM_POSITION);
                int currentPosition = mTmpReenterState.getInt(EXTRA_CURRENT_ALBUM_POSITION);
                if (startingPosition != currentPosition) {
                    // If startingPosition != currentPosition the user must have swiped to a
                    // different page in the DetailsActivity. We must update the shared element
                    // so that the correct one falls into place.
                    String newTransitionName = data.get(currentPosition);
                    View newSharedElement = recyclerView.findViewWithTag(newTransitionName);
                    if (newSharedElement != null) {
                        names.clear();
                        names.add(newTransitionName);
                        sharedElements.clear();
                        sharedElements.put(newTransitionName, newSharedElement);
                    }
                }
                mTmpReenterState = null;
            }
        }
    };

2、在activity B中调用SharedElementCallback

ActivityCompat.setEnterSharedElementCallback(BigImgGalleryActivity.this,callback);

如果退出时的共享元素view和进入时的不一致,重新装载sharedElement,返回

    private SharedElementCallback callback=new SharedElementCallback() {
        @Override
        public void onMapSharedElements(List names, Map sharedElements) {
            BigImgFragment fragment=adapter.getCurrentFragment();
            ImageView sharedElement =fragment.getAlbumImage();
            if(startPosition!=currentPosition){
                names.clear();
                sharedElements.clear();
                String name=ViewCompat.getTransitionName(sharedElement);
                names.add(name);
                sharedElements.put(name,sharedElement);
            }
        }
    };

3、在activity B中返回进入、退出位置

    @Override
    public void finishAfterTransition() {
        Intent data = new Intent();
        data.putExtra(EXTRA_STARTING_ALBUM_POSITION, startPosition);
        data.putExtra(EXTRA_CURRENT_ALBUM_POSITION, currentPosition);
        setResult(RESULT_OK, data);
        super.finishAfterTransition();
    }

4、接受从B中返回的数据

    @Override
    public void onActivityReenter(int resultCode, Intent data) {
        super.onActivityReenter(resultCode, data);
        mTmpReenterState=data.getExtras();
        ...
    }

6、 SharedElementCallback和TransitionListener
为共享元素动画同时添加了SharedElementCallback和TransitionListener,执行顺序如下图, 可以在共享元素动画结束后,在TransitionListener里做一些其他的操作。比如上图中,对于共享元素view,在加载大图之前,我先去加载了一张小图,在共享元素动画执行完毕后,在去显示大图...

        ActivityCompat.setEnterSharedElementCallback(BigImgGalleryActivity.this,callback);
  
        Transition transition=getWindow().getSharedElementEnterTransition();
        transition.addListener(new SimpleTransitionListener() {
            @Override
            public void onTransitionStart(Transition transition) {
                Log.e("TransitionListener","onTransitionStart");
            }
        });
        

SharedElementCallback和TransitionListener

7、实现共享元素
可以将需要传递的数据信息(如图片的url和bitmap的映射)封装在一个静态的类中,在详情页根据url取bitmap。如,在列表页,添加map,在详情页,根据可以显示bitmap。但是不要忘记clear。

public class ShareImgMap {
    private static Map> map=new HashMap<>();
    public static  void addImg(String key,Bitmap bitmap){
        if (!bitmap.isRecycled()){
            SoftReference softReference=new SoftReference(bitmap);
            map.put(key,softReference);
        }
    }
    public static Bitmap getImg(String key){
        if(map.get(key)==null){
            return null;
        }
        Bitmap bitmap=map.get(key).get();
        if(bitmap==null || bitmap.isRecycled()){
            return null;
        }
        return bitmap;
    }
    public static  void  clear(){
        map.clear();
    }
}

参考:Activity之间传递大数据问题

8、还有好多问题不理解???
比如,怎么定义一个共享元素view,从圆形到方形的Transition(不用CircularReveal,希望的效果是圆形->圆角矩形->方形的Transition)?。。。

以后再说吧。。。

参考:
Android 过渡(Transition)动画解析之基础篇、深入理解Android L新特性之 页面内容&共享元素过渡动画、深入理解共享元素变换(Shared Element Transition)、深入理解Content Transition ...

参考:
https://material.google.com/motion/material-motion.html#
http://www.jianshu.com/p/98f2ec280945
http://wiki.jikexueyuan.com/project/android-training-geek/animations.html
https://developer.android.com/reference/android/transition/Transition.html
Android的Activity屏幕切换动画(一)-左右滑动切换
利用Theme自定义Activity间的切换动画

 

你可能感兴趣的:(动画)