前言
使用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,你可以在代码中使用
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
res/transition/activity_slide.xml
然后需要在代码中使用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);
}
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动画运动过程相反的动画作为默认选项。
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动画和设置了return动画的区别
Without Return Transition | With Return Transition |
---|---|
Enter: Fade In |
Enter: Fade In |
Exit: Fade Out |
Exit: Slide out |
你可以通过为两个不同activity中某个view设置一个唯一的统一的标识,告诉Transition这个view是这两aictivity共享的元素,变换的时候要额外照顾到,那么transition动画框架就会在场景切换的时候,让这个view顺滑的从前一个activity转移到另一个activity(注意,这里的转移并不是真正的转移,这两个item并不是同一个对象,他们是彼此独立的两个view)
1 设置windowContentTransition
首先要对app的style文件做一些修改
values/styles.xml
layout/activity_a.xml
layout/activity_b.xml
通过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
values/styles.xml
2 在布局文件中为享元view定义相同的Transition Name
layout/fragment_a.xml
layout/fragment_b.xml
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();
四 叠加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;
}
}
2 布局文件中View属性的变动
Transition动画还可以检测到同一个布局文件中某些视图元素的属性变化,然后做相应的动画效果,你可以随意在代码中修改
你需要的一切变动,比如颜色,大小,位置之类的,之后的动画Transition会自动帮你实现。
做到这些 只需要
a 先调用BeginDelayTransition方法
TransitionManager.beginDelayedTransition(sceneRoot);
ViewGroup.LayoutParams params = greenIconView.getLayoutParams();
params.width = 200;
greenIconView.setLayoutParams(params);
做出相应的动画
六 享元动画+circle Reveal动画
享元方式的过场动画前面已经提到了,而CircleReveal是属性动画的一种,和享元切换一样都是安卓5.0之后引入的新特性
,对了忘了提,上面享元切换和这个circleReveal动画在执行前最好都加上一个版本判断
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
}
上面的动画 依次执行了
从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);
}
}
你可以随意设置你想要的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();
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);