老司機帶你順手擼一個支付寶螞蟻森林效果

老司機,不存在的,其實我還算不上,哈哈。。。 git

最近公司產品忽然有一個相似支付寶螞蟻森林的功能,大體功能跟支付寶螞蟻森林相像,在看了一下支付寶螞蟻森林的效果以後,原本這種東西用RN實現是最好不過的,不過產品既然決定選擇原生,咱們也不能把產品打死是吧,誰讓咱們是搬磚的呢?趁效果圖還沒出來,先擼一個控件出來,等公司效果圖出來以後就能夠放上去直接使用。github

首先咱們先大體看下支付寶的螞蟻森林效果圖: 算法

支付寶螞蟻森林

這是目前我實現的效果圖: canvas

自定實現效果

當咱們拿到這個需求時先分析一波,不要忙着就動手開幹,否則容易平地翻車。。 須要實現的功能有:bash

一、自定小圓球,圓球內文字、上下浮動、消失動畫;
二、根據數據動態添加小球,而且位置隨機分佈在小樹周圍,不能重疊。這點是最重要的,涉及到一個隨機位置生成算法的設計。

好了,當咱們肯定了咱們要實現的功能以後就能夠逐步開始擼代碼了。微信

自定義圓球

這個比較容易實現,繪製一個圓,再在園內繪製文字,動畫實現統一採用的是屬性動畫來實現,代碼以下,註釋寫的比較詳細就不一一解釋了,懶...dom

/**
 * @author: xiaohaibin.
 * @time: 2018/1/5
 * @mail:xhb_199409@163.com
 * @github:https://github.com/xiaohaibin
 * @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進去,比較簡便,在這裏最重要是動態隨機添加小球的算法,解決了這個算法就好辦了。經過仔細觀察支付寶螞蟻森林的效果實現,咱們能夠發現通常小球都是在樹的正上方隨機分佈的。因此我想以小樹的根爲中心,小樹的高度爲半徑爲一個扇形,在這個扇形上方隨機擺放小球。ide

公式:座標 = 旋轉角度 * 半徑 * 根據遠近距離的不一樣計算獲得的應該佔的半徑比例

圓上任一點(x1,y1)座標的計算公式:

x1 = x0 + r * cos(ao * 3.14 /180 )

y1 = y0 + r * sin(ao * 3.14 /180 )

具體實現代碼以下:學習

/**
 * @author: xiaohaibin.
 * @time: 2018/1/5
 * @mail:xhb_199409@163.com
 * @github:https://github.com/xiaohaibin
 * @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;
    }
}

複製代碼

小球擺放隨機算法有多種實現方式,這只是其中一種,寫的很差的地方,還望各位指正,歡迎你們一塊兒交流學習。字體

最後附上源碼連接

喜歡的話,麻煩給個Star支持一下,謝謝~~~

歡迎關注我的微信公衆號~

歡迎關注「大話安卓」微信公衆號
相關文章
相關標籤/搜索