Android中View的炸裂特效实现方法详解
本文实例讲述了Android中View的炸裂特效实现方法。分享给大家供大家参考,具体如下:
前几天微博上被一个很优秀的Android开源组件刷屏了-ExplosionField,效果非常酷炫,有点类似MIUI卸载APP时的动画,先来感受一下。
ExplosionField不但效果很拉风,代码写得也相当好,让人忍不住要拿来好好读一下。
创建ExplosionField
ExplosionField继承自View,在onDraw方法中绘制动画特效,并且它提供了一个attach2Window方法,可以把ExplosionField最为一个子View添加到Activity上的rootview中。
publicstaticExplosionFieldattach2Window(Activityactivity){
ViewGrouprootView=(ViewGroup)activity.findViewById(Window.ID_ANDROID_CONTENT);
ExplosionFieldexplosionField=newExplosionField(activity);
rootView.addView(explosionField,newViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
returnexplosionField;
}
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。
intstartDelay=100;
ValueAnimatoranimator=ValueAnimator.ofFloat(0f,1f).setDuration(150);
animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){
Randomrandom=newRandom();
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
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-
voidexplode(Bitmapbitmap,Rectbound,longstartDelay,longduration)
前两个参数bitmap和bound是关键,通过View来创建bitmap的代码比较有意思。
如果View是一个ImageView,并且它的Drawable是一个BitmapDrawable就可以直接获取这个Bitmap。
if(viewinstanceofImageView){
Drawabledrawable=((ImageView)view).getDrawable();
if(drawable!=null&&drawableinstanceofBitmapDrawable){
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();
Bitmapbitmap=createBitmapSafely(view.getWidth(),
view.getHeight(),Bitmap.Config.ARGB_8888,1);
if(bitmap!=null){
synchronized(sCanvas){
Canvascanvas=sCanvas;
canvas.setBitmap(bitmap);
view.draw(canvas);
canvas.setBitmap(null);
}
}
作者创建位图的办法非常巧妙,如果新建Bitmap时产生了OOM,可以主动进行一次GC-System.gc(),然后再次尝试创建。
这个函数的实现方式让人佩服作者的功力。
publicstaticBitmapcreateBitmapSafely(intwidth,intheight,Bitmap.Configconfig,intretryCount){
try{
returnBitmap.createBitmap(width,height,config);
}catch(OutOfMemoryErrore){
e.printStackTrace();
if(retryCount>0){
System.gc();
returncreateBitmapSafely(width,height,config,retryCount-1);
}
returnnull;
}
}
出了bitmap,还有一个一个很重要的参数bound,它的创建相对比较简单:
Rectr=newRect(); view.getGlobalVisibleRect(r); int[]location=newint[2]; getLocationOnScreen(location); r.offset(-location[0],-location[1]); r.inset(-mExpandInset[0],-mExpandInset[1]);
首先获取需要炸裂的View的全局可视区域-Rectr,然后通过getLocationOnScreen(location)获取ExplosionField在屏幕中的坐标,并根据这个坐标把炸裂View的可视区域进行平移,这样炸裂效果才会显示在ExplosionField中,最后根据mExpandInset值(默认为0)扩展一下。
那创建的bitmap和bound有什么用呢?我们继续往下分析。
创建粒子
先来看一下炸裂成粒子这个方法的全貌:
publicvoidexplode(Bitmapbitmap,Rectbound,longstartDelay,longduration){
finalExplosionAnimatorexplosion=newExplosionAnimator(this,bitmap,bound);
explosion.addListener(newAnimatorListenerAdapter(){
@Override
publicvoidonAnimationEnd(Animatoranimation){
mExplosions.remove(animation);
}
});
explosion.setStartDelay(startDelay);
explosion.setDuration(duration);
mExplosions.add(explosion);
explosion.start();
}
这里要解释一下为什么用一个容器类变量-mExplosions来保存一个ExplosionAnimator。因为activity中多个View的炸裂效果可能要同时进行,所以要把每个View对应的炸裂动画保存起来,等动画结束的时候再删掉。
作者自定义了一个继承自ValueAnimator的类-ExplosionAnimator,它主要做了两件事情,一个是创建粒子-generateParticle,另一个是绘制粒子-draw(Canvascanvas)。
先来看一下构造函数:
publicExplosionAnimator(Viewcontainer,Bitmapbitmap,Rectbound){
mPaint=newPaint();
mBound=newRect(bound);
intpartLen=15;
mParticles=newParticle[partLen*partLen];
Randomrandom=newRandom(System.currentTimeMillis());
intw=bitmap.getWidth()/(partLen+2);
inth=bitmap.getHeight()/(partLen+2);
for(inti=0;i<partLen;i++){
for(intj=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分成了一个17x17的矩阵,每个元素的宽度和高度分别是w和h。
intw=bitmap.getWidth()/(partLen+2); inth=bitmap.getHeight()/(partLen+2);
所有的粒子是一个15x15的矩阵,元素色值是位图对应的像素值。
bitmap.getPixel((j+1)*w,(i+1)*h)
结构如下图所示,其中空心部分是粒子。
●●●●●●●●●●●●●●●●● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●○○○○○○○○○○○○○○○● ●●●●●●●●●●●●●●●●●
generateParticle会根据一定的算法随机地生成一个粒子。这部分比较繁琐,分析略去。
其中比较巧妙的还是它的draw方法:
publicbooleandraw(Canvascanvas){
if(!isStarted()){
returnfalse;
}
for(Particleparticle: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();
returntrue;
}
刚开始我还一直比较困惑,既然绘制粒子是在ExplosionField的onDraw方法中进行,那肯定需要不停地刷新,结果作者并不是这么做的,实现方法又着实惊艳了一把。
首先,作者在ExplosionAnimator类中重载了start()方法,通过调用mContainer.invalidate(mBound)来刷新将要炸裂的View所对应的区块。
@Override
publicvoidstart(){
super.start();
mContainer.invalidate(mBound);
}
而mContainer即是占满了activity的view-ExplosionField,它的onDraw方法中又会调用ExplosionAnimator的draw方法。
@Override
protectedvoidonDraw(Canvascanvas){
super.onDraw(canvas);
for(ExplosionAnimatorexplosion:mExplosions){
explosion.draw(canvas);
}
}
这样便形成了一个递归,两者相互调用,不停地刷新,直到所有粒子的alpha值变为0,刷新就停下来了。
publicbooleandraw(Canvascanvas){
if(!isStarted()){
returnfalse;
}
for(Particleparticle: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();
returntrue;
}
总结
这个开源库的代码质量相当高,十分佩服作者。
更多关于Android相关内容感兴趣的读者可查看本站专题:《Android视图View技巧总结》、《Android操作XML数据技巧总结》、《Android编程之activity操作技巧总结》、《Android资源操作技巧汇总》、《Android文件操作技巧汇总》、《Android操作SQLite数据库技巧总结》、《Android操作json格式数据技巧总结》、《Android数据库操作技巧总结》、《Android编程开发之SD卡操作方法汇总》、《Android开发入门与进阶教程》及《Android控件用法总结》
希望本文所述对大家Android程序设计有所帮助。