Android之蚂蚁森林能量水滴效果
创始人
2024-05-03 11:28:20
0

最近公司有个需求,需要一个类似于蚂蚁森林能量水滴浮动效果,所以有了这篇文章,目前在项目里,没时间提出来做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);}@Overridepublic void draw(Canvas canvas) {super.draw(canvas);drawCircleView(canvas);}@Overrideprotected 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))));}@Overrideprotected 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()+"==");}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();start();}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();stop();}@Overrideprotected 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 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());}@Overridepublic boolean performClick() {return super.performClick();}@Overrideprotected 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 modelList, float treeCenterX, float treeCenterY) {if (modelList == null || modelList.isEmpty()) {return;}this.treeCenterX = treeCenterX;this.treeCenterY = treeCenterY;removeAllViews();post(new Runnable() {@Overridepublic void run() {addWaterView(modelList);}});}/*** 设置小球数据,根据数据集合创建小球数量** @param modelList 数据集合*/public void setModelList(final List modelList, View view) {if (modelList == null || modelList.isEmpty()) {return;}this.treeCenterX = view.getX();this.treeCenterY = view.getY();removeAllViews();post(new Runnable() {@Overridepublic void run() {addWaterView(modelList);}});}private void addWaterView(List 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() {@Overridepublic 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() {@Overridepublic 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布局(图片就是效果图的背景)




4:xml布局

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

 

5:activity使用

1:变量

private WaterFlake mWaterFlake;//能量浮动

2:实例化

 mWaterFlake = findViewById(R.id.mWaterFlake);

3:点击事件

 mWaterFlake.setOnWaterItemListener(new WaterFlake.OnWaterItemListener() {@Overridepublic 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;}
}

最后

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

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...