专栏首页lzj_learn_noteAndroid红包雨动画

Android红包雨动画

红包雨

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

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;
    }
}
最后

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android RecyclerView之粘性头部+点击事件

    实现上图列表的粘性头部功能一般通过在布局页面额外写粘性头部View,然后通过监听列表的滑动来控制显示隐藏粘性头部View。而如果列表使用RecyclerView...

    用户3106371
  • 自定义无限循环ViewPager(一)――ViewPager初始化源码解析

    大部分app首页一般都会有个无限循环的广告轮播位,通常都是采用ViewPager来实现的,对此大家肯定不会感到陌生。而关于无限循环的ViewPager的实现,一...

    用户3106371
  • 自定义无限循环ViewPager(二)――ViewPager滑动原理解析

    在前面一篇文章中,已经分析了ViewPager初始化的原理,而本篇文章开始分析ViewPager的滑动及页面切换的原理。在阅读本文之前,大家可以先去了解下Scr...

    用户3106371
  • Android实现红包雨动画效果

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

    砸漏
  • appium+python自动化32-android_uiautomator定位进阶版

    前言 上一盘介绍uiautomator的定位方式都是类似这种'new UiSelector().xxx("xxx")',看起非常长,我也记不住,这很不pytho...

    上海-悠悠
  • 05-树8 File Transfer (25分)

    We have a network of computers and a list of bi-directional connections. Each of...

    AI那点小事
  • 浅谈JAVA设计模式之——观察者模式(Observer)

    定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

    冰河
  • SpringBoot 关于Feign的超时时间配置

    今天项目现场提过来一个问题 “公司发过来的封装好的 springboot 项目中的 feign 调用超时时间需要修改”,细问之后,具体的需求场景是这样的: 1、...

    烂猪皮
  • 百度Apollo源码学习之localization模块基础知识

    自动驾驶系统先通过起点终点规划出全局路径(routing);然后在行驶过程中感知(perception)当前环境(识别车辆行人路况标志等),并预测下一步发展;然...

    李小白是一只喵
  • 设计模式-从风控链理解责任链模式

    责任链是一种行为型模式。顾名思义,由多个有不同处理能力节点组成的一条执行链。当一个事件进来时,会判断当前节点是否有处理的能力,反之转入下一个节点进行处理。可以从...

    不一样的科技宅

扫码关注云+社区

领取腾讯云代金券