preface

“All is well” ushered in the final, I believe that many people cried. In the finale, all the characters who had made people so popular before, such as Su Mingzhe (” You let me down “) and Su Mingcheng (” Mama’s boy “), including Su Daqiang (” I feel bad if I don’t work for a day “), were successfully cleansed. The family finally resolved their differences and lived peacefully. Does anyone else like The show “All Is Well”?

In the show, Su Mingzhe is also a programmer like us. He indulges his father and almost divorces his wife in the end. It seems that programmers can’t be single-minded. Change your mind to look at the web version of the dynamic background “colorful web” is how to achieve?

Take a look at the renderings:

The preliminary analysis

In the renderings, you can see a number of “dots” moving uniformly across the screen and connecting with “adjacent dots”. Each line has a random color, and the dots bounce back when they touch the edge of the screen. Another effect is that the finger moves and drags across the screen, drawing the point connected to the finger’s touch point closer to the touch point. What is “adjacent point”, and the distance from a point is less than a specific threshold is called “adjacent point”.

Speaking of motion, “motion” in physics means that the relative positions of objects in space change over time.

So do you remember the formula for displacement and velocity?

Displacement is equal to initial displacement plus velocity times time velocity is equal to initial velocity plus accelerationCopy the code

Time, displacement, velocity and acceleration constitute the motion system of modern science. We use view to simulate the motion of the object.

  • Time: Call the invalidate method in the onDraw method of the view to achieve infinite refresh to simulate the time flow. Each refresh interval is denoted as: 1U

  • Displacement: The pixel position of the object on the screen. Each pixel is 1px away

  • Speed: Default value, unit: px/U

  • Acceleration: default value, unit (px/U^2)

Simulated “cobweb point” object class:

public class SpiderPoint extends Point {

    // Acceleration in the x direction
    public int aX;

    // Acceleration in the y direction
    public int aY;

    // Ball color
    public int color;
    
    // Small sphere radius
    public int r;

    // Speed in x direction
    public float vX;

    // Speed in the y direction
    public float vY;
    
    / / points
    public float x;
    public float y;

    public SpiderPoint(int x, int y) {
        super(x, y); }}Copy the code

Cobweb points move uniformly in a straight line

Build test View, initial position (0,0), spider web points with speed 10 in x direction and 0 in Y direction:

public class MoveView extends View {

    / / brush
    private Paint mPointPaint;
    // Spider dot object (similar to a ball)
    private SpiderPoint mSpiderPoint;
    / / coordinate system
    private Point mCoordinate;

    // Spider dot defaults to small sphere radius
    private int pointRadius = 20;
    // Default color
    private int pointColor = Color.RED;
    // Default x speed
    private float pointVX = 10;
    // Default y speed
    private float pointVY = 0;
    // The default ball acceleration
    private int pointAX = 0;
    private int pointAY = 0;

    // Whether to start exercising
    private boolean startMove = false;

    public MoveView(Context context) {
        this(context, null);
    }

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

    public MoveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initData();
        initPaint();
    }

    private void initData(a) {
        mCoordinate = new Point(500.500);
        mSpiderPoint = new SpiderPoint();
        mSpiderPoint.color = pointColor;
        mSpiderPoint.vX = pointVX;
        mSpiderPoint.vY = pointVY;
        mSpiderPoint.aX = pointAX;
        mSpiderPoint.aY = pointAY;
        mSpiderPoint.r = pointRadius;
    }

    // Initialize the brush
    private void initPaint(a) {
        mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPointPaint.setColor(pointColor);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        canvas.translate(mCoordinate.x, mCoordinate.y);
        drawSpiderPoint(canvas, mSpiderPoint);
        canvas.restore();

        // Refresh the view and call the onDraw method again to simulate the time flow
        if(startMove) { updateBall(); invalidate(); }}/** * draw spider dot **@param canvas
     * @param spiderPoint
     */
    private void drawSpiderPoint(Canvas canvas, SpiderPoint spiderPoint) {
        mPointPaint.setColor(spiderPoint.color);
        canvas.drawCircle(spiderPoint.x, spiderPoint.y, spiderPoint.r, mPointPaint);
    }

    /** * Update the ball */
    private void updateBall(a) {
        //TODO -- The motion data is transformed by this function
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // Start the time stream
                startMove = true;
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                // Pause the time stream
                startMove = false;
                invalidate();
                break;
        }
        return true; }}Copy the code

1. Horizontal movement:

Displacement = initial displacement + velocity times time

    /** * Update the ball */
    private void updateBall(a) {
        //TODO -- The motion data is transformed by this function
        mSpiderPoint.x += mSpiderPoint.vX;
    }
Copy the code

2. Rebound effect

Rebound, reverse the speed, the X-axis direction is greater than 400 rebound:

    /** * Update the ball */
    private void updateBall(a) {
        //TODO -- The motion data is transformed by this function
        mSpiderPoint.x += mSpiderPoint.vX;
        if (mSpiderPoint.x > 400) {
            // Change the color
            mSpiderPoint.color = randomRGB();
            mSpiderPoint.vX = -mSpiderPoint.vX;
        }
        if (mSpiderPoint.x < -400) {
            mSpiderPoint.vX = -mSpiderPoint.vX;
            // Change the colormSpiderPoint.color = randomRGB(); }}Copy the code

The randomRGB method looks like this:

    / * * *@returnGets a random color value */
    private int randomRGB(a) {
        Random random = new Random();
        return Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
    }
Copy the code

3. Box bounce

The ball’s translation in the y direction is the same as the translation in the x direction, and I’m not going to talk about it here, but let’s see if both x and y have initial velocity, that is, the velocity is oblique.

    // Default y speed
    private float pointVY = 6;
Copy the code

Add a change to the y direction in the updateBall method:

    /** * Update the ball */
    private void updateBall(a) {
        //TODO -- The motion data is transformed by this function
        mSpiderPoint.x += mSpiderPoint.vX;
        mSpiderPoint.y += mSpiderPoint.vY;
        if (mSpiderPoint.x > 400) {
            // Change the color
            mSpiderPoint.color = randomRGB();
            mSpiderPoint.vX = -mSpiderPoint.vX;
        }
        if (mSpiderPoint.x < -400) {
            mSpiderPoint.vX = -mSpiderPoint.vX;
            // Change the color
            mSpiderPoint.color = randomRGB();
        }

        if (mSpiderPoint.y > 400) {
            // Change the color
            mSpiderPoint.color = randomRGB();
            mSpiderPoint.vY = -mSpiderPoint.vY;
        }
        if (mSpiderPoint.y < -400) {
            mSpiderPoint.vY = -mSpiderPoint.vY;
            // Change the colormSpiderPoint.color = randomRGB(); }}Copy the code

The effect is shown below:

Android native drawing lets you understand the motion of a View

Design code

By observing the dynamic effect of web “cobweb”, it can be subdivided into the following points:

  • Draw a certain number of balls (web points)

  • The ball moves diagonally (with x and y velocity) and bounces back

  • If the distance between ball A and other balls is less than A certain value, the two balls will be connected; otherwise, they will not be connected

  • If ball A is connected to ball B first, ball B is no longer connected to ball A to improve performance and prevent overdrawing

  • Draw a ball at the touch point of the finger, in accordance with the matching rule, connect the other balls, if the finger moves, all the connected balls close to the touch point

Now, let’s look at the code in detail.

Write the code

The name

Naming is a science, and a good name that sticks in your mind is SpiderWebView.

Create SpiderWebView

First, the member variable:

    // Control width and height
    private int mWidth;
    private int mHeight;
    / / brush
    private Paint mPointPaint;
    private Paint mLinePaint;
    private Paint mTouchPaint;
    // Touch point coordinates
    private float mTouchX = -1;
    private float mTouchY = -1;
    / / the data source
    private List<SpiderPoint> mSpiderPointList;
    // Set related parameters
    private SpiderConfig mConfig;
    / / random number
    private Random mRandom;
    // Gesture helper classes handle scrolling and dragging
    private GestureDetector mGestureDetector;
Copy the code

Then there is the constructor:

    // View's default constructor argument is not explained
    public SpiderWebView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // setLayerType(LAYER_TYPE_HARDWARE, null);
        mSpiderPointList = new ArrayList<>();
        mConfig = new SpiderConfig();
        mRandom = new Random();
        mGestureDetector = new GestureDetector(context, mSimpleOnGestureListener);
        // Brush initialization
        initPaint();
    }
Copy the code

Then follow the “design code” effect one by one implementation.

Draw a certain number of balls

The specified number is 50, and each ball has a random position, color, and different acceleration. The relevant codes are as follows:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }
Copy the code

Get the width and height of the control-to-control first. Then initialize the collection of balls:

    /** * initializes the little bit */
    private void initPoint(a) {
        for (int i = 0; i < mConfig.pointNum; i++) {
            int width = (int) (mRandom.nextFloat() * mWidth);
            int height = (int) (mRandom.nextFloat() * mHeight);

            SpiderPoint point = new SpiderPoint(width, height);
            int aX = 0;
            int aY = 0;
            // Get the acceleration
            while (aX == 0) {
                aX = (int) ((mRandom.nextFloat() - 0.5 F) * mConfig.pointAcceleration);
            }
            while (aY == 0) {
                aY = (int) ((mRandom.nextFloat() - 0.5 F) * mConfig.pointAcceleration);
            }
            point.aX = aX;
            point.aY = aY;
            // The color is randompoint.color = randomRGB(); mSpiderPointList.add(point); }}Copy the code

MConfig represents the configuration parameter, which has the following member variables:

public class SpiderConfig {
    // Smaller radius 1
    public int pointRadius = DEFAULT_POINT_RADIUS;
    // The thickness (width) of the line between dots
    public int lineWidth = DEFAULT_LINE_WIDTH;
    // Transparency of the line between dots 150
    public int lineAlpha = DEFAULT_LINE_ALPHA;
    // Smaller number 50
    public int pointNum = DEFAULT_POINT_NUMBER;
    // Small point acceleration 7
    public int pointAcceleration = DEFAULT_POINT_ACCELERATION;
    // The longest straight line distance between dots is 280
    public int maxDistance = DEFAULT_MAX_DISTANCE;
    // Touch point radius 1
    public int touchPointRadius = DEFAULT_TOUCH_POINT_RADIUS;
    // The gravity is 50
    public int gravitation_strength = DEFAULT_GRAVITATION_STRENGTH;
}
Copy the code

Get the collection of balls, and finally draw the balls:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Draw the ball
        mPointPaint.setColor(spiderPoint.color);
        canvas.drawCircle(spiderPoint.x, spiderPoint.y, mConfig.pointRadius, mPointPaint);
        }
Copy the code

The renderings are as follows:

The ball moves diagonally and bounces off the boundary

According to the formula of displacement and velocity, displacement = initial displacement + velocity * time, velocity = initial velocity + acceleration. Since the initial velocity is 0 and the time is 1U, displacement = initial displacement + acceleration can be obtained:

    spiderPoint.x += spiderPoint.aX;
    spiderPoint.y += spiderPoint.aY;
Copy the code

The principle of determining transgression has been mentioned above:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (SpiderPoint spiderPoint : mSpiderPointList) {

            spiderPoint.x += spiderPoint.aX;
            spiderPoint.y += spiderPoint.aY;

            // Bounce out of bounds
            if (spiderPoint.x <= mConfig.pointRadius) {
                spiderPoint.x = mConfig.pointRadius;
                spiderPoint.aX = -spiderPoint.aX;
            } else if (spiderPoint.x >= (mWidth - mConfig.pointRadius)) {
                spiderPoint.x = (mWidth - mConfig.pointRadius);
                spiderPoint.aX = -spiderPoint.aX;
            }

            if (spiderPoint.y <= mConfig.pointRadius) {
                spiderPoint.y = mConfig.pointRadius;
                spiderPoint.aY = -spiderPoint.aY;
            } else if(spiderPoint.y >= (mHeight - mConfig.pointRadius)) { spiderPoint.y = (mHeight - mConfig.pointRadius); spiderPoint.aY = -spiderPoint.aY; }}}Copy the code

The renderings are as follows:

Two ball attachment

Loop through all the balls, if the distance between ball A and other balls is less than A certain value, then the two balls are connected, otherwise, no connection. Double-layer traversal will cause a problem. If the number of balls is too large, the efficiency of double-layer traversal will be extremely low, which will cause interface lag. At present, no better algorithm has been found to solve this problem.

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (SpiderPoint spiderPoint : mSpiderPointList) {
            // Draw a line
            for (int i = 0; i < mSpiderPointList.size(); i++) {
                SpiderPoint point = mSpiderPointList.get(i);
                // Determine the distance between the current point and other points
                if(spiderPoint ! = point) {int distance = disPos2d(point.x, point.y, spiderPoint.x, spiderPoint.y);
                    if (distance < mConfig.maxDistance) {
                        // Draw lines between dots
                        int alpha = (int) ((1.0 F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);

                        mLinePaint.setColor(point.color);
                        mLinePaint.setAlpha(alpha);
                        canvas.drawLine(spiderPoint.x, spiderPoint.y, point.x, point.y, mLinePaint);
                    }
                }
            }
        }
        invalidate();
    }
Copy the code

The disPos2d method is used to calculate the distance between two points:

    /** * distance function between two points */
    public static int disPos2d(float x1, float y1, float x2, float y2) {
        return (int) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }
Copy the code

If the distance between the two balls is within the range of maxDistance, the closer the distance is, the smaller the transparency is:

	int alpha = (int) ((1.0 F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);
Copy the code

Let’s take a look at the effect of the two balls:

Prevent overdrawing

Due to double-layer traversal, if ball A is connected to ball B first, ball B is no longer connected to ball A in order to improve performance and prevent overdrawing. The first idea is to record the ball A and other small ball attachment state, while the rest of the ball and the ball A attachment, according to the state to determine whether the attachment, if the ball A with many small ball attachment first, is bound to be inside the ball A object to maintain A collection, which is used to store small ball has A and ball attachment, so efficiency is not high, It turns a simple problem into a complex one. The trick is to record the index value of the first loop and start the second loop with the current index value, thus avoiding multiple connections between the two balls. The relevant codes are as follows:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int index = 0;
        for (SpiderPoint spiderPoint : mSpiderPointList) {
            // Draw a line
            for (int i = index; i < mSpiderPointList.size(); i++) {
                SpiderPoint point = mSpiderPointList.get(i);
                // Determine the distance between the current point and other points
                if(spiderPoint ! = point) {int distance = disPos2d(point.x, point.y, spiderPoint.x, spiderPoint.y);
                    if (distance < mConfig.maxDistance) {
                        // Draw lines between dots
                        int alpha = (int) ((1.0 F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);

                        mLinePaint.setColor(point.color);
                        mLinePaint.setAlpha(alpha);
                        canvas.drawLine(spiderPoint.x, spiderPoint.y, point.x, point.y, mLinePaint);
                    }
                }
            }
          index++;
        }
        invalidate();
    }
Copy the code

Signal processing

Remember? In the first stop of the article small red book picture cutting control, depth analysis of dachang cool control has explained the processing process of gestures. The onScroll method of gesture class needs to be rewritten because the touch point (mouse pressed point) moves with the mouse in web version and the “touch point” (finger pressed point) moves with the finger in mobile phone screen.

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // Single finger operation
            if (e1.getPointerCount() == e2.getPointerCount() && e1.getPointerCount() == 1) {
                mTouchX = e2.getX();
                mTouchY = e2.getY();
                return true;
            }
            return super.onScroll(e1, e2, distanceX, distanceY);
        }
Copy the code

The onFling method is the same as the onScroll method to obtain the touch point position in real time. Get the location, draw the touch point:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Draw touch points
        if(mTouchY ! = -1&& mTouchX ! = -1) { canvas.drawPoint(mTouchX, mTouchY, mTouchPaint); }}Copy the code

If the distance between “touch point” and other balls is less than a certain value, then the two balls are connected, otherwise, they are not connected:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
            // Draw lines between touch points and other points
            if(mTouchX ! = -1&& mTouchY ! = -1) {
                int offsetX = (int) (mTouchX - spiderPoint.x);
                int offsetY = (int) (mTouchY - spiderPoint.y);
                int distance = (int) Math.sqrt(offsetX * offsetX + offsetY * offsetY);
                if (distance < mConfig.maxDistance) {
                    int alpha = (int) ((1.0 F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha); mLinePaint.setColor(spiderPoint.color); mLinePaint.setAlpha(alpha); canvas.drawLine(spiderPoint.x, spiderPoint.y, mTouchX, mTouchY, mLinePaint); }}}Copy the code

At the same time, it also has the effect that all the balls connected to the “touch point” are close to the “touch point”. The scheme of “relative reduction of displacement” can be adopted to achieve the effect of close, and the relevant codes are as follows:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
            // Draw lines between touch points and other points
            if(mTouchX ! = -1&& mTouchY ! = -1) {...// omit the relevant code
                if (distance < mConfig.maxDistance) {
                    if (distance >= (mConfig.maxDistance - mConfig.gravitation_strength)) {
                        // the X-axis displacement decreases
                        if (spiderPoint.x > mTouchX) {
                            spiderPoint.x -= 0.03 F * -offsetX;
                        } else {
                            spiderPoint.x += 0.03 F * offsetX;
                        }
                        // The Y-axis displacement decreases
                        if (spiderPoint.y > mTouchY) {
                            spiderPoint.y -= 0.03 F * -offsetY;
                        } else {
                            spiderPoint.y += 0.03 F* offsetY; }}...// omit the relevant code
Copy the code

Check out the renderings:

conclusion

Stay up late to write an article, youdao unknown, please forgive me. At the same time, I hope you can have a good friends.

The source code is as follows:

Github.com/HpWens/MeiW…

Github.com/HpWens/Spid…

I hope people with lofty ideals can work with me to maintain the “control life” public number.