Android粒子爆炸特效[可用于任意控件]
小米手机用户可以看到,小米手机在应用卸载时会有一个粒子爆炸的特效效果,对此类动画效果垂涎已久,奈何一直没有机会用。正好最近项目里需要用到粒子爆炸的特效,于是便抽时间自己也试着仿写了一个效果出来。
先看下效果:
How to use:
**
Step 1. Add the JitPack repository to your build file
Add it in your root build.gradle at the end of repositories:
allprojects { repositories { ... maven { url 'https://jitpack.io' } } }
Step 2.Add the dependency
dependencies { compile 'com.github.zhaolei9527:Particle-master:v1.0.1' }
代码中这样使用:
首先提供了响应式触发的方式,首先进行埋雷,当控件被点击时,触发爆炸。是不是很刺激?
//目前提供了六种的粒子爆炸特效 explosionSite1 = new ExplosionSite(this, new BooleanFactory()); explosionSite2 = new ExplosionSite(this, new ExplodeParticleFactory()); explosionSite3 = new ExplosionSite(this, new FallingParticleFactory()); explosionSite4 = new ExplosionSite(this, new FlyawayFactory()); explosionSite5 = new ExplosionSite(this, new InnerFallingParticleFactory()); explosionSite6 = new ExplosionSite(this, new VerticalAscentFactory()); //爆炸激活方式一:将View或ViewGroup添加至雷管监听,View被点击时,触发爆炸 explosionSite1.addListener(img_1); explosionSite2.addListener(img_2); explosionSite3.addListener(img_6); explosionSite4.addListener(img_4); explosionSite5.addListener(img_5); explosionSite6.addListener(img_3);
其次针对一些情况,提供了另外一种直接的触发方式。是不是更刺激?
//爆炸激活方式二:将View或ViewGroup直接点燃爆炸 explosionSite1.explode(img_1); explosionSite2.explode(img_2); explosionSite3.explode(img_3); explosionSite4.explode(img_4); explosionSite5.explode(img_5); explosionSite6.explode(img_6);
看下原理:GitHub项目地址
网上实现类似相同效果的很多,基本规则也都差不多。
主要对象如下:
ExplosionSite:爆炸效果发生的场地,是一个View。当一个控件需要爆炸时,需要为控件生成一个ExplosionSite,这个ExplosionSite覆盖整个屏幕,于是我们才能看到完整的爆炸效果,在ExplosionField的构造函数中,传入不同的ParticleFactory,就可以生成不同的爆炸效果。
ExplosionAnimator:爆炸动画,其实是一个计时器,继承自ValueAnimator。0x400s内,完成爆炸动画,每次计时,就更新所有粒子的运动状态。draw()方法是它最重要的方法,也就是使所有粒子重绘自身,从而实现动画效果。
ParticleFactory:是一个抽象类。用于产生粒子数组,不同的ParticleFactory可以产生不同类型的粒子数组。
Particle:抽象的粒子类。代表粒子本身,必须拥有的属性包括,当前自己的cx,cy坐标和颜色color。必须实现两个方法,draw()方法选择怎么绘制自身(圆形还是方形等),caculate()计算当前时间,自己所处的位置。
实现原理:
1、获取当前控件背景bitmap
例如,例子中使用的是imageview,对于控件,在Utils中提供有一个工具类,可以获得其背景的Bitmap对象
public static Bitmap createBitmapFromView(View view) { 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); } } return bitmap; } 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; } }
2、将View镜像转换成粒子对象
获取Bitmap以后,我们交给工厂对象进行加工,根据Bitmap生产Particle数组。
我们知道Bitmap可以看成是一个像素矩阵,矩阵上面的点,就是一个个带有颜色的像素,于是我们可以获取到每个点的颜色和位置,组装成一个对象Particle,这么一来,Particle就代表带有颜色的点了。
public abstract class ParticleFactory { public abstract Particle[][] generateParticles(Bitmap bitmap, Rect bound); }
简单来说都一样,就是拿要爆炸的View制作一个镜像出来然后返回。
然后来看一个粒子效果的具体实现。
public Particle[][] generateParticles(Bitmap bitmap, Rect bound) { int w = bound.width(); int h = bound.height(); int partW_Count = w / PART_WH; //横向个数 int partH_Count = h / PART_WH; //竖向个数 int bitmap_part_w = bitmap.getWidth() / partW_Count; int bitmap_part_h = bitmap.getHeight() / partH_Count; Particle[][] particles = new Particle[partH_Count][partW_Count]; for (int row = 0; row < partH_Count; row ++) { //行 for (int column = 0; column < partW_Count; column ++) { //列 //取得当前粒子所在位置的颜色 int color = bitmap.getPixel(column * bitmap_part_w, row * bitmap_part_h); float x = bound.left + BooleanFactory.PART_WH * column; float y = bound.top + BooleanFactory.PART_WH * row; particles[row][column] = new BooleanParticle(color,x,y,bound); } } return particles; }
很简单,其中Rect类型的bound,是代表原来View控件的宽高信息。
根据我们设定的每个粒子的大小,和控件的宽高,我们就可以计算出,有多少个粒子组成这个控件的背景。
我们取得每个粒子所在位置的颜色,位置,用于生产粒子,这就是BooleanParticle。
3、爆炸对象及主要流程
public class ExplosionSite extends View { private static final String TAG = "ExplosionField"; private ArrayList<ExplosionAnimator> explosionAnimators; private HashMap<View, ExplosionAnimator> explosionAnimatorsMap; private OnClickListener onClickListener; private ParticleFactory mParticleFactory; public ExplosionSite(Context context, ParticleFactory particleFactory) { super(context); init(particleFactory); } public ExplosionSite(Context context, AttributeSet attrs, ParticleFactory particleFactory) { super(context, attrs); init(particleFactory); } private void init(ParticleFactory particleFactory) { explosionAnimators = new ArrayList<ExplosionAnimator>(); explosionAnimatorsMap = new HashMap<View, ExplosionAnimator>(); mParticleFactory = particleFactory; attach2Activity((Activity) getContext()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (ExplosionAnimator animator : explosionAnimators) { animator.draw(canvas); } } /** * 爆破 * * @param view 使得该view爆破 */ public void explode(final View view) { //防止重复点击 if (explosionAnimatorsMap.get(view) != null && explosionAnimatorsMap.get(view).isStarted()) { return; } if (view.getVisibility() != View.VISIBLE || view.getAlpha() == 0) { return; } final Rect rect = new Rect(); view.getGlobalVisibleRect(rect); //得到view相对于整个屏幕的坐标 int contentTop = ((ViewGroup) getParent()).getTop(); Rect frame = new Rect(); ((Activity) getContext()).getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); int statusBarHeight = frame.top; rect.offset(0, -contentTop - statusBarHeight);//去掉状态栏高度和标题栏高度 if (rect.width() == 0 || rect.height() == 0) { return; } //震动动画 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.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); explode(view, rect); } }); animator.start(); } private void explode(final View view, Rect rect) { final ExplosionAnimator animator = new ExplosionAnimator(this, Utils.createBitmapFromView(view), rect, mParticleFactory); explosionAnimators.add(animator); explosionAnimatorsMap.put(view, animator); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { //缩小,透明动画 view.animate().setDuration(150).scaleX(0f).scaleY(0f).alpha(0f).start(); } @Override public void onAnimationEnd(Animator animation) { view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(150).start(); //动画结束时从动画集中移除 explosionAnimators.remove(animation); explosionAnimatorsMap.remove(view); animation = null; } }); animator.start(); } /** * 给Activity加上全屏覆盖的ExplosionField */ private void attach2Activity(Activity activity) { ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); rootView.addView(this, lp); } /** * 希望谁有破碎效果,就给谁加Listener * * @param view 可以是ViewGroup */ public void addListener(View view) { if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; int count = viewGroup.getChildCount(); for (int i = 0; i < count; i++) { addListener(viewGroup.getChildAt(i)); } } else { view.setClickable(true); view.setOnClickListener(getOnClickListener()); } } private OnClickListener getOnClickListener() { if (null == onClickListener) { onClickListener = new OnClickListener() { @Override public void onClick(View v) { ExplosionSite.this.explode(v); } }; } return onClickListener; } }
总结:总体来说实现很简单,就是根据工厂类,生成粒子数组。
而其实质是一个ValueAnimator,在一定时间内,从0数到1。
然后提供了一个draw()方法,方法里面调用了每个粒子的advance()方法,并且传入了当前数到的数字。
advance()方法里,其实调用了draw()方法和caculate()方法。
上面的实现,其实是一个固定的流程,添加了爆炸场地以后,我们就开始从0数到1,在这个过程中,粒子会根据当前时间,绘制自己的位置,所以粒子的位置,其实是它自己决定的,和流程无关。
也就是说,我们只要用不同的算法,绘制粒子的位置即可,实现了流程和粒子运动的分离。
所以除了提供的六种爆炸特效之外,只要遵循这个原则,就能生成更多的粒子爆炸特效。
有了需求才有了功能,有了想法才有了创作,你的反馈会是使我进步的最大动力。
觉得还不够方便?还想要什么功能?告诉我!欢迎反馈,欢迎Star。源码入口:GitHub项目地址
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Android全面插件化方案-RePlugin踩坑
1.什么是RePlugin? 在Android开发领域,有关插件化的讨论一直热度不减。目前市面上的插件化方案虽然很多,但多数只能实现某些功能的插件化,距离开发者的预期尚有相当差距。对此,在近期GMTC全球移动技术大会上,360手机卫士主程序架构负责人张炅轩宣布,360的插件化框架RePlugin已经可以实现“全面插件化”,同时具有出色的稳定性和灵活性,可适用于各种类型的应用上。“RePlugin预计7月份开源,这将是我们献给安卓世界最好的礼物。”360如是说。 2.RePlugin有什么用? RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。 3.RePlugin官方介绍 其主要优势有: 极其灵活:主程序无需升级(无需在Manifest中预埋组件),即可支持新增的四大组件,甚至全新的插件 非常稳定:Hook点仅有一处(ClassLoader),无任何Binder Hook!如此可做到其崩溃率仅为“万分之一”,并完美兼容市面上近乎所有的Androi...
- 下一篇
Android开发 - 掌握ConstraintLayout(七)辅助线(Guideline)
了解过UI设计的同学都知道,在设计的时候,我们经常在界面上拖进一些辅助线来帮我们对齐UI元素,或者方便我们统一的页边距。 在ConstraintLayout的编辑器中,同样也支持这样的功能,我们可以创建一些横向的或者纵向的Guideline,在布局界面的时候可以充分利用这些辅助线,对齐我们的View,避免重复写一些marginXXX。 创建Guideline 打开编辑器,选择Helpers -> Add Vertical Guideline 创建后,默认的Guideline是靠左的: 我们可以拖动来调整这个Guideline的边距,也可以通过右侧的属性栏直接输入边距的大小: 细心的同学可能发现,这里除了设置左边局,还有一个layout_constraintGuide_end,这个是做什么用的呢?没错,如果设置这个值,那么这条Guideline就是靠右的! 还有layout_constraintGuide_percent,从名字就可以看出,这个是按宽度的百分比设置边距,这个值的范围是0-1。(0% - 100%) 设置好辅助线后,我们就可以将View约束到这条辅助线上了。 当调整这...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8安装Docker,最新的服务器搭配容器使用
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7设置SWAP分区,小内存服务器的救世主