Android红包雨动画

作者: time_fly | 来源:发表于2017-07-20 16:09 被阅读815次
    红包雨

    关于实现上面红包雨效果步骤如下:

    1.创建一个红包实体类
    public class RedPacket {
        public float x, y;
        public float rotation;
        public float speed;
        public float rotationSpeed;
        public int width, height;
        public Bitmap bitmap;
        public int money;
        public boolean isRealRed;
    
        public RedPacket(Context context, Bitmap originalBitmap, int speed, float maxSize, float minSize, int viewWidth) {
            //获取一个显示红包大小的倍数
            double widthRandom = Math.random();
            if (widthRandom < minSize || widthRandom > maxSize) {
                widthRandom = maxSize;
            }
            //红包的宽度
            width = (int) (originalBitmap.getWidth() * widthRandom);
            //红包的高度
            height = width * originalBitmap.getHeight() / originalBitmap.getWidth();
            int mWidth = (viewWidth == 0) ? context.getResources().getDisplayMetrics().widthPixels : viewWidth;
            //生成红包bitmap
            bitmap = Bitmap.createScaledBitmap(originalBitmap, width, height, true);
            originalBitmap.recycle();
            Random random = new Random();
            //红包起始位置x:[0,mWidth-width]
            int rx = random.nextInt(mWidth) - width;
            x = rx <= 0 ? 0 : rx;
            //红包起始位置y
            y = -height;
            //初始化该红包的下落速度
            this.speed = speed + (float) Math.random() * 1000;
            //初始化该红包的初始旋转角度
            rotation = (float) Math.random() * 180 - 90;
            //初始化该红包的旋转速度
            rotationSpeed = (float) Math.random() * 90 - 45;
            //初始化是否为中奖红包
            isRealRed = isRealRedPacket();
        }
    
        /**
         * 判断当前点是否包含在区域内
         */
        public boolean isContains(float x, float y) {
            //稍微扩大下点击的区域
            return this.x-50 < x && this.x +50 + width > x
                    && this.y-50 < y && this.y+50 + height > y;
        }
    
        /**
         * 随机 是否为中奖红包
         */
        public boolean isRealRedPacket() {
            Random random = new Random();
            int num = random.nextInt(10) + 1;
            //如果[1,10]随机出的数字是2的倍数 为中奖红包
            if (num % 2 == 0) {
                money = num*2;//中奖金额
                return true;
            }
            return false;
        }
    
        /**
         * 回收图片
         */
        public void recycle() {
            if (bitmap!= null && !bitmap.isRecycled()){
                bitmap.recycle();
            }
        }
    }
    

    上面就红包实体类的源码,重点就是在创建红包实体的时候,初始化红包相关的值,如生成红包图片,图片的宽高,红包初始位置,下落速度等。比较简单。

    2.自定义红包雨view
    • view初始化
      public RedPacketTest(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RedPacketStyle);
            //获取xml中配置的view的style属性,如下落红包数量,下落的基础速度,以及红包图片的最大最小范围
            count = typedArray.getInt(R.styleable.RedPacketStyle_count, 20);
            speed = typedArray.getInt(R.styleable.RedPacketStyle_speed, 20);
            minSize = typedArray.getFloat(R.styleable.RedPacketStyle_min_size, 0.5f);
            maxSize = typedArray.getFloat(R.styleable.RedPacketStyle_max_size, 1.2f);
            typedArray.recycle();
            init();
        }
    
    
        /**
         * 初始化
         */
        private void init() {
            //初始化画笔
            paint = new Paint();
            paint.setFilterBitmap(true);
            paint.setDither(true);
            paint.setAntiAlias(true);
            //创建一个属性动画,通过属性动画来控制刷新红包下落的位置
            animator = ValueAnimator.ofFloat(0, 1);
           //绘制view开启硬件加速
            setLayerType(View.LAYER_TYPE_HARDWARE, null);
          //初始化属性动画
            initAnimator();
        }
    
        private void initAnimator() {
            //每次动画更新的时候,更新红包下落的坐标值
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    long nowTime = System.currentTimeMillis();
                    //获取两次动画更新之间的时间,以此来计算下落的高度
                    float secs = (float) (nowTime - prevTime) / 1000f;
                    prevTime = nowTime;
                    for (int i = 0; i < redpacketlist.size(); ++i) {
                        RedPacket  redPacket = redpacketlist.get(i);
                        //更新红包的下落的位置y
                        redPacket.y += (redPacket.speed * secs);
    
                        //如果y坐标大于view的高度 说明划出屏幕,y重新设置起始位置,以及中奖属性
                        if (redPacket.y > getHeight()) {
                            redPacket.y = 0 - redPacket.height;
                            redPacket.isRealRed = redPacket.isRealRedPacket();
                        }
                        //更新红包的旋转的角度
                        redPacket.rotation = redPacket.rotation
                                + (redPacket.rotationSpeed * secs);
                    }
                    //重绘
                    invalidate();
                }
            });
            //属性动画无限循环
            animator.setRepeatCount(ValueAnimator.INFINITE);
            //属性值线性变换
            animator.setInterpolator(new LinearInterpolator());
            animator.setDuration(0);
        }
    
    • view绘制
     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //获取自定义view的宽度
            mWidth = getMeasuredWidth();
        }
    
        @Override
        protected void onDraw(final Canvas canvas) {
            //遍历红包数组,绘制红包
            for (int i = 0; i < redpacketlist.size(); i++) {
                RedPacket  redPacket = redpacketlist.get(i);
                //将红包旋转redPacket.rotation角度后 移动到(redPacket.x,redPacket.y)进行绘制红包
                Matrix m = new Matrix();
                m.setTranslate(-redPacket.width / 2, -redPacket.height / 2);
                m.postRotate(redPacket.rotation);
                m.postTranslate(redPacket.width / 2 + redPacket.x, redPacket.height / 2 + redPacket.y);
                //绘制红包
                canvas.drawBitmap(redPacket.bitmap, m, paint);
            }
        }
    
    
    • 红包雨动画开始结束
      /**
         *停止动画
         */
        public void stopRainNow() {
            //清空红包数据
            clear();
            //重绘
            invalidate();
            //动画取消
            animator.cancel();
        }
    
    
        /**
         * 开始动画
         */
        public void startRain() {
            //清空红包数据
            clear();
            //添加红包
            setRedpacketCount(count);
            prevTime = System.currentTimeMillis();
            //动画开始
            animator.start();
    
        }
    
        public void setRedpacketCount(int count) {
            if (mImgIds == null || mImgIds.length == 0)
                return;
            for (int i = 0; i < count; ++i) {
                //获取红包原始图片
                Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), mImgIds[0]);
                //生成红包实体类
                RedPacket redPacket = new RedPacket(getContext(), originalBitmap, speed,maxSize,minSize,mWidth);
                //添加进入红包数组
                redpacketlist.add(redPacket);
            }
        }
    
        /**
         * 暂停红包雨
         */
        public void pauseRain() {
            animator.cancel();
        }
    
        /**
         * 重新开始
         */
        public void restartRain() {
            animator.start();
        }
    
        /**
         * 清空红包数据,并回收红包中的bitmap
         */
        private void clear() {
            for (RedPacket redPacket :redpacketlist) {
                redPacket.recycle();
            }
            redpacketlist.clear();
        }
    
    • 红包点击事件
       @Override
        public boolean onTouchEvent(MotionEvent motionEvent) {
            switch (motionEvent.getAction()){
                case MotionEvent.ACTION_DOWN:
                    //根据点击的坐标点,判断是否点击在红包的区域
                    RedPacket redPacket = isRedPacketClick(motionEvent.getX(), motionEvent.getY());
                    if (redPacket != null) {
                        //如果点击在红包上,重新设置起始位置,以及中奖属性
                        redPacket.y = 0 - redPacket.height;
                        redPacket.isRealRed = redPacket.isRealRedPacket();
                        if (onRedPacketClickListener != null) {
                            onRedPacketClickListener.onRedPacketClickListener(redPacket);
                        }
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
    
                    break;
            }
            return true;
        }
    
        //根据点击坐标点,遍历红包数组,看是否点击在红包上
        private RedPacket isRedPacketClick(float x, float y) {
            for (int i = redpacketlist.size() - 1; i >= 0; i --) {
                if (redpacketlist.get(i).isContains(x, y)) {
                    return redpacketlist.get(i);
                }
            }
            return null;
        }
    

    关于自定义红包雨view的主要代码以及分析基本完成了。下面是自定义view的使用。

    3.自定义view的使用
    • 红包雨Activity
    public class RedPacketActivity extends AppCompatActivity implements View.OnClickListener {
        private RedPacketTest redRainView1;
        private Button start, stop;
        private TextView money;
        private int totalmoney = 0;
        AlertDialog.Builder ab;
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.red_rain);
            ab = new AlertDialog.Builder(RedPacketActivity.this);
            start = (Button) findViewById(R.id.start);
            stop = (Button) findViewById(R.id.stop);
            money = (TextView) findViewById(R.id.money);
            redRainView1 = (RedPacketTest) findViewById(R.id.red_packets_view1);
            start.setOnClickListener(this);
            stop.setOnClickListener(this);
    
        }
    
        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.start) {
                startRedRain();
            } else if (v.getId() == R.id.stop) {
                stopRedRain();
            }
        }
    
        /**
         * 开始下红包雨
         */
        private void startRedRain() {
            redRainView1.startRain();
            redRainView1.setOnRedPacketClickListener(new RedPacketTest.OnRedPacketClickListener() {
                @Override
                public void onRedPacketClickListener(RedPacket redPacket) {
                    redRainView1.pauseRain();
                    ab.setCancelable(false);
                    ab.setTitle("红包提醒");
                    ab.setNegativeButton("继续抢红包", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            redRainView1.restartRain();
                        }
                    });
    
                    if (redPacket.isRealRed) {
                        ab.setMessage("恭喜你,抢到了" + redPacket.money + "元!");
                        totalmoney += redPacket.money;
                        money.setText("中奖金额: " + totalmoney);
                    } else {
                        ab.setMessage("很遗憾,下次继续努力!");
                    }
                    redRainView1.post(new Runnable() {
                        @Override
                        public void run() {
                            ab.show();
                        }
                    });
                }
            });
        }
    
        /**
         * 停止下红包雨
         */
        private void stopRedRain() {
            totalmoney = 0;//金额清零
            redRainView1.stopRainNow();
        }
    
    • 红包雨Activity的xml
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#80000000">
    
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/red_packets_bg" />
        <Button
            android:id="@+id/start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始"
            />
        <Button
            android:id="@+id/stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:text="结束"
            />
    
        <TextView
            android:id="@+id/money"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="中奖金额:"
            android:textSize="18sp"
            android:layout_marginTop="10dp"
            />
    
        <com.example.test.redpacketrain.RedPacketTest
            android:id="@+id/red_packets_view1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:count="20"
            app:max_size="0.8"
            app:min_size="0.6"
            app:speed="500" />
    </RelativeLayout>
    
    • 自定义view的styleable
    <resources>
        <declare-styleable name="RedPacketStyle">
            <attr name="count" format="integer" />
            <attr name="speed" format="integer" />
            <attr name="max_size" format="float" />
            <attr name="min_size" format="float" />
        </declare-styleable>
    </resources>
    
    • 完整的自定义view代码
    public class RedPacketTest extends View {
        private int[] mImgIds = new int[]{
                R.drawable.red_packets_icon
        };//红包图片
        private int count;//红包数量
        private int speed;//下落速度
        private float maxSize;//红包大小的范围
        private float minSize;//红包大小的范围
        private int mWidth;//view宽度
        private ValueAnimator animator;//属性动画,用该动画来不断改变红包下落的坐标值
    
        private Paint paint;//画笔
        private long prevTime;
        private ArrayList<RedPacket> redpacketlist = new ArrayList<>();//红包数组
    
        public RedPacketTest(Context context) {
            super(context);
            init();
        }
    
        public RedPacketTest(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RedPacketStyle);
            count = typedArray.getInt(R.styleable.RedPacketStyle_count, 20);
            speed = typedArray.getInt(R.styleable.RedPacketStyle_speed, 20);
            minSize = typedArray.getFloat(R.styleable.RedPacketStyle_min_size, 0.5f);
            maxSize = typedArray.getFloat(R.styleable.RedPacketStyle_max_size, 1.2f);
            typedArray.recycle();
            init();
        }
    
    
        /**
         * 初始化
         */
        private void init() {
            paint = new Paint();
            paint.setFilterBitmap(true);
            paint.setDither(true);
            paint.setAntiAlias(true);
            animator = ValueAnimator.ofFloat(0, 1);
            setLayerType(View.LAYER_TYPE_HARDWARE, null);
            initAnimator();
        }
    
        private void initAnimator() {
            //每次动画更新的时候,更新红包下落的坐标值
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    long nowTime = System.currentTimeMillis();
                    float secs = (float) (nowTime - prevTime) / 1000f;
                    prevTime = nowTime;
                    for (int i = 0; i < redpacketlist.size(); ++i) {
                        RedPacket  redPacket = redpacketlist.get(i);
                        //更新红包的下落的位置y
                        redPacket.y += (redPacket.speed * secs);
    
                        //如果y坐标大于view的高度 说明划出屏幕,y重新设置起始位置,以及中奖属性
                        if (redPacket.y > getHeight()) {
                            redPacket.y = 0 - redPacket.height;
                            redPacket.isRealRed = redPacket.isRealRedPacket();
                        }
                        //更新红包的旋转的角度
                        redPacket.rotation = redPacket.rotation
                                + (redPacket.rotationSpeed * secs);
                    }
                    invalidate();
                }
            });
            //属性动画无限循环
            animator.setRepeatCount(ValueAnimator.INFINITE);
            //属性值线性变换
            animator.setInterpolator(new LinearInterpolator());
            animator.setDuration(0);
        }
    
    
        /**
         *停止动画
         */
        public void stopRainNow() {
            //清空红包数据
            clear();
            //重绘
            invalidate();
            //动画取消
            animator.cancel();
        }
    
    
        /**
         * 开始动画
         */
        public void startRain() {
            //清空红包数据
            clear();
            //添加红包
            setRedpacketCount(count);
            prevTime = System.currentTimeMillis();
            //动画开始
            animator.start();
    
        }
    
        public void setRedpacketCount(int count) {
            if (mImgIds == null || mImgIds.length == 0)
                return;
            for (int i = 0; i < count; ++i) {
                //获取红包原始图片
                Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), mImgIds[0]);
                //生成红包实体类
                RedPacket redPacket = new RedPacket(getContext(), originalBitmap, speed,maxSize,minSize,mWidth);
                //添加进入红包数组
                redpacketlist.add(redPacket);
            }
        }
    
        /**
         * 暂停红包雨
         */
        public void pauseRain() {
            animator.cancel();
        }
    
        /**
         * 重新开始
         */
        public void restartRain() {
            animator.start();
        }
    
        /**
         * 清空红包数据,并回收红包中的bitmap
         */
        private void clear() {
            for (RedPacket redPacket :redpacketlist) {
                redPacket.recycle();
            }
            redpacketlist.clear();
        }
    
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //获取自定义view的宽度
            mWidth = getMeasuredWidth();
        }
    
        @Override
        protected void onDraw(final Canvas canvas) {
            //遍历红包数组,绘制红包
            for (int i = 0; i < redpacketlist.size(); i++) {
                RedPacket  redPacket = redpacketlist.get(i);
                //将红包旋转redPacket.rotation角度后 移动到(redPacket.x,redPacket.y)进行绘制红包
                Matrix m = new Matrix();
                m.setTranslate(-redPacket.width / 2, -redPacket.height / 2);
                m.postRotate(redPacket.rotation);
                m.postTranslate(redPacket.width / 2 + redPacket.x, redPacket.height / 2 + redPacket.y);
                //绘制红包
                canvas.drawBitmap(redPacket.bitmap, m, paint);
            }
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent motionEvent) {
            switch (motionEvent.getAction()){
                case MotionEvent.ACTION_DOWN:
                    //根据点击的坐标点,判断是否点击在红包的区域
                    RedPacket redPacket = isRedPacketClick(motionEvent.getX(), motionEvent.getY());
                    if (redPacket != null) {
                        //如果点击在红包上,重新设置起始位置,以及中奖属性
                        redPacket.y = 0 - redPacket.height;
                        redPacket.isRealRed = redPacket.isRealRedPacket();
                        if (onRedPacketClickListener != null) {
                            onRedPacketClickListener.onRedPacketClickListener(redPacket);
                        }
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
    
                    break;
            }
            return true;
        }
    
        //根据点击坐标点,遍历红包数组,看是否点击在红包上
        private RedPacket isRedPacketClick(float x, float y) {
            for (int i = redpacketlist.size() - 1; i >= 0; i --) {
                if (redpacketlist.get(i).isContains(x, y)) {
                    return redpacketlist.get(i);
                }
            }
            return null;
        }
    
        public interface OnRedPacketClickListener {
            void onRedPacketClickListener(RedPacket redPacket);
        }
    
        private OnRedPacketClickListener onRedPacketClickListener;
        public void setOnRedPacketClickListener(OnRedPacketClickListener onRedPacketClickListener) {
            this.onRedPacketClickListener = onRedPacketClickListener;
        }
    }
    
    最后

    如果本文对各位有所帮助,希望大家能点个喜欢,谢谢!

    相关文章

      网友评论

      • 封丶存:你好 我想点击的红包的时候 有一个打开红包的动画怎么做啊?
      • 半夏微凉sheila:你好,在7.0的手机上 红包动画没反应,掉落不下来 能给看下么
        孤独蓝天:@time_fly 看似简单的参数却让我饶了一大圈,所以如果不能确定特殊值的含义的兼容性的时候还是默认的值作为demo比较好哦!
        time_fly:@半夏微凉sheila 谢谢,帮忙找出了7.0手机上的问题,因为身边没7.0的手机所以没发现这个问题
        半夏微凉sheila:解决了 , animator.setDuration(0); 把这里的 0 改成其他值,就可以

      本文标题:Android红包雨动画

      本文链接:https://www.haomeiwen.com/subject/sejdkxtx.html