最近公司有个需求,需要一个类似于蚂蚁森林能量水滴浮动效果,所以有了这篇文章,目前在项目里,没时间提出来做demo,有代码欠缺的地方欢迎指出,一定补上。
第一张是蚂蚁效果图,第二张是项目里的效果图,换一下图片和设置一下文字颜色即可
Android雪花飘落效果以及仿蚂蚁森林能量水滴浮动效果
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;}}}
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);}}
根据公司需求所以是40dp,放了三个WaterFlake,你们可以一个WaterFlake铺满即可
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);
这里是一个数组,多个能量直接循环添加进去就行了
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;}
}
有不足的地方欢迎指出,欢迎讨论!