Android mimics the effect of ant forest water droplets

Keywords: Android Mobile

Click "Android Technology Grocery Store" above and select "Top Public Number"

Dry article, first time service!

 

Author: laer_L
Link: https://www.jianshu.com/p/cc526bb34414, this article is authorized by the author.

I haven't written anything for a long time, and you may come down again. You may think it's very simple to see this Dongdong effect. But if you read it, I believe you will find that there are still articles in it. Maybe it is not so simple. The essence of this article is performance.

The old rule is GIF first.

Maybe you don't want to look at this picture any more. I think this animation is very simple. It's not to create a loop to create a view, then give each view an animation, each view's starting direction is random, and then give an accelerator to work out. If you think the same way, you should finish reading this article.
Analysis:

  • First create water droplet animation, zoom with transparency changes

  • Scaling accompanies movement when it disappears

  • Droplet display is always floating up and down

  • The direction of each droplet floating up and down is uncertain.

  • Every drop of water moves faster and slower (which may not be visible to you, so I'll take it again).
    Increase the range of jitter with another GIF

First of all, we certainly can't use each view corresponding to an animation to deal with, because if I was 100 low droplets, it would not be 100 animations, which can't be stuck, so it must be an animation to complete, I first thought of using Value Animator to do, but a Value Animator how to control the direction of each view, you may say that each view in the. Initialization of a reverse, can really solve the problem of different directions of motion, but how to solve the speed of view movement is different, and sometimes fast and slow, and the movement of each view is fundamentally different, finally I chose handler to deal with, but here you think it is over, continue to look down.

Topic

1. First create view

1,to view A random direction and saved to view Of tag in
            //Randomly set the direction of view animation
            view.setTag(R.string.isUp, mRandom.nextBoolean());
 2,Random settings view Location (I'm not totally random here, but give some values, and then randomly select these values). Here, a new set is used to save the selected number. Exclude these values the next time you choose, because it's better that the droplets don't completely coincide.
/**But in fact, this is not my final method. Look down first, there are colored eggs.**/
 /**
     * Get random values on the x or y axis
     *
     * @return
     */
    private double getX_YRandom(List<Float> choseRandoms,List<Float> saveRandoms) {
        float random = 0;
        while (random == 0 || saveRandoms.contains(random)) {
            random = choseRandoms.get(mRandom.nextInt(choseRandoms.size()));
        }

        saveRandoms.add(random);
        return random;
    }

There is also an animated view, which is not shown, absolutely can be done.

2. Next, set an initial random acceleration for view (which is also randomly selected from the existing values, because the velocities cannot vary too much)

/**Control the speed of water droplet animation*/
    private List<Float> mSpds = Arrays.asList(2.0f, 1.7f, 1.5f, 1.3f);
 /**
     * Set the acceleration of all child view s
     */
    private void setViewsSpd() {
        for (int i = 0; i < mViews.size(); i++) {
            View view = mViews.get(i);
            setSpd(view);
        }
    }
 /**
     * Set the acceleration of all child view s
     */
    private void setViewsSpd() {
        for (int i = 0; i < mViews.size(); i++) {
            View view = mViews.get(i);
            setSpd(view);
        }
    }

3. The next step is to use handler to set the offset of view, which is also critical.

/**
     * Set offset
     */
    private void setOffSet() {
        for (int i = 0; i < mViews.size(); i++) {
            View view = mViews.get(i);
            float spd = (float) view.getTag(R.string.spd);
            float original = (float) view.getTag(R.string.original_y);
            float step = CHANGE_RANGE / BASE_OFFSET_MUL * spd;
            //Get the orientation of the initial settings
            boolean isUp = (boolean) view.getTag(R.string.isUp);
            float translationY;
            if (isUp) {
                translationY = view.getY() - step;
            } else {
                translationY = view.getY() + step;
            }
            //The range of movement of the control view should not be greater than the range we specified. If this value is reached, the direction of movement of the view should be changed.
            if (translationY - original > CHANGE_RANGE) {
                translationY = original + CHANGE_RANGE;
                view.setTag(R.string.isUp, true);
            } else if (translationY - original < -CHANGE_RANGE) {
                translationY = original - CHANGE_RANGE;
//Every time a view moves to the bottom, it changes the acceleration of the view when it moves up again. Doesn't that make the view sometimes faster and sometimes slower?
                setSpd(view);
                view.setTag(R.string.isUp, false);
            }
            view.setY(translationY);
        }
    }

4. Next, the vanishing animation after the droplet clicks, let alone, will do.

The effect is over here, but my work is not over. Open profiler and look at OMG. There is a huge increase in memory in the place where the view is initialized. A little more (10) will still have card owners. It seems that it is still optimized.

It's obvious that the private double getX_YRandom(List choseRandoms, List saveRandoms) method has gone too many times because I create views in a loop and randomly create locations for views in this loop, but for incomplete coincidence, I know again that the loop ends with a different value, that is to say, there is a double loop.

  • After optimizing and randomly selecting a value, the value will be removed from the set, so that it won't get the same value. It's really over here. After optimizing, there is no single carton in the 200 measured. Readers can optimize the location logic algorithm of water droplets according to their own needs, because our product clearly says up to 6 droplets, so my current calculation logic of water droplet location is enough. Here's a GIF.

All code

/**
 * Creation time: 2018/1/9
 * Creator: Laitian Bing
 * Description: Ant forest simulation
 */

public class WaterView extends FrameLayout {
    private static final int WHAT_ADD_PROGRESS = 1;
    /**The larger the value, the smaller the offset.*/
    private static final int BASE_OFFSET_MUL = 12;
    /**view Variable y jitter range*/
    private static final int CHANGE_RANGE = 50;
    /**Control the speed of jitter animation execution*/
    public static final int PROGRESS_DELAY_MILLIS = 60;
    /**Control the animation execution time of removing view*/
    public static final int REMOVE_DELAY_MILLIS = 2000;
    /**The animation shows the execution time of view when water droplets are added*/
    public static final int ANIMATION_SHOW_VIEW_DURATION = 500;
    /**Control the speed of water droplet animation*/
    private List<Float> mSpds = Arrays.asList(2.0f, 1.7f, 1.5f, 1.3f);
    /**x Maximum optional random number*/
    private static final List<Float> X_MAX_CHOSE_RANDOMS = Arrays.asList(
            0.01f,0.05f,0.1f,0.6f,0.11f, 0.16f, 0.21f, 0.26f, 0.31f, 0.7f, 0.75f, 0.8f, 0.85f, 0.87f);
    /**y Maximum optional random number*/
    private static final List<Float> Y_MAX_CHOSE_RANDOMS = Arrays.asList(
            0.01f,0.06f,0.11f, 0.17f, 0.23f, 0.29f, 0.35f, 0.41f, 0.47f, 0.53f, 0.59f, 0.65f, 0.71f, 0.77f, 0.83f);
    /**x Current optional random array of coordinates*/
    private List<Float> mXCurrentCanShoseRandoms = new ArrayList<>();
    /**y Current optional random array of coordinates*/
    private List<Float> mYCurrentCanShoseRandoms = new ArrayList<>();

    /**Random values of selected x*/
    private List<Float> mXRandoms = new ArrayList<>();
    /**Random values of y have been selected*/
    private List<Float> mYRandoms = new ArrayList<>();


    private Random mRandom = new Random();
    private List<View> mViews = new ArrayList<>();
    private int mChildViewRes = R.layout.water_item;//Sub view resource file

    private LayoutInflater mInflater;
    private int mTotalConsumeWater;//Total clicked droplets
    private boolean isOpenAnimtion;//Whether to turn on animation or not
    private boolean isCancelAnimtion;//Whether to Destroy Animation
    private int maxX, maxY;//Maximum values of x and y coordinates of sub-view
    private float mMaxSpace;//Diagonal distance of parent control
    private Point mDestroyPoint ;//view Destruction Point


    public WaterView(@NonNull Context context) {
        this(context, null);
    }

    public WaterView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WaterView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mInflater = LayoutInflater.from(getContext());
    }

    @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (isCancelAnimtion) {
                return;
            }
            setOffSet();
            mHandler.sendEmptyMessageDelayed(WHAT_ADD_PROGRESS, PROGRESS_DELAY_MILLIS);
        }
    };

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mMaxSpace = (float) Math.sqrt(w * w + h * h);
        mDestroyPoint=new Point((int) getX(), h);
        maxX = w;
        maxY = h;
    }

    /**
     * Reset child view
     */
    private void reset() {
        isCancelAnimtion = true;
        isOpenAnimtion = false;
        for (int i = 0; i < mViews.size(); i++) {
            removeView(mViews.get(i));
        }
        mViews.clear();
        mXRandoms.clear();
        mYRandoms.clear();
        mYCurrentCanShoseRandoms.clear();
        mXCurrentCanShoseRandoms.clear();
        mHandler.removeCallbacksAndMessages(null);
    }

    /**
     * Setting water drops
     * @param waters
     */
    public void setWaters(final List<Water> waters) {
        if (waters == null || waters.isEmpty()) {
            return;
        }
        post(new Runnable() {
            @Override
            public void run() {
                setDatas(waters);
            }
        });
    }

    /**
     * Setting data
     * @param waters
     */
    private void setDatas(List<Water> waters) {
        reset();
        isCancelAnimtion = false;
        setCurrentCanChoseRandoms();
        addWaterView(waters);
        setViewsSpd();
        startAnimation();
    }

    private void setCurrentCanChoseRandoms() {
        mXCurrentCanShoseRandoms.addAll(X_MAX_CHOSE_RANDOMS);
        mYCurrentCanShoseRandoms.addAll(Y_MAX_CHOSE_RANDOMS);
    }

    /**
     * Add droplet view
     */
    private void addWaterView(List<Water> waters) {
        for (int i = 0; i < waters.size(); i++) {
            final Water water = waters.get(i);
            View view = mInflater.inflate(mChildViewRes, this, false);
            TextView tvWater = view.findViewById(R.id.tv_water);
            view.setTag(water);
            tvWater.setText(String.valueOf(water.getNumber()) + "g");
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    handViewClick(view);
                }
            });
            //Randomly set the direction of view animation
            view.setTag(R.string.isUp, mRandom.nextBoolean());
            setChildViewLocation(view);
            mViews.add(view);
            addShowViewAnimation(view);
        }
    }

    /**
     * Add Display Animation
     * @param view
     */
    private void addShowViewAnimation(View view) {
        addView(view);
        view.setAlpha(0);
        view.setScaleX(0);
        view.setScaleY(0);
        view.animate().alpha(1).scaleX(1).scaleY(1).setDuration(ANIMATION_SHOW_VIEW_DURATION).start();
    }

    /**
     * Processing view clicks
     *
     * @param view
     */
    private void handViewClick(View view) {
        //Remove the view from the current collection
        mViews.remove(view);
        Object tag = view.getTag();
        if (tag instanceof Water) {
            Water waterTag = (Water) tag;
            mTotalConsumeWater += waterTag.getNumber();
            Toast.makeText(getContext(), "The current click is:" + waterTag.getName() + "The value of the droplet is:"
                    + waterTag.getNumber() + "The total number of droplets is" + mTotalConsumeWater, Toast.LENGTH_SHORT).show();
        }
        view.setTag(R.string.original_y, view.getY());
        animRemoveView(view);
    }

    /**
     * Set the position of view in the parent control
     *
     * @param view
     */
    private void setChildViewLocation(View view) {
        view.setX((float) (maxX * getX_YRandom(mXCurrentCanShoseRandoms,mXRandoms)));
        view.setY((float) (maxY * getX_YRandom(mYCurrentCanShoseRandoms,mYRandoms)));
        view.setTag(R.string.original_y, view.getY());
    }

    /**
     * Get random values on the x or y axis
     *
     * @return
     */
    private double getX_YRandom(List<Float> choseRandoms,List<Float> saveRandoms) {

        if (choseRandoms.size() <= 0) {
            //There are more alternatives to prevent water droplets. Here we re-assign the alternatives.
            setCurrentCanChoseRandoms();
        }
        //Take a random number and remove a random number.
        float random = choseRandoms.get(mRandom.nextInt(choseRandoms.size()));
        choseRandoms.remove(random);
        saveRandoms.add(random);
        return random;
    }

    /**
     * Set the acceleration of all child view s
     */
    private void setViewsSpd() {
        for (int i = 0; i < mViews.size(); i++) {
            View view = mViews.get(i);
            setSpd(view);
        }
    }

    /**
     * Setting View's spd
     *
     * @param view
     */
    private void setSpd(View view) {
        float spd = mSpds.get(mRandom.nextInt(mSpds.size()));
        view.setTag(R.string.spd, spd);
    }

    /**
     * Set offset
     */
    private void setOffSet() {
        for (int i = 0; i < mViews.size(); i++) {
            View view = mViews.get(i);
            float spd = (float) view.getTag(R.string.spd);
            float original = (float) view.getTag(R.string.original_y);
            float step = CHANGE_RANGE / BASE_OFFSET_MUL * spd;
            boolean isUp = (boolean) view.getTag(R.string.isUp);
            float translationY;
            if (isUp) {
                translationY = view.getY() - step;
            } else {
                translationY = view.getY() + step;
            }

            if (translationY - original > CHANGE_RANGE) {
                translationY = original + CHANGE_RANGE;
                view.setTag(R.string.isUp, true);
            } else if (translationY - original < -CHANGE_RANGE) {
                translationY = original - CHANGE_RANGE;
                setSpd(view);
                view.setTag(R.string.isUp, false);
            }
            view.setY(translationY);
        }
    }

    /**
     * Get the distance between two points
     *
     * @param p1
     * @param p2
     * @return
     */
    public float getDistance(Point p1, Point p2) {
        float _x = Math.abs(p2.x - p1.x);
        float _y = Math.abs(p2.y - p1.y);
        return (float) Math.sqrt(_x * _x + _y * _y);
    }

    /**
     * Animation Remove view
     * @param view
     */
    private void animRemoveView(final View view) {
        final float x = view.getX();
        final float y = view.getY();
        float space = getDistance(new Point((int) x, (int) y), mDestroyPoint);

        ValueAnimator animator = ValueAnimator.ofFloat(x, 0);
        animator.setDuration((long) (REMOVE_DELAY_MILLIS / mMaxSpace * space));
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                if (isCancelAnimtion) {
                    return;
                }
                float value = (float) valueAnimator.getAnimatedValue();
                float alpha = value / x;
                float translationY = y + (x - value) * (maxY - y) / x;
                setViewProperty(view, alpha, translationY, value);
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                removeView(view);
            }
        });
        animator.start();
    }

    /**
     * Setting the properties of view
     * @param view
     * @param alpha
     * @param translationY
     * @param translationX
     */
    private void setViewProperty( View view, float alpha,float translationY,float translationX) {
        view.setTranslationY(translationY);
        view.setTranslationX(translationX);
        view.setAlpha(alpha);
        view.setScaleY(alpha);
        view.setScaleX(alpha);
    }

    /**
     * Turn on Droplet Shake Animation
     */
    private void startAnimation() {
        if (isOpenAnimtion) {
            return;
        }

        mHandler.sendEmptyMessage(WHAT_ADD_PROGRESS);
        isOpenAnimtion = true;
    }

    /**
     * Destruction
     */
    public void onDestroy() {
        isCancelAnimtion = true;
        mHandler.removeCallbacksAndMessages(this);
    }
}

Praise one if you like.

Dry goods in the past

1

[Recommendation Number] How to use Public Number for self-promotion?

2

Efficiency | A few Android Studio tips

3

Prediction of Mobile Application UI Design Trend in 2018

Posted by dark_mirage on Sun, 12 May 2019 05:03:41 -0700