Android之蚂蚁森林能量水滴效果

news2024/9/30 9:19:40

最近公司有个需求,需要一个类似于蚂蚁森林能量水滴浮动效果,所以有了这篇文章,目前在项目里,没时间提出来做demo,有代码欠缺的地方欢迎指出,一定补上。


文章目录

  • 一:效果图
  • 二:具体实现
    • 1.自定义圆球WaterView
    • 2.动态随机添加小球WaterFlake
    • 3:item布局(图片就是效果图的背景)
    • 4:xml布局
    • 5:activity使用
    • 6:Javabean(WaterModel)
  • 最后


一:效果图

第一张是蚂蚁效果图,第二张是项目里的效果图,换一下图片和设置一下文字颜色即可
在这里插入图片描述

Android雪花飘落效果以及仿蚂蚁森林能量水滴浮动效果

二:具体实现

1.自定义圆球WaterView

package com.mago.sports.utils;

/**
 * Created by :caoliulang
 * ❤
 * Creation time :2022/8/31
 * ❤
 * Function :自定义仿支付宝蚂蚁森林水滴View
 */
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class WaterView extends View {

    private Paint paint;
    private ObjectAnimator mAnimator;
    /**
     * 文字颜色
     */
    private int textColor = Color.parseColor("#69c78e");
    /**
     * 水滴填充颜色
     */
    private int waterColor = Color.parseColor("#c3f593");
    /**
     * 球描边颜色
     */
    private int storkeColor = Color.parseColor("#69c78e");
    /**
     * 描边线条宽度
     */
    private float strokeWidth = 0.5f;
    /**
     * 文字字体大小
     */
    private float textSize = 36;
    /**
     * 水滴球半径
     */
    private int mRadius = 30;
    /**
     * 圆球文字内容
     */
    private String textContent="";

    public WaterView(Context context,String textContent) {
        super(context);
        this.textContent=textContent;
        init();
    }

    public WaterView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public WaterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        drawCircleView(canvas);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(Utils.dp2px(getContext(), (int) (2 * (mRadius+strokeWidth))),Utils.dp2px(getContext(), (int) (2 * (mRadius+strokeWidth))));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.i("====》WaterView X",getX()+"==");
        Log.i("====》WaterView Y",getY()+"==");
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        start();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stop();
    }

    @Override
    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        if (visibility == VISIBLE) {
            start();
        } else {
            stop();
        }
    }

    private void drawCircleView(Canvas canvas){
        //圆球
        paint.setColor(waterColor);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), mRadius), paint);

        //描边
        paint.setColor(storkeColor);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(Utils.dp2px(getContext(), (int) strokeWidth));
        canvas.drawCircle(Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), (int) (mRadius+strokeWidth)) , paint);

        //圆球文字
        paint.setTextSize(textSize);
        paint.setColor(textColor);
        paint.setStyle(Paint.Style.FILL);
        drawVerticalText(canvas, Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), mRadius), textContent);
    }

    private void drawVerticalText(Canvas canvas, float centerX, float centerY, String text) {
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        float baseLine = -(fontMetrics.ascent + fontMetrics.descent) / 2;
        float textWidth = paint.measureText(text);
        float startX = centerX - textWidth / 2;
        float endY = centerY + baseLine;
        canvas.drawText(text, startX, endY, paint);
    }


    public void start() {
        if (mAnimator == null) {
            mAnimator = ObjectAnimator.ofFloat(this, "translationY", -6.0f, 6.0f, -6.0f);
            mAnimator.setDuration(3500);
            mAnimator.setInterpolator(new LinearInterpolator());
            mAnimator.setRepeatMode(ValueAnimator.RESTART);
            mAnimator.setRepeatCount(ValueAnimator.INFINITE);
            mAnimator.start();
        } else if (!mAnimator.isStarted()) {
            mAnimator.start();
        }
    }

    public void stop() {
        if (mAnimator != null) {
            mAnimator.cancel();
            mAnimator = null;
        }
    }

}

2.动态随机添加小球WaterFlake

package com.mago.sports.utils;

/**
 * Created by :caoliulang
 * ❤
 * Creation time :2022/8/31
 * ❤
 * Function :支付宝蚂蚁森林水滴能量
 */

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.mago.sports.R;
import com.mago.sports.model.WaterModel;

import java.util.Arrays;
import java.util.List;
import java.util.Random;


public class WaterFlake extends FrameLayout {
    private static final int WHAT_ADD_PROGRESS = 1;
    private OnWaterItemListener mOnWaterItemListener;
    /**
     * 小树坐标X
     */
    private float treeCenterX = 0;
    /**
     * 小树坐标Y
     */
    private float treeCenterY = 0;
    /**
     * 是否正在收集能量
     */
    private boolean isCollect = false;
    /**
     * view变化的y抖动范围
     */
    private static final int CHANGE_RANGE = 10;
    /**
     * 控制抖动动画执行的快慢
     */
    public static final int PROGRESS_DELAY_MILLIS = 12;
    /**
     * 控制水滴动画的偏移量
     */
    private List<Float> mOffsets = Arrays.asList(5.0f, 4.5f, 4.8f, 5.5f, 5.8f, 6.0f, 6.5f);
    private Random mRandom = new Random();
    private float mWidth, mHeight;
    private LayoutInflater mLayoutInflater;

    public WaterFlake(@NonNull Context context) {
        this(context, null);
    }

    public WaterFlake(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WaterFlake(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mLayoutInflater = LayoutInflater.from(getContext());
    }

    @Override
    public boolean performClick() {
        return super.performClick();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    /**
     * 设置小球数据,根据数据集合创建小球数量
     *
     * @param modelList 数据集合
     */
    public void setModelList(final List<WaterModel> modelList, float treeCenterX, float treeCenterY) {
        if (modelList == null || modelList.isEmpty()) {
            return;
        }
        this.treeCenterX = treeCenterX;
        this.treeCenterY = treeCenterY;
        removeAllViews();
        post(new Runnable() {
            @Override
            public void run() {
                addWaterView(modelList);
            }
        });
    }

    /**
     * 设置小球数据,根据数据集合创建小球数量
     *
     * @param modelList 数据集合
     */
    public void setModelList(final List<WaterModel> modelList, View view) {
        if (modelList == null || modelList.isEmpty()) {
            return;
        }
        this.treeCenterX = view.getX();
        this.treeCenterY = view.getY();
        removeAllViews();
        post(new Runnable() {
            @Override
            public void run() {
                addWaterView(modelList);
            }
        });

    }

    private void addWaterView(List<WaterModel> modelList) {
        int[] xRandom = randomCommon(1, 1, modelList.size());
        int[] yRandom = randomCommon(1, 1, modelList.size());
        if (xRandom == null || yRandom == null) {
            return;
        }
        for (int i = 0; i < modelList.size(); i++) {
            WaterModel waterModel = modelList.get(i);
            final View view = mLayoutInflater.inflate(R.layout.water_item1, this, false);
            TextView text_lk = view.findViewById(R.id.text_lk);
            text_lk.setText("LK:"+modelList.get(i).getContent());
            view.setX((float) ((mWidth * xRandom[i] * 0.11)));
            view.setY((float) ((mHeight * yRandom[i] * 0.08)));
            view.setTag(waterModel);
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    Object tag = v.getTag();
                    if (tag instanceof WaterModel) {
                        if (mOnWaterItemListener != null) {
                            mOnWaterItemListener.onItemClick((WaterModel) tag);
                            collectAnimator(view);
                        }
                    }
                }
            });
            view.setTag(R.string.isUp, mRandom.nextBoolean());
            setOffset(view);
            addView(view);
            addShowViewAnimation(view);
            start(view);
        }
    }

    /**
     * 设置小球点击事件
     *
     * @param onWaterItemListener
     */
    public void setOnWaterItemListener(OnWaterItemListener onWaterItemListener) {
//        mOnWaterItemListener = onWaterItemListener;
    }

    public interface OnWaterItemListener {
        void onItemClick(WaterModel waterModel);
    }

    private void collectAnimator(final View view) {
        if (isCollect) {
            return;
        }
        isCollect = true;

        ObjectAnimator translatAnimatorY = ObjectAnimator.ofFloat(view, "translationY", getTreeCenterY());
        translatAnimatorY.start();

        ObjectAnimator translatAnimatorX = ObjectAnimator.ofFloat(view, "translationX", getTreeCenterX());
        translatAnimatorX.start();

        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
        alphaAnimator.start();

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(translatAnimatorY).with(translatAnimatorX).with(alphaAnimator);
        animatorSet.setDuration(3000);
        animatorSet.start();
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                removeView(view);
                isCollect = false;
            }
        });
    }

    public void start(View view) {
        boolean isUp = (boolean) view.getTag(R.string.isUp);
        float offset = (float) view.getTag(R.string.offset);
        ObjectAnimator mAnimator = null;
        if (isUp) {
            mAnimator = ObjectAnimator.ofFloat(view, "translationY", view.getY() - offset, view.getY() + offset, view.getY() - offset);
        } else {
            mAnimator = ObjectAnimator.ofFloat(view, "translationY", view.getY() + offset, view.getY() - offset, view.getY() + offset);
        }
        mAnimator.setDuration(1800);
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.setRepeatMode(ValueAnimator.RESTART);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.start();
    }


    /**
     * 添加显示动画
     *
     * @param view
     */
    private void addShowViewAnimation(View view) {
        view.setAlpha(0);
        view.setScaleX(0);
        view.setScaleY(0);
        view.animate().alpha(1).scaleX(1).scaleY(1).setDuration(500).start();
    }

    /**
     * 随机指定范围内N个不重复的数
     * 最简单最基本的方法
     *
     * @param min 指定范围最小值
     * @param max 指定范围最大值
     * @param n   随机数个数
     */
    public static int[] randomCommon(int min, int max, int n) {
        if (n > (max - min + 1) || max < min) {
            return null;
        }
        int[] result = new int[n];
        int count = 0;
        while (count < n) {
            int num = (int) ((Math.random() * (max - min)) + min);
            boolean flag = true;
            for (int j = 0; j < n; j++) {
                if (num == result[j]) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                result[count] = num;
                count++;
            }
        }
        return result;
    }


    public float getTreeCenterX() {
        return treeCenterX;
    }

    public float getTreeCenterY() {
        return treeCenterY;
    }

    /**
     * 设置View的offset
     *
     * @param view
     */
    private void setOffset(View view) {
        float offset = mOffsets.get(mRandom.nextInt(mOffsets.size()));
        view.setTag(R.string.offset, offset);
    }

}

3:item布局(图片就是效果图的背景)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    xmlns:android="http://schemas.android.com/apk/res/android" >
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/home_lk"/>
    <TextView
        android:id="@+id/text_lk"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Lk.500"
        android:textColor="#263402"
        android:layout_centerInParent="true"
        android:textSize="6dp"
        />
</RelativeLayout>

4:xml布局

根据公司需求所以是40dp,放了三个WaterFlake,你们可以一个WaterFlake铺满即可

 <com.mago.sports.utils.WaterFlake
      android:id="@+id/mWaterFlake"
      android:layout_width="40dp"
      android:layout_height="40dp"
      android:layout_toRightOf="@+id/imag_addsb"
      android:visibility="gone"></com.mago.sports.utils.WaterFlake>

5:activity使用

1:变量

private WaterFlake mWaterFlake;//能量浮动

2:实例化

 mWaterFlake = findViewById(R.id.mWaterFlake);

3:点击事件

 mWaterFlake.setOnWaterItemListener(new WaterFlake.OnWaterItemListener() {
            @Override
            public void onItemClick(WaterModel pos) {
              
            }
        });

4:添加数据

 //此处目前写死坐标,后期可以获取小树的坐标添加进去
 mWaterFlake.setModelList(mModelList, text_start);

6:Javabean(WaterModel)

这里是一个数组,多个能量直接循环添加进去就行了

package com.mago.sports.model;

/**
 * Created by :caoliulang
 * ❤
 * Creation time :2022/8/31
 * ❤
 * Function :
 */
public class WaterModel {

    private String content;

    public WaterModel(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

最后

有不足的地方欢迎指出,欢迎讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/128631.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HotPDF Delphi PDF VCL完全形成PDF文档

HotPDF Delphi PDF VCL完全形成PDF文档 HotPDF Delphi PDF&#xff0c;用于创建和创建PDF文档的应用程序名称。您的公司已在您之前发布&#xff0c;以便在Delphi和C中使用。事实上&#xff0c;使用这个库&#xff0c;您将能够以不同的方式编辑、执行、构建和管理PDF文档。如果您…

configmap中data变成字符串问题(k8s踩坑)

发现问题 编写text文件&#xff0c;用于创建要给nginx的configmap&#xff0c;如下&#xff1a; [rootmaster redis]# vim txt apiVersion: v1 kind: ConfigMap metadata:name: nginx-confignamespace: redis data:nginx.conf: |#user nginx;worker_processes 1;#error_log…

【pandas】教程:4-显示数据

Pandas 显示数据 本节使用的数据为 data/air_quality_no2.csv&#xff0c;链接为 pandas案例和教程所使用的数据-机器学习文档类资源-CSDN文库 import pandas as pd import matplotlib.pyplot as pltair_quality pd.read_csv("data/air_quality_no2.csv", index_co…

知物由学 | SO VMP 加壳与混淆,为移动应用提供函数级保护

导读&#xff1a;VMP 是一种用于软件保护的软件&#xff0c;对软件进行加壳&#xff0c;加固厂商都有自己的 VMP 方案&#xff0c;但值得注意的是&#xff0c;native 层的 VMP 方案并不成熟&#xff0c;兼容性只是其中一个影响因素&#xff0c;性能更是导致该方案无法普及的重要…

2022年广西最新建筑八大员(材料员)模拟真题及答案

百分百题库提供建筑八大员&#xff08;材料员&#xff09;考试试题、建筑八大员&#xff08;材料员&#xff09;考试预测题、建筑八大员&#xff08;材料员&#xff09;考试真题、建筑八大员&#xff08;材料员&#xff09;证考试题库等,提供在线做题刷题&#xff0c;在线模拟考…

C++仍然是2023年值得学习的好语言吗?

如今所有学习C的人都不得不面临一个问题&#xff0c;即C已经大大失去了它受欢迎程度。它同时被三种编程语言所取代 - python&#xff0c;Java和JavaScript。这就引出了一个问题&#xff0c;2023年是否还值得学习C&#xff1f; 如果您学习C是为了在IT领域工作&#xff0c;那么学…

系分 - 软件工程

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 系分 - 软件工程 考点摘要 信息/软件系统的生命周期&#xff08;★★&#xff09;软件开发方法&#xff08;★★★&#xff09;软件开发模型&#xff08;★★★★&#xff09;逆向工程&#xff08;★&#xff09…

一步一步改写Observer观察者模式

简单的概述&#xff1a; Observer模式是建立一个一&#xff08;Subject&#xff09;对多&#xff08;Observer&#xff09;的依赖关系&#xff0c;并且做到当“一”变化的时候&#xff0c;依赖这个“一”的多也能够同步自动改变。 Observer的结构&#xff1a; Subject相当于…

H5 拖动排序 美食排行榜

尝试写一下拖动元素进行排序&#xff0c;真是想到什么去写什么 &#x1f602;&#xff0c;有的时候很多人老是跟我说&#xff0c;别人都封装好了&#xff0c;你为什么还要自己去实现一下&#xff0c;写的还没别人好。但我总感觉所有都用别人写好的&#xff0c;就放弃思考的机会…

linux的CPU使用率达到100%的快速定位方式

一.问题现象 Linux服务器&#xff08;操作系统版本是centos7.9&#xff09;上面部署了若干Java站点服务,突然收到运维的CPU异常g告警&#xff0c;到了影响业务的情况发生&#xff0c;经初步排查&#xff0c;未出现异常进程&#xff0c;排除挖矿病毒的原因。 二.排查思路 &am…

[Verilog]Verilog经典电路设计(一)

Verilog经典电路设计&#xff08;一&#xff09; 1.1 8位移位寄存器 module shifter (din , clk, clr, dout) ; input din, clk, clr; output [7:0] dout; reg [7:0] dout_data;always (posedge clk) begin if (!clr) dout_data < 8b0; //同步清 &#xff0c;高电平…

第二证券|美国巨头向欧盟宣战,暴跌25%,芯片大国告急

“暴利税”完全激怒美国动力巨子。 3万亿美国巨子直接将欧盟告上法庭&#xff0c;当地时间12月28日&#xff0c;埃克森美孚公司正式申述欧盟&#xff0c;要求其撤销对石油集团征收的一项新“暴利税”。该公司表明&#xff0c;欧盟方面征收“暴利税”的行为超出了法律权限。别的…

蓝牙学习八(配对与绑定)

1.简介 Paring&#xff08;配对&#xff09;和Bonding&#xff08;绑定&#xff09;是实现蓝牙射频通信安全的一种机制&#xff0c;有两点需要注意&#xff1a; Paring/bonding实现的是蓝牙链路层的安全&#xff0c;对应用层来说是完全透明的。也就是说&#xff0c;不管有没有…

GitHub入门指南(下)

三、新手必备的GitHub基本操作 1.配置SSH Key (1) 第一次使用时&#xff0c;要配置一下账户。 在 Git Bash 客户端&#xff0c;输入&#xff1a; git config --global user.name “这里输入你在GitHub的账户名” git config --global user.email “这里输入你在GitHub的注册邮…

基于51单片机的数字电压表(PCF8591)(Proteus仿真+程序)

编号&#xff1a;32 基于51单片机的数字电压表&#xff08;PCF8591&#xff09; 功能描述&#xff1a; 本设计由51单片机最小系统PCF8591模块四路模拟量输入模块一路DA输出液晶1602显示模块 1、主控制器是89C82单片机。 2、PCF8591模数转换器进行A/D转换&#xff0c;读取四路…

Java中常见的文件操作

作者&#xff1a;~小明学编程 文章专栏&#xff1a;JavaEE 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 目录 操作文件 File类 属性 构造方法 常见方法 重要方法的操作演示 文件内容的读写 FileInputStream OutputStream 按照字符读入 按照字符写入…

『分分钟玩转VueRouter●中』少开一把王者荣耀掌握VueRouter的基本使用

文章目录一、编程式路由导航二、缓存路由组件三、两个新的声明周期钩子四、路由守卫五、路由器的两种工作模式本篇博客会介绍Vue中的VueRouter的基本使用&#xff0c;编程式路由导航增加了我们进行路由跳转的灵活性&#xff0c;缓存路由组件保障了我们使用路由时的便捷性&#…

【高阶数据结构】搜索二叉树 经典习题讲解

&#x1f308;欢迎来到数据结构专栏~~搜索二叉树 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句…

【OpenFOAM】-olaFlow-算例6- waveFloatingObject

算例路径&#xff1a; olaFlow\tutorials\waveFloatingObject 算例描述&#xff1a; 波浪作用下的浮体的刚体运动&#xff0c;属于流固耦合&#xff08;FSI&#xff09;问题 学习目标&#xff1a; 动网格设置和使用&#xff0c;网格变形控制&#xff0c;浮体的物理参数设置&…

23种设计模式(二)——享元模式【对象性能】

文章目录意图什么时候使用享元享元模式的实现内部状态和外部状态享元模式的优缺点与其他模式的关系亦称&#xff1a; 缓存、Cache、Flyweight 意图 享元模式是一种结构型设计模式&#xff0c; 它摒弃了在每个对象中保存所有数据的方式&#xff0c; 通过共享多个对象所共有的相…