【黄啊码】安卓实现支付宝中的蚂蚁森林效果
最近公司产品突然有一个类似支付宝蚂蚁森林的功能,大致功能跟支付宝蚂蚁森林相像,在看了一下支付宝蚂蚁森林的效果之后,打算先撸一个控件出来,等公司效果图出来之后就可以放上去直接使用。
首先我们先大致看下支付宝的蚂蚁森林效果图:
这是目前我实现的效果图:
当我们拿到这个需求时先分析一波,不要忙着就动手开干,不然容易平地翻车。。
需要实现的功能有:
1、自定小圆球,圆球内文字、上下浮动、消失动画;
2、根据数据动态添加小球,并且位置随机分布在小树周围,不能重叠。这点是最重要的,涉及到一个随机位置生成算法的设计。
好了,当我们确定了我们要实现的功能之后就可以逐步开始撸代码了。
自定义圆球
这个比较容易实现,绘制一个圆,再在园内绘制文字,动画实现统一采用的是属性动画来实现,代码如下,注释写的比较详细就不一一解释了,懒...
-
/**
-
* @describe: 自定义仿支付宝蚂蚁森林水滴View
-
*/
-
-
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 float proportion;
-
/**
-
* 水滴球半径
-
*/
-
private int mRadius = 30;
-
/**
-
* 圆球文字内容
-
*/
-
private String textContent="3g";
-
-
public WaterView(Context context) {
-
super(context);
-
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 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;
-
}
-
}
-
-
public float getProportion() {
-
return proportion;
-
}
-
-
public void setProportion(float proportion) {
-
this.proportion = proportion;
-
}
-
}
动态随机添加小球
这里我采用的是集成FrameLayout 通过设置小球数据,动态将小球add进去,比较简便,在这里最重要是动态随机添加小球的算法,解决了这个算法就好办了。通过仔细观察支付宝蚂蚁森林的效果实现,我们可以发现一般小球都是在树的正上方随机分布的。所以我想以小树的根为中心,小树的高度为半径为一个扇形,在这个扇形上方随机摆放小球。
公式:坐标 = 旋转角度 * 半径 * 根据远近距离的不同计算得到的应该占的半径比例
圆上任一点(x1,y1)坐标的计算公式:
x1 = x0 + r * cos(ao * 3.14 /180 )
y1 = y0 + r * sin(ao * 3.14 /180 )
具体实现代码如下:
-
/**
-
* @describe: 支付宝蚂蚁森林水滴能量
-
*/
-
-
public class WaterFlake extends FrameLayout {
-
-
private OnWaterItemListener mOnWaterItemListener;
-
/**
-
* 小树坐标X
-
*/
-
private float treeCenterX = 0;
-
/**
-
* 小树坐标Y
-
*/
-
private float treeCenterY = 0;
-
/**
-
* 小树高度
-
*/
-
private int radius = 80;
-
/**
-
* 开始角度
-
*/
-
private double mStartAngle = 0;
-
/**
-
* 是否正在收集能量
-
*/
-
private boolean isCollect = false;
-
-
public WaterFlake(@NonNull Context context) {
-
super(context);
-
}
-
-
public WaterFlake(@NonNull Context context, @Nullable AttributeSet attrs) {
-
super(context, attrs);
-
}
-
-
public WaterFlake(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-
super(context, attrs, defStyleAttr);
-
}
-
-
@Override
-
public boolean onTouchEvent(MotionEvent event) {
-
if (event.getAction() == MotionEvent.ACTION_DOWN) {
-
int x = (int) event.getX();
-
int y = (int) event.getY();
-
Rect rect = new Rect();
-
for (int i = 0; i < getChildCount(); i++) {
-
getChildAt(i).getHitRect(rect);
-
if (rect.contains(x, y)) {
-
if (mOnWaterItemListener != null) {
-
getChildAt(i).performClick();
-
mOnWaterItemListener.onItemClick(i);
-
startAnimator(getChildAt(i));
-
return true;
-
}
-
}
-
}
-
}
-
return super.onTouchEvent(event);
-
}
-
-
@Override
-
public boolean performClick() {
-
return super.performClick();
-
}
-
-
@Override
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
measureChildren(widthMeasureSpec, heightMeasureSpec);
-
}
-
-
@Override
-
protected void onLayout(boolean changed, int l, int t, int r, int b) {
-
int childCount = getChildCount();
-
if (childCount==0){
-
return;
-
}
-
int left, top;
-
// 根据tem的个数,计算角度
-
float angleDelay = -180 / childCount;
-
for (int i = 0; i < childCount; i++) {
-
WaterView child = (WaterView) getChildAt(i);
-
mStartAngle %= 180;
-
-
//设置CircleView小圆点的坐标信息
-
//坐标 = 旋转角度 * 半径 * 根据远近距离的不同计算得到的应该占的半径比例
-
// 则圆上任一点为:(x1,y1)
-
// x1 = x0 + r * cos(ao * 3.14 /180 )
-
// y1 = y0 + r * sin(ao * 3.14 /180 )
-
if (child.getVisibility() != GONE) {
-
left = (int) (getTreeCenterX() + radius * Math.cos(mStartAngle * 3.14 / 180) * (child.getProportion() / radius * 2));
-
top = (int) (getTreeCenterY() + radius * Math.sin(mStartAngle * 3.14 / 180) * (child.getProportion() / radius * 2));
-
child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredWidth());
-
}
-
mStartAngle += angleDelay;
-
}
-
}
-
-
/**
-
* 设置小球数据,根据数据集合创建小球数量
-
*
-
* @param modelList 数据集合
-
*/
-
public void setModelList(List<WaterModel> modelList, float treeCenterX, float treeCenterY) {
-
this.treeCenterX = treeCenterX;
-
this.treeCenterY = treeCenterY;
-
for (int i = 0; i < modelList.size(); i++) {
-
WaterView waterView = new WaterView(getContext(),(i+1)+"g");
-
waterView.setProportion(Utils.getRandom(radius, radius + 80));
-
addView(waterView);
-
}
-
}
-
-
/**
-
* 设置小球点击事件
-
*
-
* @param onWaterItemListener
-
*/
-
public void setOnWaterItemListener(OnWaterItemListener onWaterItemListener) {
-
mOnWaterItemListener = onWaterItemListener;
-
}
-
-
public interface OnWaterItemListener {
-
void onItemClick(int pos);
-
}
-
-
private void startAnimator(final View view) {
-
if (isCollect) {
-
return;
-
}
-
isCollect = true;
-
-
ObjectAnimator translatAnimatorY = ObjectAnimator.ofFloat(view, "translationY", getTreeCenterY());
-
translatAnimatorY.start();
-
-
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
-
alphaAnimator.start();
-
-
AnimatorSet animatorSet = new AnimatorSet();
-
animatorSet.play(translatAnimatorY).with(alphaAnimator);
-
animatorSet.setDuration(3000);
-
animatorSet.start();
-
animatorSet.addListener(new AnimatorListenerAdapter() {
-
@Override
-
public void onAnimationEnd(Animator animation) {
-
removeViewInLayout(view);
-
isCollect = false;
-
}
-
});
-
}
-
-
public float getTreeCenterX() {
-
return treeCenterX;
-
}
-
-
public float getTreeCenterY() {
-
return treeCenterY;
-
}
-
}
小球摆放随机算法有多种实现方式,这只是其中一种,写的不好的地方,还望各位指正,欢迎大家一起交流学习。
目前代码还有一些地方需要完善的,逐步更新。。。
TODO
优化小球随机生成算法,保证每个小球尽量不会重叠
文章来源: markwcm.blog.csdn.net,作者:黄啊码,版权归原作者所有,如需转载,请联系作者。
原文链接:markwcm.blog.csdn.net/article/details/126642258
- 点赞
- 收藏
- 关注作者
评论(0)