假设,两个有Activity A和B
- A启动B: A发生exit动画,B发生enter动画
- B返回A:B发生return动画,A发生reenter动画
1、使用overridePendingTransition
activity之间切换可以使用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,来实现转场动画,如下:
<item name="android:windowAnimationStyle">@style/ActivityAnim</item>
---
<style name="ActivityAnim" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/enter_anim</item>
<item name="android:activityOpenExitAnimation">@anim/exit_anim</item>
<item name="android:activityCloseEnterAnimation">@anim/close_enter_anim</item>
<item name="android:activityCloseExitAnimation">@anim/close_exit_anim</item>
</style>
- 在A启动B时:
activityOpenEnterAnimation:B进入的动画
android:activityOpenExitAnimation:A退出的动画 - 在B后退回A时:
activityCloseEnterAnimation:A重新进入的动画
activityCloseExitAnimation:B退出的动画
关于activityOpenEnterAnimation和windowEnterAnimation?
Activity就是一个可视的人机交互界面。 每一个Activity都有一个默认的Window,一般来讲,这个Window都是全屏的,当然也有例外,比如Dialog的Window就是非全屏的。他们分别设置的是activity和window的动画
定义Window进入和退出效果(及Window,Activity,View的理解)
@anim/enter_anim
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:duration="500"
android:fromXDelta="100%p"
android:toXDelta="0%p"/>
</set>
---
@anim/exit_anim
<translate android:duration="500"
android:fromXDelta="0%p"
android:toXDelta="-100%p"/>
---
@anim/close_enter_anim
<translate android:duration="500"
android:fromXDelta="-100%p"
android:toXDelta="0%p"/>
---
@anim/close_exit_anim
<translate android:duration="500"
android:fromXDelta="0%p"
android:toXDelta="100%p"/>
注意:
1、windowAnimationStyle需要继承的Animation.Activity,不然一些动画效果就没有了
2、 以上方法(1、2)设置的动画只能针对页面中的所有元素
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
2、怎么使用Content Transition
1、开启内容过渡效果
<item name="android:windowContentTransitions">true</item>
还可以设置是否覆盖执行,即是否同步执行还是顺序执行
<!--A退出的动画和B进入的动画同步进行-->
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<!--B返回的动画和A重新进入的动画同步进行-->
<item name="android:windowAllowReturnTransitionOverlap">true</item>
同样,也有对应的setWindowAllowEnterTransitionOverlap() 、setWindowAllowReturnTransitionOverlap()方法。
默认情况下,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中设置动画
<item name="android:windowExitTransition">@transition/slide_anim</item>
<item name="android:windowEnterTransition">@transition/slide_anim</item>
其中@transition/slide_anim如下:
<!-- @transition/slide_anim-->
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
>
<!--顶部的状态栏以及底部的导航栏不执行动画-->
<targets>
<target android:excludeId="@android:id/statusBarBackground"/>
<target android:excludeId="@android:id/navigationBarBackground"/>
</targets>
<slide android:slideEdge="bottom"
android:duration="1300"/>
<!--<fade />-->
</transitionSet>
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);
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默认的背景色设置为透明
<style name="TranslucentBg" parent="AppTheme.NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
</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<String> names, Map<String, View> 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<String> names, Map<String, View> 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");
}
});
7、实现共享元素
可以将需要传递的数据信息(如图片的url和bitmap的映射)封装在一个静态的类中,在详情页根据url取bitmap。如,在列表页,添加map,在详情页,根据可以显示bitmap。但是不要忘记clear。
public class ShareImgMap {
private static Map<String,SoftReference<Bitmap>> map=new HashMap<>();
public static void addImg(String key,Bitmap bitmap){
if (!bitmap.isRecycled()){
SoftReference<Bitmap> softReference=new SoftReference<Bitmap>(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();
}
}
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间的切换动画