最近公司有个需求,需要一个类似于蚂蚁森林能量水滴浮动效果,所以有了这篇文章,目前在项目里,没时间提出来做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;
}
}
最后
有不足的地方欢迎指出,欢迎讨论!