关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android实现粒子爆炸特效

    Android实现粒子爆炸特效

    2016-01-12 00:00:00 出处:工匠若水
    分享

    简介

    最近在闲逛的时候,发现了一款粒子爆炸特效的控件,觉得比较有意思,效果也不错。
    但是代码不好扩展,也就是说假如要提供不同的爆炸效果,需要修改的地方比较多。于是我对源代码进行了一些重构,将爆炸流程和粒子运动分离。

    对于源码,大家可以参考以下链接
    链接1? ??链接2

    上面两套代码,其实结构都是一样的,但是实现的效果不同(其实就是粒子运动的算法不同)。

    该文文章,将给大家介绍粒子爆炸特效的实现方式,替大家理清实现思路。

    实现效果如下:

    这里写图片描述

    类设计

    类设计图如下:

    这里写图片描述

    ExplosionField,爆炸效果发生的场地,是一个view。当一个控件需要爆炸时,需要为控件生成一个ExplosionField,这个ExplosionField**覆盖整个屏幕**,于是我们才能看到完整的爆炸效果。

    ExplosionAnimator,爆炸动画,其实是一个计时器,继承自ValueAnimator。1024s内,完成爆炸动画,每次计时,就更新所有粒子的运动状态。draw()方法是它最重要的方法,也就是使所有粒子重绘自身,从而实现动画效果。

    ParticleFactory,是一个抽象类。用于产生粒子数组,不同的ParticleFactory可以产生不同类型的粒子数组。

    Particle,抽象的粒子类。代表粒子本身,必须拥有的属性包括,当前自己的cx,cy坐标和颜色color。必须实现两个方法,d**raw()方法选择怎么绘制自身(圆形还是方形等),**caculate()计算当前时间,自己所处的位置。

    控件的使用

    控件使用很简单,首先要实现不同的爆炸效果,需要给ExplosionField传入不同的ParticleFactory工厂,产生不同的粒子。

    ExplosionField explosionField = new ExplosionField(this,new FallingParticleFactory());

    然后哪个控件需要爆炸效果,就这样添加

    explosionField.addListener(findViewById(R.id.text));
    explosionField.addListener(findViewById(R.id.layout1));

    这样就为两个控件添加了爆炸效果,注意layout1代表的是一个viewgroup,那么我们就会为viewgroup中的每个view添加爆炸效果。

    我们可以想象,在ExplosionField的构造函数中,传入不同的ParticleFactory,就可以生成不同的爆炸效果。

    爆炸实现思路

    1、获取当前控件背景bitmap

    例如,例子中使用的是imageview,对于这个控件,我提供了一个工具类,可以获得其背景的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;
            }
        }

    上面的方法,简而言之,就是将控件的Bitmap对象复制了一份,然后返回。

    我们知道,bitmap可以看成是一个像素矩阵,矩阵上面的点,就是一个个带有颜色的像素,于是我们可以获取每个点(未必需要每个)的颜色和位置,组装成一个对象Particle,这么一来,Particle就代表带有颜色的点了。

    2、将背景bitmap转换成Particle数组

    获取Bitmap以后,我们交给ParticleFactory进行加工,根据Bitmap生产Particle数组。

    public abstract class ParticleFactory {
        public abstract Particle[][] generateParticles(Bitmap bitmap, Rect bound);
    }

    例如我们来看一个简单实现类,也是gif图中,第一个下落效果的工厂类

    public class FallingParticleFactory extends ParticleFactory{
        public static final int PART_WH = 8; //默认小球宽高
    
        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];
            Point point = null;
            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 + FallingParticleFactory.PART_WH * column;
                    float y = bound.top + FallingParticleFactory.PART_WH * row;
                    particles[row][column] = new FallingParticle(color,x,y,bound);
                }
            }
    
            return particles;
        }
    
    }

    其中Rect类型的bound,是代表原来View控件的宽高信息。

    根据我们设定的每个粒子的大小,和控件的宽高,我们就可以计算出,有多少个粒子组成这个控件的背景。

    我们取得每个粒子所在位置的颜色,位置,用于生产粒子,这就是FallingParticle。

    3、生成爆炸场地,开始爆炸动画流程

    爆炸时需要场地的,也就是绘制粒子的地方,我们通过给当前屏幕,添加一个覆盖全屏幕的ExplosionField来作为爆炸场地。

    public class ExplosionField extends View{
            ...
    
            /**
             * 给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);
            }
            ...
    }

    爆炸场地添加以后,我们响应控件的点击事件,开始动画

    首先是震动动画

     /**
         * 爆破
         * @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);//去掉状态栏高度和标题栏高度
    
            //震动动画
            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();
        }

    震动动画很简单,就是x,y方向上,随机产生一些位移,使原控件发生移动即可。

    在震动动画的最后,调用了爆炸动画,于是爆炸动画开始。

    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();
        }

    爆炸动画首先将原控件隐藏。

    我们来看爆炸动画的具体实现

    public class ExplosionAnimator extends ValueAnimator {
        ...
    
        public ExplosionAnimator(View view, Bitmap bitmap, Rect bound,ParticleFactory particleFactory) {
            mParticleFactory = particleFactory;
            mPaint = new Paint();
            mContainer = view;
            setFloatValues(0.0f, 1.0f);
            setDuration(DEFAULT_DURATION);
            mParticles = mParticleFactory.generateParticles(bitmap, bound);
        }
        //最重要的方法
        public void draw(Canvas canvas) {
            if(!isStarted()) { //动画结束时停止
                return;
            }
            //所有粒子运动
            for (Particle[] particle : mParticles) {
                for (Particle p : particle) {
                    p.advance(canvas,mPaint,(Float) getAnimatedValue());
                }
            }
            mContainer.invalidate();
        }
    
        @Override
        public void start() {
            super.start();
            mContainer.invalidate();
        }
    }

    实现很简单,就是根据工厂类,生成粒子数组。
    而其实质是一个ValueAnimator,在一定时间内,从0数到1。
    然后提供了一个draw()方法,方法里面调用了每个粒子的advance()方法,并且传入了当前数到的数字(是一个小数)。
    advance()方法里,其实调用了draw()方法和caculate()方法。

    上面的实现,其实是一个固定的流程,添加了爆炸场地以后,我们就开始从0数到1,在这个过程中,粒子会根据当前时间,绘制自己的位置,所以粒子的位置,其实是它自己决定的,和流程无关。

    也就是说,我们只要用不同的算法,绘制粒子的位置即可,实现了流程和粒子运动的分离。

    4、怎么运动?粒子自己最清楚

    举个例子,gif图中,下落效果的粒子是这样运动的

    public class FallingParticle extends Particle{
        static Random random = new Random();
        float radius = FallingParticleFactory.PART_WH;
        float alpha = 1.0f;
        Rect mBound;
        /**
         * @param color 颜色
         * @param x
         * @param y
         */
        public FallingParticle(int color, float x, float y,Rect bound) {
            super(color, x, y);
            mBound = bound;
        }
        ...
        protected void caculate(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());
        }
    }

    caculate(float factor)方法,根据当前时间,计算粒子的下一个位置

    我们可以看到,在这个粒子中,cy也就是竖直方向上是不断增加的,cx也就是水平方向上,是随机增加或者减少,这样就形成了下落效果。

    计算出当前位置以后,粒子就将自己绘制出来

    protected void draw(Canvas canvas,Paint paint){
            paint.setColor(color);
            paint.setAlpha((int) (Color.alpha(color) * alpha)); //这样透明颜色就不是黑色了
            canvas.drawCircle(cx, cy, radius, paint);
        }

    怎么扩展?

    从上面的代码结构可以看出,爆炸流程和粒子具体运动无关,最重要的是,大家要实现自己的caculate()方法,决定粒子的运动形态。

    而不同的粒子可以由对应的工厂产生,所以要扩展爆炸特性,只需要定义一个粒子类,和生成粒子类的工厂即可。

    源码下载? ??github地址

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。