【译】Activity分割动画

这周,正好有时间可以写一个小而酷的Activity过渡动画。

在切换不同Activity时,系统级过渡动画是作用于整个Activity的,而我想要实现的动画效果是将Activity A分割成两部分,然后将他们向外推开,最后呈现Activity B。gif图效果如下:

我的思路很简单:

  1. Activity A保存为bitmap
  2. 把bitmap分割成两个子bitmap
  3. 子bitmap传递至Activity B
  4. 在Activity B的布局之上显示两个子bitmap
  5. 使用动画向外移出两个子bitmap
  6. Activity B呈现在用户眼前 :)

可是实现起来,并不如我预期的那样简单。我遇到了一些困难,但最终我找到了所有问题的解决办法。接下来,就让我们一步步搞定它。

提示:这种实现方式需要保存整个屏幕的内容为bitmap(译者注:源码中,作者只是保存了android.R.id.content下的内容作为bitmap,并非整个screen)。对于低内存或者大屏幕的设备来说,可能是很大的开销。如果你依然选择使用,请小心,并且不要过度使用。

保存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,可以通过调用getDrawingCache( )方法,它将返回一个缓存bitmap,但前提是这个View允许绘图缓存,这就是为什么在获取缓存bitmap之前调用 setDrawingCacheEnabled( )的原因。如果View没有缓存bitmap,则会立即创建。

分割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之后,我希望跳转到下一个Activity时候把就它们放在要展示的Activity的布局之上,这样用户看到的依然是Activity A的布局,而事实上程序已经跳转到Activity B了。

起初,我想将他们作为Intent的[Extras](http://developer.android.com/reference/android/content/Intent.html#putExtra(java.lang.String, android.os.Parcelable))传递过去,因为bitmap实现了Parcelable接口,所以理论上来说这是可行的。但是问题来了,受限于IPC的容量限制,子bitmap太大了以至于不能在Intent中传递,这是我得到的错误log:

!!! FAILED BINDER TRANSACTION !!!

还有一些其他方法,比如将子bitmap写入文件,然后在另一端读出。但是我发现,最简单的实现方式,就是将他们以成员变量的形式放到一个公共区域中。所以,我创建了一个静态类用来持有子bitmap,所有的创建操作和动画逻辑,也都在这里个类里面,稍后会详细介绍。

在Activity B中显示子bitmap##

启动activity B之后,通过调用[overridePendingTransition( )](http://developer.android.com/reference/android/app/Activity.html#overridePendingTransition(int, int))禁用所有默认Activity过度动画。我创建了两个Imageview去呈现之前创建的子bitmap,并将它们展示在屏幕上,为了避免提前看到Activity B的布局,这些操作要在setContentView( )之前调用。

这两个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的什么位置.因为已经计算了子bitmap相对于屏幕顶部的X、Y的坐标,所以我们将gravity赋值为Top就可以了。

子bitmap动画##

在Activity B中创建完Imageview并且摆放好位置后,调用setContentView( )填充Layout布局。当布局填充完毕后,执行动画,把两个bitmap向外推出,从而呈现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移出屏幕,不同的只是方向而已。我使用硬件加速(了解更多有关硬件加速动画,请阅读我最新发布的blog)并且在动画结束或者取消后,做了一些清理操作(如,移除硬件图层,把Imageview从Window窗口移除等等)

如何使用我的动画##

我曾反复思考,在尽量不限制开发者的情况下,如何最简单便捷的使用它。不过话说回来,最简单的做法还是创建一个BaseActivity,然后开发者继承这个基类,这样就可以不必花费太多的精力去关心它了。但我并没有这样做是因为,我讨厌仅仅是为了获得扩展功能就继承其他的Activity。试想,如果你的工程有属于自己的BaseActivity,然而一些三方库却强制要求继承它们的BaseActivity,这种情况下,你一定感到特无语。

所以,我只创建了一个类,包含了一些静态方法,用来完成所有的工作,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中要跳转Activity B的时候,调用这个方法就行了:

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

就是辣么简单。

没有什么多余的操作,只需要调用三个静态方法即可。

目前只支持API 14以上,如果想兼容更早的版本请使用NineOldAndroid

这个是仓库地址:
https://github.com/Udinic/ActivitySplitAnimation

使用它,Fork它,丰富它。

下一步##

你可以将它扩展的更丰富,比如:

  1. 垂直分割 - 让Activity从两侧移出。
  2. 把Activity分割成更多的部分。
  3. 做所有你能想到的事情。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容