【譯】Activity分割動畫

這周枫笛,正好有時間可以寫一個小而酷的Activity過渡動畫猜极。

在切換不同Activity時履腋,系統(tǒng)級過渡動畫是作用于整個Activity的,而我想要實現(xiàn)的動畫效果是將Activity A分割成兩部分,然后將他們向外推開丰涉,最后呈現(xiàn)Activity B。gif圖效果如下:

我的思路很簡單:

  1. Activity A保存為bitmap
  2. 把bitmap分割成兩個子bitmap
  3. 子bitmap傳遞至Activity B
  4. 在Activity B的布局之上顯示兩個子bitmap
  5. 使用動畫向外移出兩個子bitmap
  6. 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, mBmp1bmp的上半部分次舌,mBmp2bmp的下半部分伊约,它們的高度大小取決于分割點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它综看,豐富它。

下一步##

你可以將它擴展的更豐富岖食,比如:

  1. 垂直分割 - 讓Activity從兩側(cè)移出红碑。
  2. 把Activity分割成更多的部分。
  3. 做所有你能想到的事情。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末析珊,一起剝皮案震驚了整個濱河市羡鸥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌忠寻,老刑警劉巖惧浴,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奕剃,居然都是意外死亡衷旅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門纵朋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柿顶,“玉大人,你說我怎么就攤上這事操软∴揖猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵聂薪,是天一觀的道長家乘。 經(jīng)常有香客問我,道長藏澳,這世上最難降的妖魔是什么仁锯? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮翔悠,結(jié)果婚禮上扑馁,老公的妹妹穿的比我還像新娘。我一直安慰自己凉驻,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布复罐。 她就那樣靜靜地躺著涝登,像睡著了一般。 火紅的嫁衣襯著肌膚如雪效诅。 梳的紋絲不亂的頭發(fā)上胀滚,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音乱投,去河邊找鬼咽笼。 笑死,一個胖子當著我的面吹牛戚炫,可吹牛的內(nèi)容都是我干的剑刑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼施掏!你這毒婦竟也來了钮惠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤七芭,失蹤者是張志新(化名)和其女友劉穎素挽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狸驳,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡预明,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了耙箍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撰糠。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖究西,靈堂內(nèi)的尸體忽然破棺而出窗慎,到底是詐尸還是另有隱情,我是刑警寧澤卤材,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布遮斥,位于F島的核電站,受9級特大地震影響扇丛,放射性物質(zhì)發(fā)生泄漏术吗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一帆精、第九天 我趴在偏房一處隱蔽的房頂上張望较屿。 院中可真熱鬧,春花似錦卓练、人聲如沸隘蝎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘱么。三九已至,卻和暖如春顽悼,著一層夾襖步出監(jiān)牢的瞬間曼振,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工蔚龙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冰评,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓木羹,卻偏偏與公主長得像甲雅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容