- 原文鏈接:Activity Split Animation
- 原文作者: Udi Cohen
- 譯文出自: 小鄧子的簡書
- 譯者: 小鄧子
- 校對者: 程序亦非猿
- 狀態(tài): 完成
這周枫笛,正好有時間可以寫一個小而酷的Activity過渡動畫猜极。
在切換不同Activity時履腋,系統(tǒng)級過渡動畫是作用于整個Activity的,而我想要實現(xiàn)的動畫效果是將Activity A分割成兩部分,然后將他們向外推開丰涉,最后呈現(xiàn)Activity B。gif圖效果如下:
我的思路很簡單:
- Activity A保存為bitmap
- 把bitmap分割成兩個子bitmap
- 子bitmap傳遞至Activity B
- 在Activity B的布局之上顯示兩個子bitmap
- 使用動畫向外移出兩個子bitmap
- Activity B呈現(xiàn)在用戶眼前 :)
可是實現(xiàn)起來,并不如我預期的那樣簡單尤溜。我遇到了一些困難,但最終我找到了所有問題的解決辦法汗唱。接下來宫莱,就讓我們一步步搞定它。
提示:這種實現(xiàn)方式需要保存整個屏幕的內(nèi)容為bitmap(譯者注:源碼中哩罪,作者只是保存了android.R.id.content下的內(nèi)容作為bitmap授霸,并非整個screen)。對于低內(nèi)存或者大屏幕的設備來說际插,可能是很大的開銷碘耳。如果你依然選擇使用,請小心框弛,并且不要過度使用辛辨。
保存Bitmap##
為了得到整個Activity的圖片,可以使用以下代碼:
View root = currActivity.getWindow().getDecorView().findViewById(android.R.id.content);
root.setDrawingCacheEnabled(true);
mBitmap = root.getDrawingCache();
第一行代碼中瑟枫,首先拿到Activity的根View斗搞,然后通過android.R.id.content
得到一個FrameLayout,這個FrameLayout存在于每一個Activity中力奋,并且包含了setContentView( )
中放入的布局榜旦。下圖是用 HierarchyViewer觀察時的樣子。
為了獲取根View或其他任何View視圖的bitmap景殷,可以通過調(diào)用getDrawingCache( )方法溅呢,它將返回一個緩存bitmap,但前提是這個View允許繪圖緩存,這就是為什么在獲取緩存bitmap之前調(diào)用 setDrawingCacheEnabled( )的原因。如果View沒有緩存bitmap遗锣,則會立即創(chuàng)建龟糕。
分割Bitmap##
分割bitmap的代碼如下:
Bitmap mBmp1 = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), splitYCoord);
Bitmap mBmp2 =Bitmap.createBitmap(bmp, 0, splitYCoord, bmp.getWidth(), bmp.getHeight() - splitYCoord);
bmp
是整個activity A的緩存bitmap,splitYCoord
是Y軸的分割點。
生成的兩個子bitmap, mBmp1
是bmp
的上半部分次舌,mBmp2
是bmp
的下半部分伊约,它們的高度大小取決于分割點splitYCoord
傳遞子bitmap到下一個Activity##
得到兩個子bitmap之后姚淆,我希望跳轉(zhuǎn)到下一個Activity時候把就它們放在要展示的Activity的布局之上,這樣用戶看到的依然是Activity A的布局屡律,而事實上程序已經(jīng)跳轉(zhuǎn)到Activity B了腌逢。
起初,我想將他們作為Intent的[Extras](http://developer.android.com/reference/android/content/Intent.html#putExtra(java.lang.String, android.os.Parcelable))傳遞過去,因為bitmap實現(xiàn)了Parcelable接口超埋,所以理論上來說這是可行的搏讶。但是問題來了,受限于IPC的容量限制霍殴,子bitmap太大了以至于不能在Intent中傳遞媒惕,這是我得到的錯誤log:
!!! FAILED BINDER TRANSACTION !!!
還有一些其他方法,比如將子bitmap寫入文件来庭,然后在另一端讀出妒蔚。但是我發(fā)現(xiàn),最簡單的實現(xiàn)方式月弛,就是將他們以成員變量的形式放到一個公共區(qū)域中面睛。所以,我創(chuàng)建了一個靜態(tài)類用來持有子bitmap尊搬,所有的創(chuàng)建操作和動畫邏輯,也都在這里個類里面土涝,稍后會詳細介紹佛寿。
在Activity B中顯示子bitmap##
啟動activity B之后,通過調(diào)用[overridePendingTransition( )](http://developer.android.com/reference/android/app/Activity.html#overridePendingTransition(int, int))禁用所有默認Activity過度動畫但壮。我創(chuàng)建了兩個Imageview去呈現(xiàn)之前創(chuàng)建的子bitmap冀泻,并將它們展示在屏幕上,為了避免提前看到Activity B的布局蜡饵,這些操作要在setContentView( )
之前調(diào)用弹渔。
這兩個Imageview將直接添加到activity所在的Window上。這樣做不僅可以保證Imageview能夠處在即將被填充的布局之上溯祸,而且還可以靈活控制每一個Imageview在屏幕上的位置肢专。
ImageView imageView = new ImageView(destActivity);
imageView.setImageBitmap(bmp);
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
windowParams.gravity = Gravity.TOP;
windowParams.x = loc[0];
windowParams.y = loc[1];
windowParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
windowParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
windowParams.flags =WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
windowParams.format = PixelFormat.TRANSLUCENT;
windowParams.windowAnimations = 0;
destActivity.getWindowManager().addView(imageView, windowParams);
簡單明了。
gravity表示將把我們的layout放在window的什么位置.因為已經(jīng)計算了子bitmap相對于屏幕頂部的X焦辅、Y的坐標,所以我們將gravity賦值為Top就可以了博杖。
子bitmap動畫##
在Activity B中創(chuàng)建完Imageview并且擺放好位置后,調(diào)用setContentView( )
填充Layout布局筷登。當布局填充完畢后剃根,執(zhí)行動畫,把兩個bitmap向外推出前方,從而呈現(xiàn)Activity布局狈醉。
mSetAnim = new AnimatorSet();
mTopImage.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mBottomImage.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mSetAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
clean(destActivity);
}
@Override
public void onAnimationCancel(Animator animation) {
clean(destActivity);
}
...
});
// Animating the 2 parts away from each other
Animator anim1 = ObjectAnimator.ofFloat(mTopImage, translationY, mTopImage.getHeight() * -1);
Animator anim2 = ObjectAnimator.ofFloat(mBottomImage, translationY, mBottomImage.getHeight());
mSetAnim.setDuration(duration);
mSetAnim.playTogether(anim1, anim2);
mSetAnim.start();
這個動畫僅僅是Y軸移動動畫廉油,將每個Imageview移出屏幕,不同的只是方向而已苗傅。我使用硬件加速(了解更多有關硬件加速動畫抒线,請閱讀我最新發(fā)布的blog)并且在動畫結(jié)束或者取消后,做了一些清理操作(如金吗,移除硬件圖層十兢,把Imageview從Window窗口移除等等)
如何使用我的動畫##
我曾反復思考,在盡量不限制開發(fā)者的情況下摇庙,如何最簡單便捷的使用它旱物。不過話說回來,最簡單的做法還是創(chuàng)建一個BaseActivity卫袒,然后開發(fā)者繼承這個基類宵呛,這樣就可以不必花費太多的精力去關心它了。但我并沒有這樣做是因為夕凝,我討厭僅僅是為了獲得擴展功能就繼承其他的Activity宝穗。試想,如果你的工程有屬于自己的BaseActivity码秉,然而一些三方庫卻強制要求繼承它們的BaseActivity逮矛,這種情況下,你一定感到特無語转砖。
所以须鼎,我只創(chuàng)建了一個類,包含了一些靜態(tài)方法府蔗,用來完成所有的工作晋控,API如下:
/**
* Utility class to create a split activity animation
*
* @author Udi Cohen (@udinic)
*/
public class ActivitySplitAnimationUtil {
/**
* Start a new Activity with a Split animation
*
* @param currActivity The current Activity
* @param intent The Intent needed tot start the new Activity
* @param splitYCoord The Y coordinate where we want to split the Activity on the animation. -1
* will split the Activity equally
*/
public static void startActivity(Activity currActivity, Intent intent, int splitYCoord);
/**
* Start a new Activity with a Split animation right in the middle of the Activity
*
* @param currActivity The current Activity
* @param intent The Intent needed tot start the new Activity
*/
public static void startActivity(Activity currActivity, Intent intent);
/**
* Preparing the graphics on the destination Activity.
* Should be called on the destination activity on Activity#onCreate() BEFORE setContentView()
*
* @param destActivity the destination Activity
*/
public static void prepareAnimation(final Activity destActivity);
/**
* Start the animation the reveals the destination Activity
* Should be called on the destination activity on Activity#onCreate() AFTER setContentView()
*
* @param destActivity the destination Activity
* @param duration The duration of the animation
* @param interpolator The interpulator to use for the animation. null for no interpulation.
*/
public static void animate(final Activity destActivity, final int duration,
final TimeInterpolator interpolator);
/**
* Start the animation that reveals the destination Activity
* Should be called on the destination activity on Activity#onCreate() AFTER setContentView()
*
* @param destActivity the destination Activity
* @param duration The duration of the animation
*/
public static void animate(final Activity destActivity, final int duration);
/**
* Cancel an in progress animation
*/
public static void cancel();
}
使用它非常的簡單,只需要在Activity A中要跳轉(zhuǎn)Activity B的時候姓赤,調(diào)用這個方法就行了:
ActivitySplitAnimationUtil.startActivity(Activity1.this, new Intent(Activity1.this, Activity2.class));
然后在Activity B的onCreate()
方法中這樣做:
// Preparing the 2 images to be split
ActivitySplitAnimationUtil.prepareAnimation(this);
// Setting the Activity's layout
setContentView(R.layout.act_two);
// Animating the items to be open, revealing the new activity.
// Animation duration of 1 second
ActivitySplitAnimat
就是辣么簡單赡译。
沒有什么多余的操作,只需要調(diào)用三個靜態(tài)方法即可不铆。
目前只支持API 14以上蝌焚,如果想兼容更早的版本請使用NineOldAndroid。
這個是倉庫地址:
https://github.com/Udinic/ActivitySplitAnimation
使用它誓斥,F(xiàn)ork它综看,豐富它。
下一步##
你可以將它擴展的更豐富岖食,比如:
- 垂直分割 - 讓Activity從兩側(cè)移出红碑。
- 把Activity分割成更多的部分。
- 做所有你能想到的事情。