Android中View的炸裂特效实现方法详解
本文实例讲述了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(); @override public void onanimationupdate(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:
1. 新建一个 canvas
2. 根据 view 的大小创建一个空的 bitmap
3. 把空的 bitmap 设置为 canvas 的底布
4. 把 view 绘制在 canvas上
5. 把 canvas 的 bitmap 设置成 null
当然,绘制之前要清掉 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 有什么用呢?我们继续往下分析。
创建粒子
先来看一下炸裂成粒子这个方法的全貌:
public void explode(bitmap bitmap, rect bound, long startdelay, long duration) { final explosionanimator explosion = new explosionanimator(this, bitmap, bound); explosion.addlistener(new animatorlisteneradapter() { @override public void onanimationend(animator animation) { mexplosions.remove(animation); } }); explosion.setstartdelay(startdelay); explosion.setduration(duration); mexplosions.add(explosion); explosion.start(); }
这里要解释一下为什么用一个容器类变量 - mexplosions 来保存一个 explosionanimator。因为 activity 中多个 view 的炸裂效果可能要同时进行,所以要把每个 view 对应的炸裂动画保存起来,等动画结束的时候再删掉。
作者自定义了一个继承自 valueanimator 的类 - explosionanimator,它主要做了两件事情,一个是创建粒子 - generateparticle,另一个是绘制粒子 - draw(canvas canvas)。
先来看一下构造函数:
public explosionanimator(view container, bitmap bitmap, rect bound) { mpaint = new paint(); mbound = new rect(bound); int partlen = 15; mparticles = new particle[partlen * partlen]; random random = new random(system.currenttimemillis()); int w = bitmap.getwidth() / (partlen + 2); int h = bitmap.getheight() / (partlen + 2); for (int i = 0; i < partlen; i++) { for (int j = 0; j < partlen; j++) { mparticles[(i * partlen) + j] = generateparticle(bitmap.getpixel((j + 1) * w, (i + 1) * h), random); } } mcontainer = container; setfloatvalues(0f, end_value); setinterpolator(default_interpolator); setduration(default_duration); }
根据构造函数可以知道作者把 bitmap 分成了一个 17 x 17 的矩阵,每个元素的宽度和高度分别是 w 和 h。
int w = bitmap.getwidth() / (partlen + 2); int h = bitmap.getheight() / (partlen + 2);
所有的粒子是一个 15 x 15 的矩阵,元素色值是位图对应的像素值。
bitmap.getpixel((j + 1) * w, (i + 1) * h)
结构如下图所示,其中空心部分是粒子。
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
generateparticle 会根据一定的算法随机地生成一个粒子。这部分比较繁琐,分析略去。
其中比较巧妙的还是它的 draw 方法:
public boolean draw(canvas canvas) { if (!isstarted()) { return false; } for (particle particle : mparticles) { particle.advance((float) getanimatedvalue()); if (particle.alpha > 0f) { mpaint.setcolor(particle.color); mpaint.setalpha((int) (color.alpha(particle.color) * particle.alpha)); canvas.drawcircle(particle.cx, particle.cy, particle.radius, mpaint); } } mcontainer.invalidate(); return true; }
刚开始我还一直比较困惑,既然绘制粒子是在 explosionfield 的 ondraw 方法中进行,那肯定需要不停地刷新,结果作者并不是这么做的,实现方法又着实惊艳了一把。
首先,作者在 explosionanimator 类中重载了 start() 方法,通过调用 mcontainer.invalidate(mbound) 来刷新 将要炸裂的 view 所对应的区块。
@override public void start() { super.start(); mcontainer.invalidate(mbound); }
而 mcontainer 即是占满了 activity 的 view - explosionfield,它的 ondraw 方法中又会调用 explosionanimator 的 draw 方法。
@override protected void ondraw(canvas canvas) { super.ondraw(canvas); for (explosionanimator explosion : mexplosions) { explosion.draw(canvas); } }
这样便形成了一个递归,两者相互调用,不停地刷新,直到所有粒子的 alpha 值变为 0,刷新就停下来了。
public boolean draw(canvas canvas) { if (!isstarted()) { return false; } for (particle particle : mparticles) { particle.advance((float) getanimatedvalue()); if (particle.alpha > 0f) { mpaint.setcolor(particle.color); mpaint.setalpha((int) (color.alpha(particle.color) * particle.alpha)); canvas.drawcircle(particle.cx, particle.cy, particle.radius, mpaint); } } mcontainer.invalidate(); return true; }
总结
这个开源库的代码质量相当高,十分佩服作者。
更多关于android相关内容感兴趣的读者可查看本站专题:《android视图view技巧总结》、《android操作xml数据技巧总结》、《android编程之activity操作技巧总结》、《android资源操作技巧汇总》、《android文件操作技巧汇总》、《android操作sqlite数据库技巧总结》、《android操作json格式数据技巧总结》、《android数据库操作技巧总结》、《android编程开发之sd卡操作方法汇总》、《android开发入门与进阶教程》及《android控件用法总结》
希望本文所述对大家android程序设计有所帮助。