【黄啊码】安卓实现支付宝中的蚂蚁森林效果

举报
黄啊码 发表于 2022/09/02 00:26:45 2022/09/02
6.3k+ 0 0
【摘要】 最近公司产品突然有一个类似支付宝蚂蚁森林的功能,大致功能跟支付宝蚂蚁森林相像,在看了一下支付宝蚂蚁森林的效果之后,打算先撸一个控件出来,等公司效果图出来之后就可以放上去直接使用。 首先我们先大致看下支付宝的蚂蚁森林效果图: 这是目前我实现的效果图: 当我们拿到这个需求时先分析一波,不要忙着就动手开干,不然容易平地翻车。。 需...

最近公司产品突然有一个类似支付宝蚂蚁森林的功能,大致功能跟支付宝蚂蚁森林相像,在看了一下支付宝蚂蚁森林的效果之后,打算先撸一个控件出来,等公司效果图出来之后就可以放上去直接使用。
首先我们先大致看下支付宝的蚂蚁森林效果图:

这是目前我实现的效果图:

当我们拿到这个需求时先分析一波,不要忙着就动手开干,不然容易平地翻车。。
需要实现的功能有:

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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。