Android中View的炸裂特效实现方法详解_Android

本文实例讲述了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程序设计有所帮助。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索android
, view
特效
ps字体炸裂特效、ae内置特效详解、开心消消乐特效详解图、开心消消乐特效详解、钢铁侠3幕后特效详解,以便于您获取更多的相关知识。

时间: 2024-10-26 12:27:43

Android中View的炸裂特效实现方法详解_Android的相关文章

android中DatePicker和TimePicker的使用方法详解_Android

本文以实例讲述了android中DatePicker和TimePicker的使用方法,具体步骤如下: 下面是实现具体功能的代码,其中main.xml代码为: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width=&quo

Android使用onCreateOptionsMenu()创建菜单Menu的方法详解_Android

本文实例讲述了Android使用onCreateOptionsMenu()创建菜单Menu的方法.分享给大家供大家参考,具体如下: Android一共有三种形式的菜单: 1.选项菜单(optinosMenu) 2.上下文菜单(ContextMenu) 3.子菜单(subMenu) 其中最常用的就是选项菜单(optionsMenu), 该菜单在点击 menu 按键 后会在对应的Activity底部显示出来. 1.Activity菜单机制 (与dialog类似) Activity有一套机制来实现对菜

Android中AlertDialog各种对话框的用法实例详解_Android

 目标效果: 程序运行,显示图一的几个按钮,点击按钮分别显示图二到图六的对话框,点击对话框的某一项或者按钮,也会显示相应的吐司输出. 1.activity_main.xml页面存放五个按钮. activity_main.xml页面: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools

Android中Activity生命周期和启动模式详解_Android

Activity生命周期经典图解: 按键对生命周期的影响: BACK键: 当我们按BACK键时,我们这个应用程序将结束,这时候我们将先后调用onPause()->onStop()->onDestory()三个方法. 再次启动App时,会执行onCreate()->onStart()->onResume() HOME键: 当我们打开应用程序时,比如浏览器,我正在浏览NBA新闻,看到一半时,我突然想听歌,这时候我们会选择按HOME键,然后去打开音乐应用程序,而当我们按HOME的时候,A

Android组件必学之TabHost使用方法详解_Android

一.TabHost用法通常情况下我们会通过继承TabActivity,调用getTabHost()获取TabHost实例,下面是具体过程. TabHostActivity.java public class TabHostActivity extends TabActivity { private TabHost tabHost; private Intent certificateIntent; private Intent feeIntent; private Intent scoreInt

Android中Split()字符串分割特殊用法案例详解_Android

split()分割字符串 1.不同环境下的区分 Java:分割字符串不能写成split("$")//$为要分割的字符 Android:分割字符串需要加上中括号split("[$]")//$为要分割的字符 2.特殊用法--当split()分割字符串遇上特殊符号 案例分析 String str = "abc|dfg"; String[] all=str.split("|"); system.out.println(all[0]);

Android 中SP与DP的区别实例详解_Android

从一开始写Android程序,就被告知这些常识 1.长度宽度的数值要使用dp作为单位放入dimens.xml文件中 2.字体大小的数值要使用sp作为单位,也放入dimens.xml文件中 然后,就没有然后了,仿佛潜台词就是说,你记住去用就行了. 偶然有一天,当我们阴差阳错地将字体写成了dp,也是可以工作,而且效果和sp一样. 这时候,就开始怀疑了,到底有啥区别呢,dp和sp有什么不同呢? 我们做个简单的Sample验证一下,如下,一个布局代码 <TextView android:layout_w

Android自定义View实现绘制虚线的方法详解

前言 说实话当第一次看到这个需求的时候,第一反应就是Canvas只有drawLine方法,并没有drawDashLine方法啊!这咋整啊,难道要我自己做个遍历不断的drawLine?不到1秒,我就放弃这个想法了,因为太恶心了.方法肯定是有的,只不过我不知道而已. 绘制方法 最简单的方法是利用ShapeDrawable,比如说你想用虚线要隔开两个控件,就可以在这两个控件中加个View,然后给它个虚线背景. 嗯,理论上就是这样子的,实现上也很简单. <!-- drawable 文件 --> <

Android中RecyclerView实现分页滚动的方法详解

一.需求分析 最近公司项目要实现一个需求要满足以下功能: 1)显示一个 list 列表, item 数量不固定. 2)实现翻页功能,一次翻一页. 3)实现翻至某一页功能. 下面介绍通过 RecyclerView 实现该需求的实现过程(效果图如下). 二.功能实现 2.1 OnTouchListener 记录当前开始滑动位置 要实现翻页滑动首先我们要确定是向前翻页还是向后翻页,这里通过记录开始翻页前当前的位置和滑动后的位置比较即可得知,下面选择手指触摸按下时滑动的位置为当前开始滑动位置: //当前