5分钟快速实现Android爆炸破碎酷炫动画特效的示例

这个破碎动画,是一种类似小米系统删除应用时的爆炸破碎效果的动画。

效果图展示

先来看下是怎样的动效,要是感觉不是理想的学习目标,就跳过,避免浪费大家的时间。��

源码在这里:point_right: https://github.com/ReadyShowShow/explosion

一行代码即可调用该动画

new ExplosionField(this).explode(view, null))

下面开始我们酷炫的Android动画特效正式讲解:point_down:

先来个整体结构的把握

整体结构非常简单明了,新老从业者都可快速看懂,容易把握学习。

./
|-- explosion
| |-- MainActivity.java (测试爆炸破碎动效的主界面)
| |-- animation(爆炸破碎动效有关的类均在这里)
| | |-- ExplosionAnimator.java(爆炸动画)
| | |-- ExplosionField.java(爆炸破碎动画所依赖的View)
| | `-- ParticleModel.java(每个破碎后的粒子的model,颜色、位置、大小等)
| `-- utils
|  `-- UIUtils.java(计算状态栏高度的工具类)
`-- tree.txt

庖丁解牛

下面开始每个类的详细分析

本着从简到繁、由表及里的原则,详细讲解每个类

MainActivity.java

MainActivity.java是测试动效的界面,该Activity内部有7个测试按钮。该类做的事情非常单纯,就是给每个View分别绑定click点击事件,让View在点击时能触发爆炸破碎动画。

/**
 * 说明:测试的界面
 * 作者:Jian
 * 时间:2017/12/26.
 */
public class MainActivity extends AppCompatActivity {

 /**
  * 加载布局文件,添加点击事件
  */
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  initViewsClick();
 }

 /**
  * 添加点击事件的实现
  */
 private void initViewsClick() {
  // 为单个View添加点击事件
  final View title = findViewById(R.id.title_tv);
  title.setOnClickListener(v ->
    new ExplosionField(MainActivity.this).explode(title, null));

  // 为中间3个View添加点击事件
  setSelfAndChildDisappearOnClick(findViewById(R.id.title_disappear_ll));
  // 为下面3个View添加点击事件
  setSelfAndChildDisappearAndAppearOnClick(findViewById(R.id.title_disappear_and_appear_ll));

  // 跳转到github网页的点击事件
  findViewById(R.id.github_tv).setOnClickListener((view) -> {
   Intent intent = new Intent();
   intent.setAction(Intent.ACTION_VIEW);
   Uri content_url = Uri.parse(getString(R.string.github));
   intent.setData(content_url);
   startActivity(intent);
  });
 }

 /**
  * 为自己以及子View添加破碎动画,动画结束后,把View消失掉
  * @param view 可能是ViewGroup的view
  */
 private void setSelfAndChildDisappearOnClick(final View view) {
  if (view instanceof ViewGroup) {
   ViewGroup viewGroup = (ViewGroup) view;
   for (int i = 0; i < viewGroup.getChildCount(); i++) {
    setSelfAndChildDisappearOnClick(viewGroup.getChildAt(i));
   }
  } else {
   view.setOnClickListener(v ->
     new ExplosionField(MainActivity.this).explode(view,
       new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
         super.onAnimationEnd(animation);
         view.setVisibility(View.GONE);
        }
       }));
  }
 }

 /**
  * 为自己以及子View添加破碎动画,动画结束后,View自动出现
  * @param view 可能是ViewGroup的view
  */
 private void setSelfAndChildDisappearAndAppearOnClick(final View view) {
  if (view instanceof ViewGroup) {
   ViewGroup viewGroup = (ViewGroup) view;
   for (int i = 0; i < viewGroup.getChildCount(); i++) {
    setSelfAndChildDisappearAndAppearOnClick(viewGroup.getChildAt(i));
   }
  } else {
   view.setOnClickListener(v ->
     new ExplosionField(MainActivity.this).explode(view, null));
  }
 }
}

ParticleModel.java

ParticleModel.java是包含一个粒子的所有信息的model。advance方法根据值动画返回的进度计算出粒子的位置和颜色等信息

/**
 * 说明:爆破粒子,每个移动与渐变的小块
 * 作者:Jian
 * 时间:2017/12/26.
 */
class ParticleModel {
 // 默认小球宽高
 static final int PART_WH = 8;
 // 随机数,随机出位置和大小
 static Random random = new Random();
 //center x of circle
 float cx;
 //center y of circle
 float cy;
 // 半径
 float radius;
 // 颜色
 int color;
 // 透明度
 float alpha;
 // 整体边界
 Rect mBound;

 ParticleModel(int color, Rect bound, Point point) {
  int row = point.y; //行是高
  int column = point.x; //列是宽

  this.mBound = bound;
  this.color = color;
  this.alpha = 1f;
  this.radius = PART_WH;
  this.cx = bound.left + PART_WH * column;
  this.cy = bound.top + PART_WH * row;
 }

 // 每一步动画都得重新计算出自己的状态值
 void advance(float factor) {
  cx = cx + factor * random.nextInt(mBound.width()) * (random.nextFloat() - 0.5f);
  cy = cy + factor * random.nextInt(mBound.height() / 2);

  radius = radius - factor * random.nextInt(2);

  alpha = (1f - factor) * (1 + random.nextFloat());
 }
}

ExplosionAnimation.java

ExlosionAnimation.java是动画类,是一个值动画,在值动画每次产生一个值的时候,就计算出整个爆炸破碎动效内的全部粒子的状态。这些状态交由使用的View在渲染时进行显示。

/**
 * 说明:爆炸动画类,让离子移动和控制离子透明度
 * 作者:Jian
 * 时间:2017/12/26.
 */
class ExplosionAnimator extends ValueAnimator {
 private static final int DEFAULT_DURATION = 1500;
 private ParticleModel[][] mParticles;
 private Paint mPaint;
 private View mContainer;

 public ExplosionAnimator(View view, Bitmap bitmap, Rect bound) {
  setFloatValues(0.0f, 1.0f);
  setDuration(DEFAULT_DURATION);

  mPaint = new Paint();
  mContainer = view;
  mParticles = generateParticles(bitmap, bound);
 }

 // 生成粒子,按行按列生成全部粒子
 private ParticleModel[][] generateParticles(Bitmap bitmap, Rect bound) {
  int w = bound.width();
  int h = bound.height();

  // 横向粒子的个数
  int horizontalCount = w / ParticleModel.PART_WH;
  // 竖向粒子的个数
  int verticalCount = h / ParticleModel.PART_WH;

  // 粒子宽度
  int bitmapPartWidth = bitmap.getWidth() / horizontalCount;
  // 粒子高度
  int bitmapPartHeight = bitmap.getHeight() / verticalCount;

  ParticleModel[][] particles = new ParticleModel[verticalCount][horizontalCount];
  for (int row = 0; row < verticalCount; row++) {
   for (int column = 0; column < horizontalCount; column++) {
    //取得当前粒子所在位置的颜色
    int color = bitmap.getPixel(column * bitmapPartWidth, row * bitmapPartHeight);

    Point point = new Point(column, row);
    particles[row][column] = new ParticleModel(color, bound, point);
   }
  }
  return particles;
 }

 // 由view调用,在View上绘制全部的粒子
 void draw(Canvas canvas) {
  // 动画结束时停止
  if (!isStarted()) {
   return;
  }
  // 遍历粒子,并绘制在View上
  for (ParticleModel[] particle : mParticles) {
   for (ParticleModel p : particle) {
    p.advance((Float) getAnimatedValue());
    mPaint.setColor(p.color);
    // 错误的设置方式只是这样设置,透明色会显示为黑色
    // mPaint.setAlpha((int) (255 * p.alpha));
    // 正确的设置方式,这样透明颜色就不是黑色了
    mPaint.setAlpha((int) (Color.alpha(p.color) * p.alpha));
    canvas.drawCircle(p.cx, p.cy, p.radius, mPaint);
   }
  }
  mContainer.invalidate();
 }

 @Override
 public void start() {
  super.start();
  mContainer.invalidate();
 }
}

ExplosionField.java

ExplosionField.java是真实执行上面ExplosionAnimator。ExplosionField会创建一个View并依附在Activity的根View上。

/**
 * 说明:每次爆炸时,创建一个覆盖全屏的View,这样的话,不管要爆炸的View在任何位置都能显示爆炸效果
 * 作者:Jian
 * 时间:2017/12/26.
 */
public class ExplosionField extends View {
 private static final String TAG = "ExplosionField";
 private static final Canvas mCanvas = new Canvas();
 private ExplosionAnimator animator;

 public ExplosionField(Context context) {
  super(context);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  animator.draw(canvas);
 }

 /**
  * 执行爆破破碎动画
  */
 public void explode(final View view, final AnimatorListenerAdapter listener) {
  Rect rect = new Rect();
  view.getGlobalVisibleRect(rect); //得到view相对于整个屏幕的坐标
  rect.offset(0, -UIUtils.statusBarHeignth()); //去掉状态栏高度

  animator = new ExplosionAnimator(this, createBitmapFromView(view), rect);

  // 接口回调
  animator.addListener(new Animator.AnimatorListener() {
   @Override
   public void onAnimationStart(Animator animation) {
    if (listener != null) listener.onAnimationStart(animation);
    // 延时添加到界面上
    attach2Activity((Activity) getContext());
    // 让被爆炸的View消失(爆炸的View是新创建的View,原View本身不会发生任何变化)
    view.animate().alpha(0f).setDuration(150).start();
   }

   @Override
   public void onAnimationEnd(Animator animation) {
    if (listener != null) listener.onAnimationEnd(animation);
    // 从界面中移除
    removeFromActivity((Activity) getContext());
    // 让被爆炸的View显示(爆炸的View是新创建的View,原View本身不会发生任何变化)
    view.animate().alpha(1f).setDuration(150).start();
   }

   @Override
   public void onAnimationCancel(Animator animation) {
    if (listener != null) listener.onAnimationCancel(animation);
   }

   @Override
   public void onAnimationRepeat(Animator animation) {
    if (listener != null) listener.onAnimationRepeat(animation);
   }
  });
  animator.start();
 }

 private Bitmap createBitmapFromView(View view) {
//   为什么屏蔽以下代码段?
//   如果ImageView直接得到位图,那么当它设置背景(backgroud)时,不会读取到背景颜色
//  if (view instanceof ImageView) {
//   Drawable drawable = ((ImageView)view).getDrawable();
//   if (drawable != null && drawable instanceof BitmapDrawable) {
//    return ((BitmapDrawable) drawable).getBitmap();
//   }
//  }
  //view.clearFocus(); //不同焦点状态显示的可能不同——(azz:不同就不同有什么关系?)

  Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);

  if (bitmap != null) {
   synchronized (mCanvas) {
    mCanvas.setBitmap(bitmap);
    view.draw(mCanvas);
    // 清除引用
    mCanvas.setBitmap(null);
   }
  }
  return bitmap;
 }

 /**
  * 将创建的ExplosionField添加到Activity上
  */
 private void attach2Activity(Activity activity) {
  ViewGroup rootView = activity.findViewById(Window.ID_ANDROID_CONTENT);

  ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
  rootView.addView(this, lp);
 }

 /**
  * 将ExplosionField从Activity上移除
  */
 private void removeFromActivity(Activity activity) {
  ViewGroup rootView = activity.findViewById(Window.ID_ANDROID_CONTENT);
  rootView.removeView(this);
 }
}

动画执行时为什么要创建一个新View(ExplosionField)

其实上面的动画类ExplosionAnimator已经实现了核心功能,直接在原View上使用该动画应该是没问题的。为什么还要引入一个ExplosionField类呢?动画的执行为什么不能直接在原本的View上执行呢?偏偏要在一个看似多余的ExplosionField对象上执行呢。

这里就得从Android下View绘制原理来解释了:Android下的View都有一个Bound,在View进行measure和layout的时候,已经确定了View的大小和位置,如果要在这个View上进行动画的话,就会出现动画只能在view大小范围内进行展现。当然了,也不是说在原来View上一定不能实现这一动效,就是相当复杂,要在动画执行过程中,不断改变原View的大小和View的属性等信息,相当复杂。

在性能还行的前提下,要优先代码的整洁度,尽量避免为了优化的性能,而舍弃整洁清爽的代码。一般来说,过度的优化,并没有给用户带来太多体验上的提升,反而给项目带来了巨大的维护难度。

UIUtils.java

UIUtils是关于UI的工具类,没啥可说的

public class UIUtils {
 public static int dp2px(double dpi) {
  return (int) (Resources.getSystem().getDisplayMetrics().density * dpi + 0.5f);
 }

 public static int statusBarHeignth() {
  return dp2px(25);
 }
}

结束

源码:point_right: https://github.com/ReadyShowShow/explosion

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android实现粒子爆炸效果的方法

    本文实例讲述了Android实现粒子爆炸效果的方法.分享给大家供大家参考.具体如下: 1. Explosion.java文件: package net.obviam.particles.model; import android.graphics.Canvas; import android.graphics.Rect; import android.util.Log; public class Explosion { private static final String TAG = Expl

  • Android实战打飞机游戏之子弹生成与碰撞以及爆炸效果(5)

    Android实战打飞机游戏子弹生成,新建子弹类 public class Bullet { // 子弹图片资源 public Bitmap bmpBullet; // 子弹的坐标 public int bulletX, bulletY; // 子弹的速度 public int speed; // 子弹的种类以及常量 public int bulletType; // 主角的 public static final int BULLET_PLAYER = -1; // 鸭子的 public st

  • 5分钟快速实现Android爆炸破碎酷炫动画特效的示例

    这个破碎动画,是一种类似小米系统删除应用时的爆炸破碎效果的动画. 效果图展示 先来看下是怎样的动效,要是感觉不是理想的学习目标,就跳过,避免浪费大家的时间.�� 源码在这里:point_right: https://github.com/ReadyShowShow/explosion 一行代码即可调用该动画 new ExplosionField(this).explode(view, null)) 下面开始我们酷炫的Android动画特效正式讲解:point_down: 先来个整体结构的把握 整

  • Flutter 实现酷炫的3D效果示例代码

    此文讲解3个酷炫的3D动画效果. 下面是要实现的效果: Flutter 中3D效果是通过 Transform 组件实现的,没有变换效果的实现: class TransformDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('3D 变换Demo'), ), body: Container( alignm

  • 一分钟快速定位Android启动耗时问题

    目录 前言 1. 接入Tencent Matrix 2. 改造Application子类 3.运行,快速定位 总结 前言 Tencent Matrix默认无法监测Application冷启动的耗时方法,本文介绍了如何改造Matrix支持冷启动耗时方法监测.让你一分钟就能给App启动卡顿号脉. 1. 接入Tencent Matrix 1.1 在你项目根目录下的 gradle.properties 中配置要依赖的 Matrix 版本号,如: MATRIX_VERSION=1.0.0 1.2 在你项目

  • Android酷炫动画效果之3D星体旋转效果

    在Android中,如果想要实现3D动画效果一般有两种选择:一是使用Open GL ES,二是使用Camera.Open GL ES使用起来太过复杂,一般是用于比较高级的3D特效或游戏,并且这个也不是开源的,像比较简单的一些3D效果,使用Camera就足够了. 一些熟知的Android 3D动画如对某个View进行旋转或翻转的 Rotate3dAnimation类,还有使用Gallery( Gallery目前已过时,现在都推荐使用 HorizontalScrollView或 RecyclerVi

  • 基于jquery和svg实现超炫酷的动画特效

    今天给大家分享一款基于jquery和svg超炫的网页动画.这款动画效果非常炫.下面还有重播.慢速.和反向动画按钮.效果非常漂亮.一起看下效果图: 实现的代码. html代码: 复制代码 代码如下: <div id="intro">         <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"          

  • Android利用贝塞尔曲线绘制动画的示例代码

    目录 彩虹系列 弹簧动画 复杂立体感动画 总结 前面我们花了几篇介绍了贝塞尔曲线的原理和绘制贝塞尔曲线,着实让我们见识到了贝塞尔曲线的美.好奇心驱使我想看看贝塞尔曲线动起来会是什么样?本篇就借由动画驱动贝塞尔曲线绘制看看动起来的贝塞尔曲线什么效果. 彩虹系列 通过动画控制绘制的结束点,就可以让贝塞尔曲线动起来.例如下面的动图展示的效果,看起来像搭了一个滑滑梯一样.实际上就是用7条贝塞尔曲线实现的,我们使用了 Animation 对象的值来控制绘制的结束点,从而实现了对应的动画效果. 具体源码如下

  • 快速了解Android Room使用细则进阶

    目录 1.前言 2.@ForeignKey和@PrimaryKey 3.@TypeConverters 4.@Relation 5.@Transaction 6.@Embedded 7.@ColumnInfo (1)指定实体类中的字段名称 (2)指定实体类中的字段默认值 (3)指定实体类中的字段约束 8.@Ignore 忽略一个实体类中的字段 (1)忽略一个实体类中的 getter 和 setter 方法 (2)忽略一个实体类中的整个构造函数 9.@Index (1)在一个实体类中创建单个索引

  • 5分钟快速搭建FTP服务器的图文教程

    一.什么是FTP FTP(File Transfer Protocol)是TCP/IP网络上两台计算机传送文件的协议,使得主机间可以共享文件. 二.搭建前期准备 1.首先打开控制面板找到"程序"点击打开,如下图所示: 2.在打开的"程序"窗口中,找到"启用或关闭windows功能"点击打开,如下图所示: 3.在"windows功能"中找到"Internet Information Services",并选中

  • Vue.js 60分钟快速入门教程

    vuejs是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的.相比于Angular.js,Vue.js提供了更加简洁.更易于理解的API,使得我们能够快速地上手并使用Vue.js. 如果你之前已经习惯了用jQuery操作DOM,学习Vue.js时请先抛开手动操作DOM的思维,因为Vue.js是数据驱动的,你无需手动操作DOM.它通过一些特殊的HTML语法,将DOM和数据绑定起来.一旦你创建了绑定,DOM将和数据保持同步,每当变更了数据,DOM也会相应地更新. 当

  • 反向Ajax 30分钟快速掌握

    场景1:当有新邮件的时候,网页自动弹出提示信息而无需用户手动的刷新收件箱. 场景2:当用户的手机扫描完成页面中的二维码以后,页面会自动跳转. 场景3:在类似聊天室的环境中有任何人发言,所有登录用户都可以即时看见信息. 与传统的MVC模型请求必须从客户端发起由服务器响应相比,使用反向Ajax能够模拟服务器端主动向客户端推送事件从而提高用户体验.本文将分两个部分讨论反向Ajax技术,包括:Comet和WebSocket.文章旨在演示如何实现以上两种技术手段,Struts2或SpringMVC中的应用

随机推荐