Old driver, does not exist, actually I am not, ha ha…

Recently the company product suddenly there was a similar pay treasure ant the function of the forest, roughly function with pay treasure forest like ants, the ants look at the pay treasure forest, the effect of original it is best to implement in RN, but product since decided to choose the native, we also can’t kill the product right, who let us be moved brick? While the effect map has not come out, first out of a control, such as the company effect map can be put up directly after use.

First of all, let’s take a look at the ant Forest effect of Alipay:

Here is my implementation so far:

When we get this demand first analysis of a wave, do not busy to start dry, otherwise easy to ground overturned. The following functions need to be implemented:

1. Customize the small ball, the text inside the ball, float up and down, disappear animation;
2. Dynamically add balls according to the data, and randomly distribute them around the trees without overlapping. This is the most important, involving the design of a random position generation algorithm.

Ok, so once we’ve identified what we want to implement we can start coding step by step.

Custom sphere

This is easier to achieve, draw a circle, and then draw text in the park, animation unified use is the property of animation to achieve, the code is as follows, notes to write more detailed is not an explanation, lazy…

/** * @author: xiaohaibin. * @time: 2018/1/5 * @mail:[email protected] * @github:https://github.com/xiaohaibin * @describe: Public class extends View {private Paint extends View; private ObjectAnimator mAnimator; /** * private int textColor = color.parsecolor ()"#69c78e"); /** * private int waterColor = color.parsecolor ()"#c3f593"); /** * private int storkeColor = color.parsecolor ()"#69c78e"); /** * line width */ privatefloatStrokeWidth = 0.5 f; /** * text size */ privatefloattextSize = 36; /** * The ratio of the radius calculated according to the distance */ privatefloatproportion; /** * 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); / / stroke 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); // Ball text 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.0 f, 6.0 f to 6.0 f); mAnimator.setDuration(3500); mAnimator.setInterpolator(new LinearInterpolator()); mAnimator.setRepeatMode(ValueAnimator.RESTART); mAnimator.setRepeatCount(ValueAnimator.INFINITE); mAnimator.start(); }else if(! mAnimator.isStarted()) { mAnimator.start(); } } public voidstop() {
        if(mAnimator ! = null) { mAnimator.cancel(); mAnimator = null; } } publicfloat getProportion() {
        return proportion;
    }

    public void setProportion(floatproportion) { this.proportion = proportion; }}Copy the code

Dynamically and randomly add balls

Here I use the integration of FrameLayout by setting the ball data, dynamically add the ball, relatively simple, here the most important dynamic random add ball algorithm, solve this algorithm is easy to do. By looking closely at the effect of the Alipay ant forest, we can see that the balls are usually randomly distributed directly above the tree. So I want to take the root of the tree as the center, the height of the tree as the radius of a fan, and randomly place the ball above this fan.

Formula: coordinate = rotation Angle * radius * the proportion of radius calculated according to different distances

The formula for calculating the coordinates of any point (x1,y1) of a circle is as follows:

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

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

The specific implementation code is as follows:

/** * @author: xiaohaibin. * @time: 2018/1/5 * @mail:[email protected] * @github:https://github.com/xiaohaibin * @describe: */ public class WaterFlake extends FrameLayout {private OnWaterItemListener mOnWaterItemListener; /** */ privatefloattreeCenterX = 0; /** * Y */ privatefloattreeCenterY = 0; /** * private int radius = 80; Private double mStartAngle = 0; private double mStartAngle = 0; /** * Whether power is being collected */ 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; // Calculate the Angle according to the tem numberfloat angleDelay = -180 / childCount;
        for(int i = 0; i < childCount; i++) { WaterView child = (WaterView) getChildAt(i); mStartAngle %= 180; // Set the CircleView point coordinate information // coordinate = rotation Angle * radius * should be calculated according to the distance of the radius ratio // then any point of the circle is: (x1,y1) // x1 = x0 + r * cos(ao * 3.14/180) // y1 = y0 + r * sin(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; }} /** * set the number of balls from the data set ** @param modelList data set */ public voidsetModelList(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); }} /** * set the ball click event ** @param onWaterItemListener */ public voidsetOnWaterItemListener(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; }}); } publicfloat getTreeCenterX() {
        return treeCenterX;
    }

    public float getTreeCenterY() {
        returntreeCenterY; }}Copy the code

There are many ways to implement the random ball placement algorithm. This is just one of them. I hope you can correct my mistakes.

Finally, the source link is attached

If you like, please give a Star to support, thank you ~~~

Welcome to follow your personal wechat official number ~