前言
使用Transition动画框架,可以帮你做到:
1不同Activity切换的时候,根据每个activity对应的layout内容的不同做整体的场景变换的动画。
2 不同activity切换的时候,不同activity对应的layout有相同的元素,比如activity1中有一个button,activity2有一个相同的button,transition框架可以在整体场景变换的同时,特别照顾到这个button,让动画变换的过程中,这个button享有视觉连贯性。
3 在相同的activity里,当同一个view内容发生变化,比如在代码中removie或者add了某个ui元素,或者更改了某个已有元素的尺寸,颜色信息,Transition动画框架也可以根据这个变化做动画变换。
一 不同Activity切换的Transition动画
当从activity1跳转到activity2的时候,1会执行exitTransition, 而2会执行enterTransition,你可以在代码中使用
为这个activity设置它进入和退出时需要用到的过场动画(就是方法参数),谷歌为我们预设了几个transition,分别是Explode,Slide,Fade。可以看一下他们的效果getWindow().setExitTransition(slide/fade...); getWindow().setEnterTransition(slide/fade...);
| Explode | Slide | Fade |
|---|---|---|
![]() | ![]() | ![]() |
在定义Explode,Slide,Fade这几个动画效果的时候,可以使用xml也可以直接在代码中定义
1 使用xml
在res/transtion文件夹下定义一个xml文件
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"/>
然后需要在代码中使用TransitionInflator方法来加载这个xml文件生成对应的动画效果。
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); }2 在代码中定义
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); }效果如下
在这个过程中
首先Actvity1启动了Activity2
Transition框架发现Activity1退出,为activity1里的可见ui组件执行Slide动画
Transition框架发现Activity2退出,为activity2里的可见ui组件执行Fade动画
当按下返回键,如果我们没有为其设置returnTransition和reenterTransition选项的话,Transition框架会对应的activity执行我们为其设置的enter和exit动画运动过程相反的动画作为默认选项。
3 ReturnTransition & ReenterTransition
他们是enterTransition和exitTransition的对应![]()
我们把之前那个例子改一下,为TransitionActivity添加一个return动画,用的是Slide,那么当我们按下返回键的时候,会看到一个slide效果,而不是fade(之前设置的enter动画的倒放)效果。下面可以看到 没有设置return动画和设置了return动画的区别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); }
Without Return Transition With Return Transition Enter: Fade InEnter: Fade InExit: Fade OutExit: Slide out
二 Activity之间的享元切换
你可以通过为两个不同activity中某个view设置一个唯一的统一的标识,告诉Transition这个view是这两aictivity共享的元素,变换的时候要额外照顾到,那么transition动画框架就会在场景切换的时候,让这个view顺滑的从前一个activity转移到另一个activity(注意,这里的转移并不是真正的转移,这两个item并不是同一个对象,他们是彼此独立的两个view)
1 设置windowContentTransition
首先要对app的style文件做一些修改
values/styles.xml <style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar"> ... <item name="android:windowContentTransitions">true</item ... </style>
还可以在这里定义整个app默认的 enter, exit 和 shared element transitions动画<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>
2在布局文件中为享元view定义相同的Transition Name,通过android:transitioName来定义,享元之间id和属性可以不同,但是TransitionName必须相同。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和Transition Name
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()); } });
最终效果如下图
三 在Fragment之间进行享元切换动画
前两步和上面基本相同
1 设置windowContentTransition
2 在布局文件中为享元view定义相同的Transition Namevalues/styles.xml <style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar"> ... <item name="android:windowContentTransitions">true</item> ... </style>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" />
3 享元方式启动另一个FragmentFragmentB 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();
最终结果如下
四 叠加Transition动画效果
你可以设置是否让exit动画和enter动画叠加在一起执行
如果设置为true 那么enter动画会立刻执行
如果设置为false 那么exter动画会等到exit动画执行完毕之后再执行
这对Fragment和Activity之间的享元切换都有效
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 appears on top of Fragment_1 | Fragment_2 waits until Fragment_1 is gone |
![]() | ![]() |
1 Scene
在activity对应的布局文件中,定义一个占位用的视图,一般用framelayout,称之为稍后场景变换动画执行
所在的根视图rootView,然后在layout文件夹下,新建几个layout文件,每一个layout文件对应的视图都是一个scene,
可以这么理解,我们打算在Activity对应的界面视图的上半部分放置即将运行的动画,这个上半部分我们用帧布局
占位,称之为rootView,然后动画效果是一个小球从左边移动到右边,那么我们只需要在layout文件夹下面
定义两个layout文件,一个layout文件中,小球在左边,另一个文件中小球在右边。这两个layout,在代码中通过
Scene.getSceneForLayout(rootView,R.layout.***,this)生成一个Scene,此时,rootView和每一个scene的链接关系
就建立起来了。在通过TransitionManager.go方法就可以让动画在rootView中运行起来。
还是看一下代码吧
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中的四个Scene中间生成对应的动画,而且每个动画都不同

2 布局文件中View属性的变动
Transition动画还可以检测到同一个布局文件中某些视图元素的属性变化,然后做相应的动画效果,你可以随意在代码中修改
你需要的一切变动,比如颜色,大小,位置之类的,之后的动画Transition会自动帮你实现。
做到这些 只需要
a 先调用BeginDelayTransition方法
TransitionManager.beginDelayedTransition(sceneRoot);b 更改layout布局中的view属性
ViewGroup.LayoutParams params = greenIconView.getLayoutParams();
params.width = 200;
greenIconView.setLayoutParams(params);更改greenIconView的width时,会触发view的onMeasure()方法,此时Transition框架会记录下该view的起始width值并
做出相应的动画

六 享元动画+circle Reveal动画
享元方式的过场动画前面已经提到了,而CircleReveal是属性动画的一种,和享元切换一样都是安卓5.0之后引入的新特性
,对了忘了提,上面享元切换和这个circleReveal动画在执行前最好都加上一个版本判断
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
}CircleReveal实现圆形缩放效果,可用来突出显示某个部分。他和享元切换一起使用可以引导用户的视觉焦点,给他的点击以反馈。

上面的动画 依次执行了
从MainActivity到RevealActivity的一个享元切换动画
在RevealActivity中有一个监听器,当它监听到RevealActivity中的享元切换动画执行完之后,为Toobar执行了一个CircleReveal属性动画
同时为RevealActivity中其他视图执行了一个放大的属性动画
Listen to shared element enter transition end
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();
}
(...)
});Reveal 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();
}Scale up activity layout views
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);
}
}更多CircleReveal动画的例子
你可以随意设置你想要的Reveal动画,但前提是这些动画可以有效的告知用户 app对用户的点击都做了什么反馈
1 从目标视图中心开始动画

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();2 从目标视图顶部开始 并结合其他属性动画

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();3 从点击位置开始动画

@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();
}4 结合Transition动画

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);

本文详细介绍了Android平台上的Transition动画框架的应用,包括不同Activity间的过渡动画、共享元素动画、Fragment动画及同一Activity内视图变化动画等,并展示了如何实现CircleReveal动画。








1万+

被折叠的 条评论
为什么被折叠?



