主页 > 开发文档 > 高级UI特效之酷炫抢红包金币下落动画

高级UI特效之酷炫抢红包金币下落动画

最近项目需求要求做一个抢红包UI特效。效果如下:

 

项目效果
项目效果

 

从这张效果图中我们这可看出要包括功能:

  • 实现是个弹框:
  • 金币下落功能

  • 打开金币按钮的翻转效果

分析

  1. 实现是个弹框:
    可以用thime为Dialog的Activity
    或者 之谈弹出一个Dialog,或者弹出一个PopupWindow

  2. 金币下落功能: 可用自定义View+自定义属性动画

  3. 金币的翻转效果:可硬用帧动画或者自定义View+ScheduledExecutorService发送runnable

 

Markdown
Markdown

 

利用PopupWindow弹出一些红包界面:

    private PopupWindow showPopWindows(View v, String moneyStr, boolean show) {
            View view = this.getLayoutInflater().inflate(R.layout.view_login_reward, null);
            TextView tvTips = (TextView) view.findViewById(R.id.tv_tip);
            TextView money = (TextView) view.findViewById(R.id.tv_money);
            tvTips.setText("连续登陆3天,送您" + moneyStr + "个爱心币");
            money.setText(moneyStr);
            final LinearLayout container = (LinearLayout) view.findViewById(R.id.container);
            container.removeAllViews();
            //将flakeView 添加到布局中
            container.addView(flakeView);
            //设置背景
            this.getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
            //设置同时出现在屏幕上的金币数量  建议64以内 过多会引起卡顿
            flakeView.addFlakes(8);
            /**
             * 绘制的类型
             * @see View.LAYER_TYPE_HARDWARE
             * @see View.LAYER_TYPE_SOFTWARE
             * @see View.LAYER_TYPE_NONE
             */
            flakeView.setLayerType(View.LAYER_TYPE_NONE, null);
            iv_onclick = (BofangView) view.findViewById(R.id.iv_onclick);
           // iv_onclick.setBackgroundResource(R.drawable.open_red_animation_drawable);
            iv_onclick.startAnation();
            iv_onclick.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (container!=null){
                        container.removeAllViews();
                    }
                    pop.dismiss();
                    GetToast.useString(getBaseContext(),"恭喜您,抢到红包");
                }
            });
            pop = new PopupWindow(view, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
            ColorDrawable dw = new ColorDrawable(getResources().getColor(R.color.half_color));
            pop.setBackgroundDrawable(dw);
            pop.setOutsideTouchable(true);
            pop.setFocusable(true);
            pop.showAtLocation(v, Gravity.CENTER, 0, 0);

            /**
             * 移除动画
             */
            final Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //设置2秒后
                        Thread.sleep(2000);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                container.removeAllViews();
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
            if (!show)
                thread.start();
            //ivOpen指的是需要播放动画的ImageView控件
    //        AnimationDrawable animationDrawable = (AnimationDrawable)iv_onclick.getBackground();
    //        animationDrawable.start();//启动动画
            MediaPlayer player = MediaPlayer.create(this, R.raw.shake);
            player.start();
            return pop;
        }

在Java中万物皆对象,一个金币就是一个对象,
拥有自己bitmap,宽高,大小,还有自己的坐标 利用属性动画进行改变每一个小金币的属性值
这里将每一个金币看作为一个类

    /**
     * 类功能描述:</br>
     *红包金币仿雨滴下落效果
     * @author yuyahao
     * @version 1.0 </p> 修改时间:</br> 修改备注:</br>
     */
    public class FlakeView extends View {

        Bitmap droid;
        int numFlakes = 0;
        ArrayList<Flake> flakes = new ArrayList<Flake>(); // List of current flakes
        public ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        long startTime, prevTime; // Used to track elapsed time for animations and fps
        int frames = 0;     // Used to track frames per second
        Paint textPaint;    // Used for rendering fps text
        float fps = 0;      // frames per second
        Matrix m = new Matrix(); // Matrix used to translate/rotate each flake during rendering
        String fpsString = "";
        String numFlakesString = "";
        /**
         * 利用属性动画进行改变每一个小金币的属性值
         * 这里是将每一个金币看作为一个类
         * 在Java中万物皆对象,一个金币就是一个对象,
         * 拥有自己bitmap,宽高,大小,还有自己的坐标
         * the animator
         */
        public FlakeView(Context context) {
            super(context);
            droid = BitmapFactory.decodeResource(getResources(), R.drawable.b);
            textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            textPaint.setColor(Color.WHITE);
            textPaint.setTextSize(24);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator arg0) {
                    long nowTime = System.currentTimeMillis();
                    float secs = (float) (nowTime - prevTime) / 100f;
                    prevTime = nowTime;
                    for (int i = 0; i < numFlakes; ++i) {
                        Flake flake = flakes.get(i);
                        flake.y += (flake.speed * secs);
                        if (flake.y > getHeight()) {
                            // If a flake falls off the bottom, send it back to the top
                            flake.y = 0 - flake.height;
                        }
                        flake.rotation = flake.rotation + (flake.rotationSpeed * secs);
                    }
                    // Force a redraw to see the flakes in their new positions and orientations
                    invalidate();
                }
            });
            animator.setRepeatCount(ValueAnimator.INFINITE);
            animator.setDuration(3000);
        }



        private void setNumFlakes(int quantity) {
            numFlakes = quantity;
            numFlakesString = "numFlakes: " + numFlakes;
        }

        /**
         *增加每一个小金币属性
         */
        public void addFlakes(int quantity) {
            for (int i = 0; i < quantity; ++i) {
                flakes.add(Flake.createFlake(getWidth(), droid,getContext()));
            }
            setNumFlakes(numFlakes + quantity);
        }

        /**
         * 减去指定数量的金币,其他的金币属性保持不变
         */
        void subtractFlakes(int quantity) {
            for (int i = 0; i < quantity; ++i) {
                int index = numFlakes - i - 1;
                flakes.remove(index);
            }
            setNumFlakes(numFlakes - quantity);
        }

        /**
         * nSizeChanged()实在布局发生变化时的回调函数,间接回去调用onMeasure, onLayout函数重新布局
         * @param w
         * @param h
         * @param oldw
         * @param oldh
         */
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            // Reset list of droidflakes, then restart it with 8 flakes
            flakes.clear();
            numFlakes = 0;
            addFlakes(16);
            // Cancel animator in case it was already running
            animator.cancel();
            // Set up fps tracking and start the animation
            startTime = System.currentTimeMillis();
            prevTime = startTime;
            frames = 0;
            animator.start();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            for (int i = 0; i < numFlakes; ++i) {
                Flake flake = flakes.get(i);
                m.setTranslate(-flake.width / 2, -flake.height / 2);
                m.postRotate(flake.rotation);
                m.postTranslate(flake.width / 2 + flake.x, flake.height / 2 + flake.y);
                canvas.drawBitmap(flake.bitmap, m, null);
            }
            ++frames;
            long nowTime = System.currentTimeMillis();
            long deltaTime = nowTime - startTime;
            if (deltaTime > 1000) {
                float secs = (float) deltaTime / 1000f;
                fps = (float) frames / secs;
                startTime = nowTime;
                frames = 0;
            }
        }
        /**
         * 生命周期 pause
         */
        public void pause() {
            animator.cancel();
        }
        /**
         * 生命周期 resume
         */
        public void resume() {
            animator.start();
        }
    }

利用PropertyValuesHolder实现图片的左右晃动效果

PropertyValuesHolder的作用:

PropertyValuesHolder这个类可以先将动画属性和值暂时的存储起来,后一起执行,在有些时候可以使用替换掉AnimatorSet,减少代码量

public static void doWaggleAnimation(View view) {
        PropertyValuesHolder rotation = PropertyValuesHolder.ofFloat("Rotation", 0f, -20f, 20f, -20f, 20f, -20f, 20f, -20f, 0f);
        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("ScaleX", 0.8f, 0.85f, 0.9f, 1f);
        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("ScaleY", 0.8f, 0.85f, 0.9f, 1f);
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, rotation, scaleX, scaleY);
        animator.setDuration(1000);
        animator.setRepeatMode(ValueAnimator.REVERSE);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.start();
    }

本文用于自定义动画比较多一些。

自定义动画总结

Android提供了几种动画类型:

  • View Animation
  • Drawable Animation
  • Property Animation

View Animation相当简单,不过只能支持简单的缩放、平移、旋转、透明度基本的动画,且有一定的局限性。比如:你希望View有一个颜色的切换动画;你希望可以使用3D旋转动画;你希望当动画停止时,View的位置就是当前的位置;这些View Animation都无法做到

Property Animation故名思议就是通过动画的方式改变对象的属性了,我们首先需要了解几个属性:

  • Duration动画的持续时间,默认300ms。

  • TimeInterpolation:定义动画变化速率的接口,所有插值器都必须实现此接口,如线性、非线性插值器;

  • TypeEvaluator:用于定义属性值计算方式的接口,有int、float、color类型,根据属性的起始、结束值和插值一起计算出当前时间的属性值;

  • Animation sets:动画集合,即可以同时对一个对象应用多个动画,这些动画可以同时播放也可以对不同动画设置不同的延迟;

  • Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响;

  • Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以让此动画一直重复,或播放完时向反向播放;

  • Frame refresh delay:帧刷新延迟,对于你的动画,多久刷新一次帧;默认为10ms,但最终依赖系统的当前状态;基本不用管。

    相关的类

  • ObjectAnimator 动画的执行类

    ObjectAnimator是其中比较容易使用的一个动画类,它继承自ValueAnimator,

    说比较容易使用是因为它在动画启动后自动监视属性值的变化并把值赋给对象属性,

    而ValueAnimator则只监视属性值的变化,但不会自动在属性中应用该值,因此我们需要手动应用这些值

  • ValueAnimator 动画的执行类

    ValueAnimtor动画的创建基本上和ObjectAnimator一样,只是我们需要手动应用属性值

  • AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。

  • AnimatorInflater 用户加载属性动画的xml文件

  • TypeEvaluator 类型估值,主要用于设置动画操作属性的值。

  • TimeInterpolator 时间插值

ValueAnimator和ObjectAnimator之间的关系:

 

自定义属性详解
自定义属性详解

 

掌握了这边可以轻松的写一个自定义属性动画了

PropertyValuesHolder的作用:

PropertyValuesHolder这个类可以先将动画属性和值暂时的存储起来,后一起执行,在有些时候可以使用替换掉AnimatorSet,减少代码量

项目中效果图:

 

抖动特效
抖动特效