11.1 問(wèn)題
應(yīng)用程序需要自定義Activity切換或Fragment切換時(shí)產(chǎn)生的過(guò)渡動(dòng)畫(huà)。
11.2 解決方案
(API Level 5)
要修改Activity間的過(guò)渡動(dòng)畫(huà),可以使用overridePendingTransition()API進(jìn)行某次切換時(shí)的動(dòng)畫(huà),或者在應(yīng)用程序的主題中聲明自定義動(dòng)畫(huà)值來(lái)進(jìn)行更多全局設(shè)置。要修改Fragment間的過(guò)渡動(dòng)畫(huà),可以使用onCreateAnimation()或onCreateAnimator()API方法。
11.3 實(shí)現(xiàn)機(jī)制
1.Activity
要自定義Activity切換時(shí)的過(guò)渡動(dòng)畫(huà),可以考慮4種動(dòng)畫(huà):打開(kāi)一個(gè)新Activity時(shí)的進(jìn)入動(dòng)畫(huà)和退出動(dòng)畫(huà),以及當(dāng)前Activity關(guān)閉時(shí)的進(jìn)入動(dòng)畫(huà)和退出動(dòng)畫(huà)。每種動(dòng)畫(huà)都會(huì)應(yīng)用到過(guò)渡動(dòng)畫(huà)中所涉及的兩個(gè)Activity之一。例如,當(dāng)打開(kāi)一個(gè)新的Activity時(shí),當(dāng)前Activity將會(huì)運(yùn)行“打開(kāi)退出”動(dòng)畫(huà),而新Activity會(huì)運(yùn)行“打開(kāi)進(jìn)入”動(dòng)畫(huà)。由于這些動(dòng)畫(huà)都是同時(shí)運(yùn)行的,因此動(dòng)畫(huà)間應(yīng)該是互補(bǔ)的,否則看起來(lái)會(huì)不太協(xié)調(diào)。以下四段代碼演示了這4種動(dòng)畫(huà)。
res/anim/activity_open_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:fromDegrees="90" android:toDegrees="0"
android:pivotX="0%" android:pivotY="0%"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
<alpha
android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
</set>
res/anim/activity_open_exit.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:fromDegrees="0" android:toDegrees="-90"
android:pivotX="0%" android:pivotY="0%"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
<alpha
android:fromAlpha="1.0" android:toAlpha="0.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
</set>
res/anim/activity_close_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
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" />
<alpha
android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
</set>
res/anim/activity_close_exit.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<rotate
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" />
<alpha
android:fromAlpha="1.0" android:toAlpha="0.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="500" />
</set>
我們創(chuàng)建了兩個(gè)“打開(kāi)”動(dòng)畫(huà),即舊的Activity順時(shí)針旋轉(zhuǎn)消失、新的Activity順時(shí)針旋轉(zhuǎn)進(jìn)入。補(bǔ)足的“關(guān)閉”動(dòng)畫(huà)會(huì)將當(dāng)前Activity逆時(shí)針旋轉(zhuǎn)退出、之前的Activity逆時(shí)針旋轉(zhuǎn)進(jìn)入。每個(gè)動(dòng)畫(huà)還有漸出或漸入的效果,這樣過(guò)渡動(dòng)畫(huà)看起來(lái)會(huì)更加流暢。要在特定時(shí)刻應(yīng)用這些自定義動(dòng)畫(huà),可以在startActivity()或finish()后立刻調(diào)用overridePendingTransition()方法,如下所示:
//使用自定義過(guò)渡動(dòng)畫(huà)啟動(dòng)一個(gè)新的Activity
Intent intent = new Intent(...);
startActivity(intent);
overridePendingTransition(R.anim.activity_open_enter,R.anim.activity_open_exit);
//使用自定義過(guò)渡動(dòng)畫(huà)關(guān)閉當(dāng)前Activity
finish();
overridePendingTransition(R.anim.activity_close_enter,R.anim.activity_close_exit);
這種方式在只希望在某些場(chǎng)合使用自定義過(guò)渡動(dòng)畫(huà)的情況下非常有用。但如果希望在應(yīng)用程序中自定義每個(gè)Activity的過(guò)渡動(dòng)畫(huà),到處調(diào)用這個(gè)方法可能會(huì)有點(diǎn)麻煩。反之,最好在應(yīng)用程序的主題中使用自定義的動(dòng)畫(huà)。以下代碼演示了一個(gè)自定義的主題,該主題可以全局使用這些過(guò)渡動(dòng)畫(huà)。
res/values/styles.xml
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="ActivityTheme" parent="@style/Theme.AppCompat.Light">
<item name="android:windowAnimationStyle">@style/ActivityAnimation</item>
</style>
<style name="ActivityAnimation" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/activity_open_enter</item>
<item name="android:activityOpenExitAnimation">@anim/activity_open_exit</item>
<item name="android:activityCloseEnterAnimation">@anim/activity_close_enter</item>
<item name="android:activityCloseExitAnimation">@anim/activity_close_exit</item>
</style>
</resources>
通過(guò)提供一個(gè)主題的自定義屬性android:windowAnimationStyle值,我們可以自定義這些過(guò)渡動(dòng)畫(huà)。引用框架的父樣式也很重要,因?yàn)檫@4種動(dòng)畫(huà)并不是該樣式中唯一定義的內(nèi)容,否則可能會(huì)無(wú)意中去除現(xiàn)有的一些窗口動(dòng)畫(huà)。
2. 支持Fragment
自定義Fragment間的過(guò)渡動(dòng)畫(huà)會(huì)有些不同,這取決于你是否使用了支持庫(kù)。這是因?yàn)樵腇ragment使用了新的Animator對(duì)象,該對(duì)象在支持庫(kù)的Fragment版本中是不支持的。
使用支持庫(kù)時(shí),可以通過(guò)調(diào)用setCustomAnimations()覆寫(xiě)單個(gè)FragmentTransaction的過(guò)渡動(dòng)畫(huà)。該方法的接受兩個(gè)參數(shù)的版本可以設(shè)置添加/替換/移除動(dòng)作時(shí)的動(dòng)畫(huà)效果,但在界面?;赝藭r(shí)不能設(shè)置相應(yīng)的動(dòng)畫(huà)。該方法的接受4個(gè)參數(shù)的版本則可以為界面棧的回退添加自定義的動(dòng)畫(huà)。還是使用之前示例中一樣的Animation對(duì)象,下面的代碼顯色了如何將這些動(dòng)畫(huà)添加到FragmentTransaction中:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
//首先必須調(diào)用該方法!
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);
ft.commit();
重點(diǎn):
setCustomAnimations()必須在add()、replace()和其他動(dòng)作方法之前調(diào)用,否則動(dòng)畫(huà)將不會(huì)運(yùn)行。最好是在每個(gè)事務(wù)代碼塊開(kāi)始時(shí)就調(diào)用該方法。
如果希望對(duì)某個(gè)Fragment一直使用同樣的動(dòng)畫(huà),可能需要覆寫(xiě)Fragment中的onCreateAnimation()方法。以下代碼顯示了使用了這種方式定義的Fragment動(dòng)畫(huà)。
使用自定義動(dòng)畫(huà)的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的動(dòng)畫(huà)行為和FragmentTransaction的設(shè)置有很大的關(guān)系。有很多的過(guò)渡值可以通過(guò)setTransition()方法關(guān)聯(lián)到事務(wù)上。如果沒(méi)有調(diào)用setTransition(),F(xiàn)ragment就無(wú)法知道打開(kāi)動(dòng)畫(huà)集和關(guān)閉動(dòng)畫(huà)集的區(qū)別,因此我們唯一知道的就是運(yùn)行進(jìn)入動(dòng)畫(huà)還是退出動(dòng)畫(huà)。
要獲得之前通過(guò)setCustomAnimations()實(shí)現(xiàn)相同的效果,需要將事務(wù)的過(guò)渡值設(shè)為T(mén)RANSIT_FRAGMENT_OPEN。這時(shí)會(huì)使用這個(gè)過(guò)渡值調(diào)用初始的事務(wù),但同時(shí)會(huì)通過(guò)TRANSIT_FRAGMENT_CLOSE調(diào)用界面?;赝藙?dòng)作,這樣就允許Fragment提供不同的動(dòng)畫(huà)。以下代碼片段演示了如何用這種方式構(gòu)造一個(gè)事務(wù):
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
//設(shè)置過(guò)渡值來(lái)觸發(fā)相應(yīng)的動(dòng)畫(huà)
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
ft.commit();
Fragment還有第三個(gè)狀態(tài),這在Activity中是沒(méi)有的,它是通過(guò)TRANSIT_FRAGMENT_FADE過(guò)渡值定義的。這個(gè)動(dòng)畫(huà)是在過(guò)渡行為不再是變化的一部分時(shí)出現(xiàn)的,例如添加或替換但在Fragment隱藏或顯示是不會(huì)出現(xiàn)。在這個(gè)示例中,我們使用標(biāo)準(zhǔn)的系統(tǒng)漸變動(dòng)畫(huà)來(lái)詮釋這種情形。
3.本地Fragment
如果應(yīng)用程序的目標(biāo)版本是API Level 11 或之后版本,則不必使用支持庫(kù)中的Fragment,而且這種情況下的自定義動(dòng)畫(huà)代碼會(huì)稍微有些不同。本地Fragment實(shí)現(xiàn)使用相對(duì)較新的Animator對(duì)象(而非舊的Animator對(duì)象)來(lái)創(chuàng)建過(guò)渡動(dòng)畫(huà)。
這需要對(duì)代碼做一些修改;首先,需要使用Animator來(lái)定義所有的XML動(dòng)畫(huà)。參見(jiàn)以下四段代碼:
res/animator/fragment_exit.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:valueFrom="0" android:valueTo="-90"
android:valueType="floatType"
android:propertyName="rotation"
android:duration="500"/>
<objectAnimator
android:valueFrom="1.0" android:valueTo="0.0"
android:valueType="floatType"
android:propertyName="alpha"
android:duration="500"/>
</set>
res/animator/fragment_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:valueFrom="90" android:valueTo="0"
android:valueType="floatType"
android:propertyName="rotation"
android:duration="500"/>
<objectAnimator
android:valueFrom="0.0" android:valueTo="1.0"
android:valueType="floatType"
android:propertyName="alpha"
android:duration="500"/>
</set>
res/animator/fragment_pop_exit.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:valueFrom="0" android:valueTo="90"
android:valueType="floatType"
android:propertyName="rotation"
android:duration="500"/>
<objectAnimator
android:valueFrom="1.0" android:valueTo="0.0"
android:valueType="floatType"
android:propertyName="alpha"
android:duration="500"/>
</set>
res/animator/fragment_pop_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:valueFrom="-90" android:valueTo="0"
android:valueType="floatType"
android:propertyName="rotation"
android:duration="500"/>
<objectAnimator
android:valueFrom="0.0" android:valueTo="1.0"
android:valueType="floatType"
android:propertyName="alpha"
android:duration="500"/>
</set>
除了語(yǔ)法的細(xì)微區(qū)別,這些動(dòng)畫(huà)幾乎和之前創(chuàng)建的動(dòng)畫(huà)一模一樣。其他僅有的差別是,這些動(dòng)畫(huà)被設(shè)置為圍繞在視圖的中心(默認(rèn)行為)而不是左上角。
和以前一樣,可以通過(guò)setCustomAnimations()直接設(shè)置某個(gè)FragmentTransaction單獨(dú)的過(guò)渡動(dòng)畫(huà);但是,新的版本使用了我們的Animator實(shí)例。下面的代碼片段使用新API實(shí)現(xiàn)了這一過(guò)程:
FragmentTransaction ft = getFragmentManager().beginTransaction();
//首先必須調(diào)用該方法!
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);
ft.commit();
如果想要對(duì)某一特定子類(lèi)總是使用相同的過(guò)渡動(dòng)畫(huà),可以像以前一樣自定義Fragment。但本地Fragment沒(méi)有onCreateAnimation()方法,而是使用了onCreateAnimator()。查看以下代碼,它使用新的API重新定義了我們創(chuàng)建的Fragment。
自定義過(guò)渡動(dòng)畫(huà)的本地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);
}
}
}
}
同樣,會(huì)像支持庫(kù)示例一樣的檢查同樣的過(guò)渡值,我們只是返回Animator實(shí)例。下面同樣的代碼片段可以使用過(guò)渡值集開(kāi)始一個(gè)事務(wù):
FragmentTransaction ft = getFragmentManager().beginTransaction();
//設(shè)置過(guò)渡值觸發(fā)相應(yīng)的動(dòng)畫(huà)
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
ft.commit();
將這些自定義動(dòng)畫(huà)全局地應(yīng)用到整個(gè)應(yīng)用程序的最終方式就是將這些動(dòng)畫(huà)關(guān)聯(lián)到應(yīng)用程序的主題上。以下代碼顯示了使用我們的Fragment動(dòng)畫(huà)的自定義主題。
res/values/styles.xml
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="NativeFragmentTheme" parent="android:Theme.Holo.Light">
<item name="android:windowAnimationStyle">@style/FragmentAnimation</item>
</style>
<style name="FragmentAnimation" parent="@android:style/Animation.Activity">
<item name="android:fragmentOpenEnterAnimation">@animator/fragment_enter</item>
<item name="android:fragmentOpenExitAnimation">@animator/fragment_exit</item>
<item name="android:fragmentCloseEnterAnimation">@animator/fragment_pop_enter</item>
<item name="android:fragmentCloseExitAnimation">@animator/fragment_pop_exit</item>
<item name="android:fragmentFadeEnterAnimation">@android:animator/fade_in</item>
<item name="android:fragmentFadeExitAnimation">@android:animator/fade_out</item>
</style>
</resources>
正如你所看到的,一個(gè)主題默認(rèn)的Fragment動(dòng)畫(huà)屬性就是相同的windowAnimationStyle屬性的一部分。因此,我們?cè)谧远x時(shí)要保證繼承自同樣的父樣式,否則會(huì)移除其他的一些系統(tǒng)默認(rèn)效果(例如Activity過(guò)渡動(dòng)畫(huà))。必須依然要在FragmentTransaction中請(qǐng)求相應(yīng)的過(guò)渡類(lèi)型,從而觸發(fā)相應(yīng)的動(dòng)畫(huà)。
如果想要在主題中同時(shí)自定義Activity和Fragment的過(guò)度動(dòng)畫(huà),可以將它們一起放到一個(gè)相同的自定義樣式中(參見(jiàn)以下代碼)。
res/values/styles.xml
<resources>
<style name="AppTheme" parent="@style/Theme.Holo.Light">
<item name="android:windowAnimationStyle">@style/TransitionAnimation</item>
</style>
<style name="TransitionAnimation" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/activity_open_enter</item>
<item name="android:activityOpenExitAnimation">@anim/activity_open_exit</item>
<item name="android:activityCloseEnterAnimation">@anim/activity_close_enter</item>
<item name="android:activityCloseExitAnimation">@anim/activity_close_exit</item>
<item name="android:fragmentOpenEnterAnimation">@animator/fragment_enter</item>
<item name="android:fragmentOpenExitAnimation">@animator/fragment_exit</item>
<item name="android:fragmentCloseEnterAnimation">@animator/fragment_pop_enter</item>
<item name="android:fragmentCloseExitAnimation">@animator/fragment_pop_exit</item>
<item name="android:fragmentFadeEnterAnimation">@android:animator/fade_in</item>
<item name="android:fragmentFadeExitAnimation">@android:animator/fade_out</item>
</style>
</resources>
警告:
對(duì)主題添加的Fragment過(guò)渡動(dòng)畫(huà)只會(huì)作用于本地Fragment實(shí)現(xiàn)。支持庫(kù)中的Fragment因?yàn)樵缙诘钠脚_(tái)版本并沒(méi)有這些屬性,所有也找不到這些屬性。
Demo下載地址:
1.11 自定義過(guò)渡動(dòng)畫(huà)