欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Android实现粒子中心扩散动画效果,

来源: 开发者 投稿于  被查看 16691 次 评论:212

Android实现粒子中心扩散动画效果,


目录
  • 前言
  • 实现步骤
    • 粒子对象定义
    • 粒子更新
    • 粒子绘制方法
    • 粒子回收
    • View逻辑
    • 绘制逻辑
    • 更新粒子
  • 效果调节
    • 总结
      • 本篇代码

        前言

        粒子动画效果相比其他动画来说是非常复杂了的,主要涉及三个方面,粒子初始化、粒子位移、粒子回收等问题,其中特别是粒子位移是最复杂的,涉及到的数学逻辑非常多,主要是各种三角函数、物理学公式等。

        本篇将实现两种动画效果,代码基本相同,只是旋转速度不一样,因此,本篇其实可以看作一篇模板文章,具体效果可以通过调节参数生成各种动画

        第一种动画

        第二种动画

        实现步骤

        其实和以往的粒子效果一样,粒子需要被管理起来,因此我们需要有容器、也需要粒子对象

        粒子对象定义

        下面是创建粒子对象的逻辑,基本属性在注释中了

        static class Circle {
            int maxLength;  //最大运行距离
            float speed; //外扩速度
            float rotate; // 角速度
            private float degree; //起始角度
            private int y; //y坐标
            private int x; //x坐标
            private int color; //颜色
            private float radius; //小圆半径
            private float drawRadius; //绘制时的小圆半径
            
           
          public Circle(int color, int maxLength, float radius, float degree) {
            this.color = color;
            this.radius = radius;
            this.maxLength = maxLength;
            this.degree = degree;
            this.x = (int) (radius * Math.cos(degree));
            this.y = (int) (radius * Math.sin(degree));
            this.rotate = 0.35f;  //触角效果
            this.speed = 0.2f;
         }
        }
        

        粒子更新

        在任何动画中,粒子运动必须具备时间属性,任何符合物理学的位移运动,速度和时间的关系是位移计算的方法。下面,我们继续给Circle类添加更新方法。

        这里一个重要的知识点是

        • Math.hypot(x, y) :平方根计算
        • Math.atan2(y, x): 斜率计算,注意,此角度具备方向
        public boolean update(long timeline) {
            float length = (float) Math.hypot(x, y);  //计算当前移动的距离(距离中心点)
            float center = length + this.speed * timeline; //计算即将到达的距离
            float ratio = center / maxLength;  //计算与最远距离的比值
        
            this.drawRadius = (1f - ratio) * radius;  //距离越远,圆的半径越小
        
            double degree = Math.atan2(y, x) + rotate;  //即将旋转的角度
        
            this.x = (int) (center * Math.cos(degree)); //新的x
            this.y = (int) (center * Math.sin(degree)); //新的y
        
            if (drawRadius <= 0) {
                return false; //如果半径为0时,意味着圆看不见了,因此要坐下标记
            }
            return true;
        }
        

        粒子绘制方法

        绘制自身其实很简单,只需要简单的调用Canvas相关逻辑即可

        public void draw(Canvas canvas, TextPaint paint) {
            paint.setColor(color);
            canvas.drawCircle(x, y, drawRadius, paint);
        }
        

        粒子回收

        为了减少内存申请频率,我们对跑出边界的粒子进行重置

        public void reset() {
            this.x = (int) (radius * Math.cos(degree));
            this.y = (int) (radius * Math.sin(degree));
        }
        

        View逻辑

        以上是完整的粒子对象逻辑,接下来我们实现一个View,用来管理和绘制粒子。

        int maxCircleRadius = 20;  //粒子初始半径
        List<Circle> circleList = new ArrayList<>(); //容器
        int maxCircleNum = 300; //最大数量
        

        绘制逻辑

        首先是初始化,我们这里设置了3种粒子,因此间隔角度是120度,而我们每次增加三种,防止出现混乱的问题。

           final float rotateDegree = (float) Math.toRadians(120f); //间隔角度
            if (circleList.size() < maxCircleNum) {
            //每次增加三种
                circleList.add(new Circle(Color.RED, (int) maxRadius, maxCircleRadius, 0 * rotateDegree));
                circleList.add(new Circle(Color.GREEN, (int) maxRadius, maxCircleRadius, 1 * rotateDegree));
                circleList.add(new Circle(Color.CYAN, (int) maxRadius, maxCircleRadius, 2 * rotateDegree));
            }
        

        下面是每个粒子的绘制逻辑

        for (int i = 0; i < circleList.size(); i++) {
            Circle circle = circleList.get(i);
            circle.draw(canvas, mPaint); //绘制方法
        }
        

        更新粒子

        下面有个重要的逻辑,其实前面也提到过,就是重置跑出边界的粒子

        for (int i = 0; i < circleList.size(); i++) {
            Circle circle = circleList.get(i);
            if(!circle.update(16)){
                circle.reset(); //如果不能更新,则进行重置
            }
        }
        postInvalidate(); //刷新绘制逻辑
        

        以上就是整体核心逻辑

        效果调节

        我们开头的两种效果其实是同一个View实现的,这其中一个重要的点就是速度调整,文章开头是调整出的两种效果,当然染还可以调整出其他效果 第一种

        this.rotate = 0.2f;
        this.speed = 0.2f; //外扩效果
        

        第二种

         this.rotate = 0.35f;  //触角效果
         this.speed = 0.2f;
        

        第三种

        this.rotate = 0.8f;
        this.speed = 0.1f;
        

        当然,还有更多,篇幅原因就不深入了。

        总结

        本篇到这里就结束了,其实我们的核心代码并不多,但是简单的逻辑就能衍生出很多动画效果。其实,学习粒子动画是非常有意思的事,很多时候,你在实现某些效果的途中,就能突然开发出一种新的动画效果。

        本篇代码

        下面是本篇内容的完整逻辑,基本就在100行左右。

        public class CircleParticleView extends View {
            private TextPaint mPaint;
            private DisplayMetrics mDM;
        
            public CircleParticleView(Context context) {
                this(context, null);
            }
            public CircleParticleView(Context context, AttributeSet attrs) {
                super(context, attrs);
                mDM = getResources().getDisplayMetrics();
                initPaint();
            }
        
            private void initPaint() {
                //否则提供给外部纹理绘制
                mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
                mPaint.setAntiAlias(true);
                mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
                mPaint.setStrokeCap(Paint.Cap.ROUND);
                PaintCompat.setBlendMode(mPaint, BlendModeCompat.PLUS);
        
            }
        
            @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        
                int widthMode = MeasureSpec.getMode(widthMeasureSpec);
                int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        
                if (widthMode != MeasureSpec.EXACTLY) {
                    widthSize = mDM.widthPixels / 2;
                }
                int heightMode = MeasureSpec.getMode(heightMeasureSpec);
                int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        
                if (heightMode != MeasureSpec.EXACTLY) {
                    heightSize = widthSize / 2;
                }
                setMeasuredDimension(widthSize, heightSize);
        
            }
        
            int maxCircleRadius = 20;
            List<Circle> circleList = new ArrayList<>();
            int maxCircleNum = 300;
            long time = 0;
        
            @Override
            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);
        
                int width = getWidth();
                int height = getHeight();
                float maxRadius = Math.min(width, height) / 2f;
        
        
                int save = canvas.save();
                canvas.translate(width / 2f, height / 2f);
        
                final float rotateDegree = (float) Math.toRadians(120f);
                if (circleList.size() < maxCircleNum) {
                    circleList.add(new Circle(Color.RED, (int) maxRadius, maxCircleRadius, 0 * rotateDegree));
                    circleList.add(new Circle(Color.GREEN, (int) maxRadius, maxCircleRadius, 1 * rotateDegree));
                    circleList.add(new Circle(Color.CYAN, (int) maxRadius, maxCircleRadius, 2 * rotateDegree));
                }
        
                mPaint.setStyle(Paint.Style.FILL);
                for (int i = 0; i < circleList.size(); i++) {
                    Circle circle = circleList.get(i);
                    circle.draw(canvas, mPaint);
                }
                canvas.restoreToCount(save);
        
                for (int i = 0; i < circleList.size(); i++) {
                    Circle circle = circleList.get(i);
                    if (!circle.update(16)) {
                        circle.reset();
                    }
                }
        
                postInvalidate();
                time += 16;
            }
        
            static class Circle {
                int maxLength;  //最大运行距离
                float speed; //外扩速度
                float rotate; // 角速度
                private float degree; //起始角度
                private int y; //y坐标
                private int x; //x坐标
                private int color; //颜色
                private float radius; //小圆半径
                private float drawRadius; //绘制时的小圆半径
        
                public Circle(int color, int maxLength, float radius, float degree) {
                    this.color = color;
                    this.radius = radius;
                    this.maxLength = maxLength;
                    this.degree = degree;
                    this.x = (int) (radius * Math.cos(degree));
                    this.y = (int) (radius * Math.sin(degree));
                    this.rotate = 0.35f;  //触角效果
                    this.speed = 0.2f;
                }
        
        
                public boolean update(long timeline) {
                    float length = (float) Math.hypot(x, y);
                    float center = length + this.speed * timeline; //距离增加
                    float ratio = center / maxLength;
        
                    this.drawRadius = (1f - ratio) * radius;
        
                    double degree = Math.atan2(y, x) + rotate;  //角度增加
        
                    this.x = (int) (center * Math.cos(degree));
                    this.y = (int) (center * Math.sin(degree));
        
                    if (drawRadius <= 0) {
                        return false;
                    }
                    return true;
                }
        
                public void draw(Canvas canvas, TextPaint paint) {
                    paint.setColor(color);
                    canvas.drawCircle(x, y, drawRadius, paint);
                }
                public void reset() {
                    this.x = (int) (radius * Math.cos(degree));
                    this.y = (int) (radius * Math.sin(degree));
                }
            }
        
        }
        

        以上就是Android实现粒子中心扩散动画效果的详细内容,更多关于Android粒子中心扩散的资料请关注3672js教程其它相关文章!

        您可能感兴趣的文章:
        • Android粒子线条效果实现过程与代码
        • Android自定义UI之粒子效果
        • Android实现粒子雨效果
        • Android实现粒子爆炸效果的方法

        用户评论