11.1 问题
应用程序需要自定义Activity切换或Fragment切换时产生的过渡动画。
11.2 解决方案
(API Level 5)
要修改Activity间的过渡动画,可以使用overridePendingTransition()API进行某次切换时的动画,或者在应用程序的主题中声明自定义动画值来进行更多全局设置。要修改Fragment间的过渡动画,可以使用onCreateAnimation()或onCreateAnimator()API方法。
11.3 实现机制
1.Activity
要自定义Activity切换时的过渡动画,可以考虑4种动画:打开一个新Activity时的进入动画和退出动画,以及当前Activity关闭时的进入动画和退出动画。每种动画都会应用到过渡动画中所涉及的两个Activity之一。例如,当打开一个新的Activity时,当前Activity将会运行“打开退出”动画,而新Activity会运行“打开进入”动画。由于这些动画都是同时运行的,因此动画间应该是互补的,否则看起来会不太协调。以下四段代码演示了这4种动画。
res/anim/activity_open_enter.xml
android:fromDegrees="90" android:toDegrees="0"
android:pivotX="0%" android:pivotY="0%"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
res/anim/activity_open_exit.xml
android:fromDegrees="0" android:toDegrees="-90"
android:pivotX="0%" android:pivotY="0%"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
android:fromAlpha="1.0" android:toAlpha="0.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
res/anim/activity_close_enter.xml
android:fromDegrees="-90" android:toDegrees="0"
android:pivotX="0%p" android:pivotY="0%p"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
res/anim/activity_close_exit.xml
android:fromDegrees="0" android:toDegrees="90"
android:pivotX="0%p" android:pivotY="0%p"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
android:fromAlpha="1.0" android:toAlpha="0.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
我们创建了两个“打开”动画,即旧的Activity顺时针旋转消失、新的Activity顺时针旋转进入。补足的“关闭”动画会将当前Activity逆时针旋转退出、之前的Activity逆时针旋转进入。每个动画还有渐出或渐入的效果,这样过渡动画看起来会更加流畅。要在特定时刻应用这些自定义动画,可以在startActivity()或finish()后立刻调用overridePendingTransition()方法,如下所示:
//使用自定义过渡动画启动一个新的Activity
Intent intent = new Intent(...);
startActivity(intent);
overridePendingTransition(R.anim.activity_open_enter,R.anim.activity_open_exit);
//使用自定义过渡动画关闭当前Activity
finish();
overridePendingTransition(R.anim.activity_close_enter,R.anim.activity_close_exit);
这种方式在只希望在某些场合使用自定义过渡动画的情况下非常有用。但如果希望在应用程序中自定义每个Activity的过渡动画,到处调用这个方法可能会有点麻烦。反之,最好在应用程序的主题中使用自定义的动画。以下代码演示了一个自定义的主题,该主题可以全局使用这些过渡动画。
res/values/styles.xml
@style/ActivityAnimation
@anim/activity_open_enter
@anim/activity_open_exit
@anim/activity_close_enter
@anim/activity_close_exit
通过提供一个主题的自定义属性android:windowAnimationStyle值,我们可以自定义这些过渡动画。引用框架的父样式也很重要,因为这4种动画并不是该样式中唯一定义的内容,否则可能会无意中去除现有的一些窗口动画。
2. 支持Fragment
自定义Fragment间的过渡动画会有些不同,这取决于你是否使用了支持库。这是因为原生的Fragment使用了新的Animator对象,该对象在支持库的Fragment版本中是不支持的。
使用支持库时,可以通过调用setCustomAnimations()覆写单个FragmentTransaction的过渡动画。该方法的接受两个参数的版本可以设置添加/替换/移除动作时的动画效果,但在界面栈回退时不能设置相应的动画。该方法的接受4个参数的版本则可以为界面栈的回退添加自定义的动画。还是使用之前示例中一样的Animation对象,下面的代码显色了如何将这些动画添加到FragmentTransaction中:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
//首先必须调用该方法!
ft.setCustomAnimations(R.anim.activity_open_enter,R.anim.activity_open_exit, R.anim.activity_close_enter, R.anim.activity_close_exit);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
mit();
重点:
setCustomAnimations()必须在add()、replace()和其他动作方法之前调用,否则动画将不会运行。最好是在每个事务代码块开始时就调用该方法。
如果希望对某个Fragment一直使用同样的动画,可能需要覆写Fragment中的onCreateAnimation()方法。以下代码显示了使用了这种方式定义的Fragment动画。
使用自定义动画的Fragment
public class SupportFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
TextView tv = new TextView(getActivity());
tv.setText("Fragment");
tv.setBackgroundColor(Color.RED);
return tv;
}
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
switch (transit) {
case FragmentTransaction.TRANSIT_FRAGMENT_FADE:
if (enter) {
return AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_in);
} else {
return AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_out);
}
case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
if (enter) {
return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_close_enter);
} else {
return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_close_exit);
}
case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
default:
if (enter) {
return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_open_enter);
} else {
return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_open_exit);
}
}
}
}
Fragment的动画行为和FragmentTransaction的设置有很大的关系。有很多的过渡值可以通过setTransition()方法关联到事务上。如果没有调用setTransition(),Fragment就无法知道打开动画集和关闭动画集的区别,因此我们唯一知道的就是运行进入动画还是退出动画。
要获得之前通过setCustomAnimations()实现相同的效果,需要将事务的过渡值设为TRANSIT_FRAGMENT_OPEN。这时会使用这个过渡值调用初始的事务,但同时会通过TRANSIT_FRAGMENT_CLOSE调用界面栈回退动作,这样就允许Fragment提供不同的动画。以下代码片段演示了如何用这种方式构造一个事务:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
//设置过渡值来触发相应的动画
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
mit();
Fragment还有第三个状态,这在Activity中是没有的,它是通过TRANSIT_FRAGMENT_FADE过渡值定义的。这个动画是在过渡行为不再是变化的一部分时出现的,例如添加或替换但在Fragment隐藏或显示是不会出现。在这个示例中,我们使用标准的系统渐变动画来诠释这种情形。
3.本地Fragment
如果应用程序的目标版本是API Level 11 或之后版本,则不必使用支持库中的Fragment,而且这种情况下的自定义动画代码会稍微有些不同。本地Fragment实现使用相对较新的Animator对象(而非旧的Animator对象)来创建过渡动画。
这需要对代码做一些修改;首先,需要使用Animator来定义所有的XML动画。参见以下四段代码:
res/animator/fragment_exit.xml
android:valueFrom="0" android:valueTo="-90"
android:valueType="floatType"
android:propertyName="rotation"
android:duration="500"/>
android:valueFrom="1.0" android:valueTo="0.0"
android:valueType="floatType"
android:propertyName="alpha"
android:duration="500"/>
res/animator/fragment_enter.xml
android:valueFrom="90" android:valueTo="0"
android:valueType="floatType"
android:propertyName="rotation"
android:duration="500"/>
android:valueFrom="0.0" android:valueTo="1.0"
android:valueType="floatType"
android:propertyName="alpha"
android:duration="500"/>
res/animator/fragment_pop_exit.xml
android:valueFrom="0" android:valueTo="90"
android:valueType="floatType"
android:propertyName="rotation"
android:duration="500"/>
android:valueFrom="1.0" android:valueTo="0.0"
android:valueType="floatType"
android:propertyName="alpha"
android:duration="500"/>
res/animator/fragment_pop_enter.xml
android:valueFrom="-90" android:valueTo="0"
android:valueType="floatType"
android:propertyName="rotation"
android:duration="500"/>
android:valueFrom="0.0" android:valueTo="1.0"
android:valueType="floatType"
android:propertyName="alpha"
android:duration="500"/>
除了语法的细微区别,这些动画几乎和之前创建的动画一模一样。其他仅有的差别是,这些动画被设置为围绕在视图的中心(默认行为)而不是左上角。
和以前一样,可以通过setCustomAnimations()直接设置某个FragmentTransaction单独的过渡动画;但是,新的版本使用了我们的Animator实例。下面的代码片段使用新API实现了这一过程:
FragmentTransaction ft = getFragmentManager().beginTransaction();
//首先必须调用该方法!
ft.setCustomAnimations(R.animator.fragment_enter,R.animator.fragment_exit,R.animator.fragment_pop_enter, R.animator.fragment_pop_exit);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
mit();
如果想要对某一特定子类总是使用相同的过渡动画,可以像以前一样自定义Fragment。但本地Fragment没有onCreateAnimation()方法,而是使用了onCreateAnimator()。查看以下代码,它使用新的API重新定义了我们创建的Fragment。
自定义过渡动画的本地Fragment
public class NativeFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
TextView tv = new TextView(getActivity());
tv.setText("Fragment");
tv.setBackgroundColor(Color.BLUE);
return tv;
}
@Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
switch (transit) {
case FragmentTransaction.TRANSIT_FRAGMENT_FADE:
if (enter) {
return AnimatorInflater.loadAnimator(getActivity(), android.R.animator.fade_in);
} else {
return AnimatorInflater.loadAnimator(getActivity(), android.R.animator.fade_out);
}
case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
if (enter) {
return AnimatorInflater.loadAnimator(getActivity(), R.animator.fragment_pop_enter);
} else {
return AnimatorInflater.loadAnimator(getActivity(), R.animator.fragment_pop_exit);
}
case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
default:
if (enter) {
return AnimatorInflater.loadAnimator(getActivity(), R.animator.fragment_enter);
} else {
return AnimatorInflater.loadAnimator(getActivity(), R.animator.fragment_exit);
}
}
}
}
同样,会像支持库示例一样的检查同样的过渡值,我们只是返回Animator实例。下面同样的代码片段可以使用过渡值集开始一个事务:
FragmentTransaction ft = getFragmentManager().beginTransaction();
//设置过渡值触发相应的动画
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
mit();
将这些自定义动画全局地应用到整个应用程序的最终方式就是将这些动画关联到应用程序的主题上。以下代码显示了使用我们的Fragment动画的自定义主题。
res/values/styles.xml
@style/FragmentAnimation
@animator/fragment_enter
@animator/fragment_exit
@animator/fragment_pop_enter
@animator/fragment_pop_exit
@android:animator/fade_in
@android:animator/fade_out
正如你所看到的,一个主题默认的Fragment动画属性就是相同的windowAnimationStyle属性的一部分。因此,我们在自定义时要保证继承自同样的父样式,否则会移除其他的一些系统默认效果(例如Activity过渡动画)。必须依然要在FragmentTransaction中请求相应的过渡类型,从而触发相应的动画。
如果想要在主题中同时自定义Activity和Fragment的过度动画,可以将它们一起放到一个相同的自定义样式中(参见以下代码)。
res/values/styles.xml
@style/TransitionAnimation
@anim/activity_open_enter
@anim/activity_open_exit
@anim/activity_close_enter
@anim/activity_close_exit
@animator/fragment_enter
@animator/fragment_exit
@animator/fragment_pop_enter
@animator/fragment_pop_exit
@android:animator/fade_in
@android:animator/fade_out
警告:
对主题添加的Fragment过渡动画只会作用于本地Fragment实现。支持库中的Fragment因为早期的平台版本并没有这些属性,所有也找不到这些属性。