package com.tejia.lijin.app.ui.goldtask.view;
|
|
import android.animation.Animator;
|
import android.animation.AnimatorListenerAdapter;
|
import android.animation.ValueAnimator;
|
import android.annotation.SuppressLint;
|
import android.content.Context;
|
import android.graphics.Point;
|
import android.os.Handler;
|
import android.os.Message;
|
import androidx.annotation.NonNull;
|
import androidx.annotation.Nullable;
|
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 com.tejia.lijin.app.R;
|
import com.tejia.lijin.app.ui.goldtask.GoldTaskActivity;
|
import com.tejia.lijin.app.ui.goldtask.Water;
|
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.List;
|
import java.util.Random;
|
|
/**
|
* 蚂蚁森林模拟(金币球展示)
|
* 处理思路:
|
* ->将水滴作为一个总体而不是单个的view,自定义一个ViewGroup容器
|
* ->循环创建view
|
* ->为view随机设置位置(在一些固定的集合中随机选取,尽量保证水滴不重合)
|
* ->为view设置一个初始的运动方向(注:由于每个view的运动方向不同,所以我选择将方向绑定到view的tag中)
|
* ->为view设置一个初始的速度(同理:将初始速度绑定到view的tag中)
|
* ->添加view到容器中,并缩放伴随透明度显示
|
* ->开启handler达到view上下位移动画(注意点:这里我们需要定一个临界值来改变view的速度,到达view时而快时而慢的目的)
|
* ->点击view后,缩放、透明度伴随位移移除水滴
|
* ->界面销毁时停止调用handler避免内存泄漏,空指针等异常
|
*/
|
public class WaterView extends FrameLayout {
|
private static final int WHAT_ADD_PROGRESS = 1;//添加进度
|
/**
|
* view变化的y抖动范围
|
*/
|
private static final int CHANGE_RANGE = 10;
|
/**
|
* 控制抖动动画执行的快慢,人眼不能识别16ms以下的
|
*/
|
public static final int PROGRESS_DELAY_MILLIS = 12;
|
/**
|
* 控制移除view的动画执行时间
|
*/
|
public static final int REMOVE_DELAY_MILLIS = 1200;
|
/**
|
* 添加水滴时动画显示view执行的时间
|
*/
|
public static final int ANIMATION_SHOW_VIEW_DURATION = 500;
|
/**
|
* 控制水滴动画的快慢
|
*/
|
private List<Float> mSpds = Arrays.asList(0.5f, 0.3f, 0.2f);
|
/**
|
* x最多可选取的随机数值(按需求 位子固定死了如有更改可改回随机)
|
*/
|
private static final List<Float> X_MAX_CHOSE_RANDOMS = Arrays.asList(
|
0.08f, 0.15f, 0.42f, 0.7f, 0.8f);
|
//0.01f, 0.05f, 0.1f, 0.2f, 0.3f, 0.4f, 0.45f, 0.5f, 0.55f, 0.6f, 0.65f, 0.7f, 0.75f, 0.8f, 0.85f);
|
/**
|
* y最多可选取的随机数值(按需求 位子固定死了如有更改可改回随机)
|
*/
|
private static final List<Float> Y_MAX_CHOSE_RANDOMS = Arrays.asList(
|
0.69f, 0.35f, 0.15f, 0.35f, 0.69f);
|
//0.01f, 0.06f, 0.1f, 0.15f, 0.2f, 0.25f, 0.3f, 0.36f, 0.41f, 0.47f, 0.52f, 0.58f, 0.61f, 0.67f, 0.7f, 0.75f, 0.8f);
|
/**
|
* x坐标当前可选的随机数组(按需求 位子固定死了如有更改可改回随机)
|
*/
|
private List<Float> mXCurrentCanShoseRandoms = new ArrayList<>();
|
/**
|
* y坐标当前可选的随机数组(按需求 位子固定死了如有更改可改回随机)
|
*/
|
private List<Float> mYCurrentCanShoseRandoms = new ArrayList<>();
|
|
/**
|
* 已经选取x的随机数值
|
*/
|
private List<Float> mXRandoms = new ArrayList<>();
|
/**
|
* 已经选取y的随机数值
|
*/
|
private List<Float> mYRandoms = new ArrayList<>();
|
|
|
private Random mRandom = new Random();
|
private List<View> mViews = new ArrayList<>();//装view
|
|
private LayoutInflater mInflater;
|
private int mTotalConsumeWater;//总的已经点击的水滴
|
private boolean isOpenAnimtion;//是否开启动画
|
private boolean isCancelAnimtion;//是否销毁动画
|
private int maxX, maxY;//子view的x坐标和y坐标的最大取值
|
private float mMaxSpace;//父控件对角线的距离
|
private Point mDestroyPoint;//view销毁时的点
|
|
public WaterView(@NonNull Context context) {
|
this(context, null);
|
}
|
|
public WaterView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
this(context, attrs, 0);
|
}
|
|
public WaterView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
super(context, attrs, defStyleAttr);
|
mInflater = LayoutInflater.from(getContext());
|
}
|
|
@SuppressLint("HandlerLeak")
|
private Handler mHandler = new Handler() {
|
@Override
|
public void handleMessage(Message msg) {
|
//根据isCancelAnimtion来标识是否退出,防止界面销毁时,再一次改变UI
|
if (isCancelAnimtion) {
|
return;
|
}
|
setOffSet();//设置浮动动画
|
if (mHandler != null) {
|
mHandler.sendEmptyMessageDelayed(WHAT_ADD_PROGRESS, PROGRESS_DELAY_MILLIS);//设置延时发送每隔12发一次
|
}
|
}
|
};
|
|
@Override
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
super.onSizeChanged(w, h, oldw, oldh);
|
|
// mDestroyPoint = new Point((int) h, h);
|
maxX = w;
|
maxY = h;
|
}
|
|
/**
|
* 重置子view
|
*/
|
public void reset() {
|
isCancelAnimtion = true;//是否销毁动画
|
isOpenAnimtion = false;//是否开启动画
|
for (int i = 0; i < mViews.size(); i++) {
|
removeView(mViews.get(i));
|
}
|
mViews.clear();
|
mXRandoms.clear();
|
mYRandoms.clear();
|
mYCurrentCanShoseRandoms.clear();
|
mXCurrentCanShoseRandoms.clear();
|
mHandler.removeCallbacksAndMessages(null);
|
}
|
|
/**
|
* 领取所有水滴
|
*/
|
public void setReceiveALL() {
|
if (mViews!=null&&mViews.size() > 0) {
|
for (final View mView : mViews) {
|
handViewClick(mView, false, null);//view的点击事件 移除动画
|
}
|
}
|
}
|
|
/**
|
* 设置水滴
|
*
|
* @param waters
|
*/
|
public void setWaters(final List<Water> waters, int x, int y, final boolean viewanimation) {
|
if (waters == null || waters.size() == 0) {
|
return;
|
}
|
// Log.e("eee", "setWaters: 设置水滴");
|
mMaxSpace = (float) (Math.sqrt(x * x + maxY * maxY));
|
mDestroyPoint = new Point(x, y);
|
btnY = y;
|
//确保初始化完成
|
post(new Runnable() {
|
@Override
|
public void run() {
|
setDatas(waters, viewanimation);
|
}
|
});
|
}
|
|
int btnY = 0;
|
|
/**
|
* 设置数据
|
*
|
* @param waters
|
* @param viewanimation
|
*/
|
private void setDatas(List<Water> waters, boolean viewanimation) {
|
reset();//重置子view
|
isCancelAnimtion = false;
|
setCurrentCanChoseRandoms();//设置xy坐标的范围
|
addWaterView(waters, viewanimation);//添加view
|
setViewsSpd();//设置view的浮动速度
|
startAnimation();//开启水滴抖动动画
|
}
|
|
/**
|
* 选取xy坐标的范围
|
*/
|
private void setCurrentCanChoseRandoms() {
|
mXCurrentCanShoseRandoms.addAll(X_MAX_CHOSE_RANDOMS);//x最多可选取的随机数值
|
mYCurrentCanShoseRandoms.addAll(Y_MAX_CHOSE_RANDOMS);//y最多可选取的随机数值
|
}
|
|
/**
|
* 添加水滴view
|
*/
|
private void addWaterView(List<Water> waters, boolean viewanimation) {
|
for (int i = 0; i < waters.size(); i++) {
|
final Water water = waters.get(i);
|
//子view的资源文件
|
int mChildViewRes = R.layout.water_item;
|
View view = mInflater.inflate(mChildViewRes, this, false);
|
TextView tvWater = view.findViewById(R.id.tv_water);
|
TextView tvWatertxt = view.findViewById(R.id.tv_watertxt);
|
view.setTag(water);
|
tvWatertxt.setText(water.taskName);
|
tvWater.setText("+" + String.valueOf(water.goldCoin));
|
view.setOnClickListener(new OnClickListener() {
|
@Override
|
public void onClick(View view) {
|
//传入activity 领取数量
|
((GoldTaskActivity) getContext()).handViewClick(view, water.id);
|
// handViewClick(view, true);//view的点击事件 移除动画
|
}
|
});
|
//随机设置view动画的方向
|
view.setTag(R.string.isUp, mRandom.nextBoolean());
|
setChildViewLocation(view, i);//设置view的位置
|
mViews.add(view);
|
addShowViewAnimation(view, viewanimation);//设置 添加动画
|
|
}
|
}
|
|
/**
|
* 添加单个水滴 view
|
*
|
* @param mWater
|
* @param i
|
*/
|
private void addWaterSingleView(Water mWater, int i) {
|
final Water water = mWater;
|
//子view的资源文件
|
int mChildViewRes = R.layout.water_item;
|
View view = mInflater.inflate(mChildViewRes, this, false);
|
TextView tvWater = view.findViewById(R.id.tv_water);
|
TextView tvWatertxt = view.findViewById(R.id.tv_watertxt);
|
view.setTag(water);
|
tvWatertxt.setText(water.taskName);
|
tvWater.setText("+" + String.valueOf(water.goldCoin));
|
view.setOnClickListener(new OnClickListener() {
|
@Override
|
public void onClick(View view) {
|
//传入activity 领取数量
|
((GoldTaskActivity) getContext()).handViewClick(view, water.id);
|
// handViewClick(view, true);//view的点击事件 移除动画
|
}
|
});
|
//随机设置view动画的方向
|
view.setTag(R.string.isUp, mRandom.nextBoolean());
|
setChildViewLocation(view, i);//设置view的位置
|
mViews.add(view);
|
addShowViewAnimation(view, true);//设置 添加动画
|
}
|
|
/**
|
* 添加显示动画
|
* 缩放动画 从透明到实体
|
* --------从0到1缩放
|
*
|
* @param view
|
* @param viewanimation 是否显示添加动画
|
*/
|
private void addShowViewAnimation(View view, boolean viewanimation) {
|
addView(view);
|
if (viewanimation) {//显示添加动画
|
view.setAlpha(0);//透明度0
|
view.setScaleX(0);//x缩放比例0
|
view.setScaleY(0);//y缩放比例0
|
view.animate().alpha(1)//透明度1
|
.scaleX(1)//x缩放比例1
|
.scaleY(1)//y缩放比例1
|
.setDuration(ANIMATION_SHOW_VIEW_DURATION)//持续时间 500
|
.start();
|
}
|
}
|
|
/**
|
* 处理view点击
|
*
|
* @param view
|
* @param b
|
*/
|
public void handViewClick(View view, final boolean b, Water mWater) {
|
// Log.e("eee", "处理view点击: ");
|
Object tag = view.getTag();
|
//移除当前集合中的该view
|
if (b) {
|
// Log.e("eee", "remove: ");
|
mViews.remove(view);
|
}
|
|
if (tag instanceof Water) {
|
Water waterTag = (Water) tag;
|
mTotalConsumeWater = waterTag.goldCoin;
|
if (b) {//表示 点击view不是一键领取
|
|
//传入activity 领取数量
|
((GoldTaskActivity) getContext()).onReceive(mTotalConsumeWater, waterTag.id);
|
// Log.e("eee", "handViewClick: " + waterTag.id);
|
((GoldTaskActivity) getContext()).mWater.remove(waterTag);//删除 activity的传入数据
|
// Log.e("eee", "移除当前集合中的该view: " + ((GoldTaskActivity) getContext()).mWater.size());
|
if (mWater != null) {//添加单个view水滴 更具上个点击的位置 重新添加
|
addWaterSingleView(mWater, (int) view.getTag(R.string.position));
|
}
|
}
|
}
|
view.setTag(R.string.original_y, view.getY());
|
animRemoveView(view);//移除动画
|
}
|
|
/**
|
* 设置view在父控件中的位置
|
* (按需求 位子固定死了如有更改可改回随机)
|
*
|
* @param view
|
* @param i
|
*/
|
private void setChildViewLocation(View view, int i) {
|
//随机位置
|
// view.setX((float) (maxX * getX_YRandom(mXCurrentCanShoseRandoms, mXRandoms)));
|
// view.setY((float) (maxY * getX_YRandom(mYCurrentCanShoseRandoms, mYRandoms)));
|
//死位置
|
view.setX((float) (maxX * mXCurrentCanShoseRandoms.get(i)));
|
view.setY((float) (maxY * mYCurrentCanShoseRandoms.get(i)));
|
view.setTag(R.string.original_y, view.getY());
|
view.setTag(R.string.position, i);//位置
|
}
|
|
/**
|
* 获取x轴或是y轴上的随机值
|
* 设置view 的位置
|
*
|
* @return
|
*/
|
private double getX_YRandom(List<Float> choseRandoms, List<Float> saveRandoms) {
|
|
if (choseRandoms.size() <= 0) {
|
//防止水滴别可选项的个数还要多,这里就重新对可选项赋值
|
setCurrentCanChoseRandoms();
|
}
|
//取用一个随机数,就移除一个随机数,达到不用循环遍历来确保获取不一样的值
|
float random = choseRandoms.get(mRandom.nextInt(choseRandoms.size()));
|
choseRandoms.remove(random);
|
saveRandoms.add(random);
|
return random;
|
}
|
|
/**
|
* 设置所有子view的加速度
|
*/
|
private void setViewsSpd() {
|
for (int i = 0; i < mViews.size(); i++) {
|
View view = mViews.get(i);
|
setSpd(view);
|
}
|
}
|
|
/**
|
* 设置View的spd
|
* 初始数度
|
*
|
* @param view
|
*/
|
private void setSpd(View view) {
|
float spd = mSpds.get(mRandom.nextInt(mSpds.size()));
|
view.setTag(R.string.spd, spd);
|
}
|
|
/**
|
* 设置偏移
|
* 设置view 的上下浮动
|
*/
|
private void setOffSet() {
|
for (int i = 0; i < mViews.size(); i++) {
|
View view = mViews.get(i);
|
//拿到上次view保存的速度
|
float spd = 0;
|
if (view.getTag(R.string.spd) != null) {
|
spd = (float) view.getTag(R.string.spd);
|
} else {
|
spd = 0.3f;
|
}
|
//水滴初始的位置
|
float original = (float) view.getTag(R.string.original_y);
|
float step = spd;
|
boolean isUp = (boolean) view.getTag(R.string.isUp);
|
float translationY;
|
//根据水滴tag中的上下移动标识移动view
|
if (isUp) {
|
translationY = view.getY() - step;
|
} else {
|
translationY = view.getY() + step;
|
}
|
//对水滴位移范围的控制
|
if (translationY - original > CHANGE_RANGE) {
|
translationY = original + CHANGE_RANGE;
|
view.setTag(R.string.isUp, true);
|
} else if (translationY - original < -CHANGE_RANGE) {
|
translationY = original - CHANGE_RANGE;
|
// FIXME:每次当水滴回到初始点时再一次设置水滴的速度,从而达到时而快时而慢
|
setSpd(view);
|
view.setTag(R.string.isUp, false);
|
}
|
view.setY(translationY);
|
}
|
}
|
|
|
/**
|
* 获取两个点之间的距离
|
* view所在点到移除点的距离
|
*
|
* @param p1
|
* @param p2
|
* @return
|
*/
|
public float getDistance(Point p1, Point p2) {
|
float _x = Math.abs(p2.x - p1.x);
|
float _y = Math.abs(p2.y - p1.y);
|
return (float) Math.sqrt((_x * _x) + (_y * _y));
|
}
|
|
/**
|
* 动画移除view
|
*
|
* @param view
|
*/
|
private void animRemoveView(final View view) {
|
final float x = view.getX();//子view 的x坐标
|
final float y = view.getY();//子view 的y坐标
|
//计算直线距离
|
final float space = getDistance(new Point((int) x, (int) y), mDestroyPoint);
|
Point startPoint = new Point((int) x, (int) y);//初始子view坐标
|
//更具子view坐标移动到最后坐标 然后返回移动坐标
|
ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(), startPoint, mDestroyPoint);
|
//根据距离计算动画执行时间
|
animator.setDuration((long) (space / (REMOVE_DELAY_MILLIS / mMaxSpace)));
|
animator.setInterpolator(new LinearInterpolator());
|
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
@Override
|
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
if (isCancelAnimtion) {//销毁动画不执行
|
return;
|
}
|
//返回的 实时子view坐标
|
Point value = (Point) valueAnimator.getAnimatedValue();
|
float space2 = getDistance(new Point((int) value.x, (int) value.y), mDestroyPoint);
|
float alpha = (1 - ((space - space2) / space));//更具距离大小 计算缩放比例
|
// setViewProperty(view, alpha < 0.95f ? alpha + 0.25f : alpha, value.y, value.x);//设置移除动画
|
setViewProperty(view, alpha < 0.45f ? (alpha + 0.53f) : 1, value.y, value.x);//设置移除动画
|
}
|
});
|
animator.addListener(new AnimatorListenerAdapter() {
|
@Override
|
public void onAnimationEnd(Animator animation) {
|
//结束时从容器移除水滴
|
removeView(view);
|
}
|
});
|
animator.start();
|
}
|
|
/**
|
* 设置view的属性
|
* 移动view并且缩放
|
*
|
* @param view
|
* @param translationY
|
* @param translationX
|
*/
|
public void setViewProperty(View view, float alpha, float translationY, float translationX) {
|
view.setTranslationX(translationX);//向y坐标移动
|
view.setTranslationY(translationY);//向x坐标移动
|
// view.setAlpha(alpha);//设置绘制图形的透明度。
|
view.setScaleY(alpha);//X轴缩放比例
|
view.setScaleX(alpha);//y轴缩放比例
|
}
|
|
/**
|
* 开启水滴抖动动画
|
*/
|
private void startAnimation() {
|
if (isOpenAnimtion) {//动画已经开启 不执行
|
return;
|
}
|
|
mHandler.sendEmptyMessage(WHAT_ADD_PROGRESS);
|
isOpenAnimtion = true;//开启动画
|
}
|
|
/**
|
* 界面销毁时回调
|
*/
|
@Override
|
protected void onDetachedFromWindow() {
|
super.onDetachedFromWindow();
|
onDestroy();
|
}
|
|
/**
|
* 销毁
|
*/
|
private void onDestroy() {
|
isCancelAnimtion = true;//销毁动画
|
mHandler.removeCallbacksAndMessages(this);
|
}
|
}
|