Android 中 View 炸裂特效的实现分析

前几天微博上被一个很优秀的 Android 开源组件刷屏了 – ExplosionField,效果非常酷炫,有点类似 MIUI 卸载 APP 时的动画,先来感受一下。

ExplosionField 不但效果很拉风,代码写得也相当好,让人忍不住要拿来好好读一下。

创建 ExplosionField

ExplosionField 继承自 View,在 onDraw 方法中绘制动画特效,并且它提供了一个 attach2Window 方法,可以把 ExplosionField 最为一个子 View 添加到 Activity 上的 root view 中。

public static ExplosionField attach2Window(Activity activity) {ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);ExplosionField explosionField = new ExplosionField(activity);rootView.addView(explosionField, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));return explosionField;}

explosionField 的 LayoutParams 属性都被设置为 MATCH_PARENT, 这样一来,一个 view 炸裂出来的粒子可以绘制在整个 Activity 所在的区域。

知识点:可以用 Window.ID_ANDROID_CONTENT 来替代 android.R.id.content

炸裂之前的震动效果

在 View 的点击事件中,调用 mExplosionField.explode(v)之后,View 首先会震动,然后再炸裂。

震动效果比较简单,设定一个 [0, 1] 区间 ValueAnimator,然后在 AnimatorUpdateListener 的 onAnimationUpdate 中随机平移 x 和 y坐标,最后把 scale 和 alpha 值动态减为 0。

int startDelay = 100;ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {Random random = new Random();(ValueAnimator animation) {view.setTranslationX((random.nextFloat() – 0.5f) * view.getWidth() * 0.05f);view.setTranslationY((random.nextFloat() – 0.5f) * view.getHeight() * 0.05f);}});animator.start();view.animate().setDuration(150).setStartDelay(startDelay).scaleX(0f).scaleY(0f).alpha(0f).start();根据 View 创建一个 bitmap

View 震动完了就开始进行最难的炸裂,并且炸裂是跟隐藏同时进行的,先来看一下炸裂的 API – void explode(Bitmap bitmap, Rect bound, long startDelay, long duration):

前两个参数 bitmap 和 bound 是关键,通过 View 来创建 bitmap 的代码比较有意思。

如果 View 是一个 ImageView,并且它的 Drawable 是一个 BitmapDrawable 就可以直接获取这个 Bitmap。

if (view instanceof ImageView) {Drawable drawable = ((ImageView) view).getDrawable();if (drawable != null && drawable instanceof BitmapDrawable) {return ((BitmapDrawable) drawable).getBitmap();}}

如果不是一个 ImageView,可以按照如下步骤创建一个 bitmap:

当然,绘制之前要清掉 View 的焦点,因为焦点可能会改变一个 View 的 UI 状态。 一下代码中用到的 sCanvas 是一个静态变量,这样可以节省每次创建时产生的开销。

view.clearFocus();Bitmap bitmap = createBitmapSafely(view.getWidth(),view.getHeight(), Bitmap.Config.ARGB_8888, 1);if (bitmap != null) {synchronized (sCanvas) {Canvas canvas = sCanvas;canvas.setBitmap(bitmap);view.draw(canvas);canvas.setBitmap(null);}}

作者创建位图的办法非常巧妙,如果新建 Bitmap 时产生了 OOM,可以主动进行一次 GC – System.gc(),然后再次尝试创建。

这个函数的实现方式让人佩服作者的功力。

public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {try {return Bitmap.createBitmap(width, height, config);} catch (OutOfMemoryError e) {e.printStackTrace();if (retryCount > 0) {System.gc();return createBitmapSafely(width, height, config, retryCount – 1);}return null;}}

出了 bitmap,还有一个一个很重要的参数 bound,它的创建相对比较简单:

Rect r = new Rect();view.getGlobalVisibleRect(r);int[] location = new int[2];getLocationOnScreen(location);r.offset(-location[0], -location[1]);r.inset(-mExpandInset[0], -mExpandInset[1]);

首先获取 需要炸裂的View 的全局可视区域 – Rect r,然后通过 getLocationOnScreen(location) 获取 ExplosionField 在屏幕中的坐标,并根据这个坐标把 炸裂View 的可视区域进行平移,这样炸裂效果才会显示在 ExplosionField 中,最后根据 mExpandInset 值(默认为 0)扩展一下。

那创建的 bitmap 和 bound 有什么用呢?我们继续往下分析。

创建粒子

先来看一下炸裂成粒子这个方法的全貌:

(Bitmap bitmap, Rect bound, long startDelay, long duration) {final ExplosionAnimator explosion = new ExplosionAnimator(this, bitmap, bound);explosion.addListener(new AnimatorListenerAdapter() {(Animator animation) {mExplosions.remove(animation);}});explosion.setStartDelay(startDelay);explosion.setDuration(duration);mExplosions.add(explosion);explosion.start();}先知三日,富贵十年。

Android 中 View 炸裂特效的实现分析

相关文章:

你感兴趣的文章:

标签云: