Android之蚂蚁森林能量水滴效果

最近公司有个需求,需要一个类似于蚂蚁森林能量水滴浮动效果,所以有了这篇文章,目前在项目里,没时间提出来做demo,有代码欠缺的地方欢迎指出,一定补上。



一:效果图

第一张是蚂蚁效果图,第二张是项目里的效果图,换一下图片和设置一下文字颜色即可
在这里插入图片描述

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;
    }
}

最后

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

猜你喜欢

转载自blog.csdn.net/Android_Cll/article/details/128492118
今日推荐