Chestnut alpha.

This is an imitation of the pin bouncing effect, see below:

The reason for this is to consider that drawing a pin completely would cause the Ui to be unusable, and the bottom “rod” would be varied and changeable. So the idea here is to split the pin into the top circle view and the bottom “rod” bitmap, and just change the size and color of the custom circle to fit the Ui as best as possible.

The loading animation of the pin and the diffusion effect of the bottom ripple are drawn regularly by the internal handler, and the radius and color can be changed every time. The main code is as follows:

Here is the loading animation:

    case MSG_TOP_DRAW:
        if (mTopCircleAnimaRadius < mTopCircleMaxRadius - 2 * topIntervalDistance) {
            mTopCircleAnimaRadius += topIntervalDistance;
        } else {
            mTopCircleAnimaRadius = mTopSmallCircleRadius + topIntervalDistance;
        } 

        drawTimingThread.removeMessages(MSG_TOP_DRAW);
        drawTimingThread.sendEmptyMessageDelayed(MSG_TOP_DRAW, animaTopIntervalTime);
        invalidate();
        break;
Copy the code

Here is the bottom ripple diffusion animation:

    case MSG_RIPPLE_DRAW:
       if (mBotCircleAnimaRadius < mBotCircleMaxRadius) {
            mBotCircleAnimaRadius += topIntervalDistance * 8;
            drawTimingThread.removeMessages(MSG_RIPPLE_DRAW);
            drawTimingThread.sendEmptyMessageDelayed(MSG_RIPPLE_DRAW, animaBotIntervalTime);
            / / transparency
            mBotCirclePaint.setAlpha(getAlphaOfRipple());
            invalidate();
        } else {
            mBotCircleAnimaRadius = 0;
            drawTimingThread.removeMessages(MSG_RIPPLE_DRAW);
        }
        break;
Copy the code

View’s jump animation is the use of AnimatorSet combination animation. The dot text effect of the loading point is simply drawn, not expanded.

.// translationY goes up first and then down
    AnimatorSet mSet1 = new AnimatorSet();
    mSet1.play(mTAnimator1).before(mTAnimator2);
    mSet1.start();
Copy the code

Chestnut beta.

This animation effect is achieved through View continuous drawing, using the arc, bitmap and text drawing API. The scale line is drawn by constantly rotating the canvas.

The difficulty is to confirm the coordinate position of the peripheral text in the process of circling, that is, how to calculate the X and Y coordinates of the end ray of the sector and the intersection point of the arc through the coordinates of the center of the circle, the radius and the Angle of the sector. Fortunately, the solution and the mathematical model behind can be found on the Internet, and the code is as follows:

    private void paintOutWord(Canvas canvas, String state) {
        PointF progressPoint = CommentUtil.calcArcEndPointXY
                (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace + wordWith
                        , radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace + wordHeigh
                        , radius + specialScaleLineLength + scaleToRingSpace
                        , progress * (360 / 100f), -90);
        int left = (int) progressPoint.x;
        int top = (int) progressPoint.y;
        wordPaint.getTextBounds(state, 0, state.length(), rect);
        if (left < radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace + wordWith) {
            left -= rect.width();
        }
        if (top > radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace + wordHeigh) {
            top += rect.height();
        }
        canvas.drawText(state, left, top, wordPaint);
    }
Copy the code

The function of this method is to obtain the x and Y coordinates of the intersection point between the end ray of the sector and the arc. If you are interested, you can study:

    / * * *@paramCenterX * cirX circle@paramCirY round centerY *@paramRadius The radius of a circle *@paramCirAngle Indicates the current arc Angle *@paramOrginAngle Starting arc Angle * *@returnXy coordinates of the intersection between the end ray of the sector and the arc */
    public static PointF calcArcEndPointXY(float cirX, float cirY, float radius, 
            float cirAngle, float orginAngle) {
        cirAngle = (orginAngle + cirAngle) % 360;
        return calcArcEndPointXY(cirX, cirY, radius, cirAngle);
    }

    /* * @param cirAngle Current arc Angle */
    public static PointF calcArcEndPointXY(float cirX, float cirY, 
            float radius, float cirAngle) {
        float posX = 0.0 f;
        float posY = 0.0 f;
        // Convert the Angle to radians
        float arcAngle = (float) (Math.PI * cirAngle / 180.0);
        if (cirAngle < 90) {
            posX = cirX + (float) (Math.cos(arcAngle)) * radius;
            posY = cirY + (float) (Math.sin(arcAngle)) * radius;
        } else if (cirAngle == 90) {
            posX = cirX;
            posY = cirY + radius;
        } else if (cirAngle > 90 && cirAngle < 180) {
            arcAngle = (float) (Math.PI * (180 - cirAngle) / 180.0);
            posX = cirX - (float) (Math.cos(arcAngle)) * radius;
            posY = cirY + (float) (Math.sin(arcAngle)) * radius;
        } else if (cirAngle == 180) {
            posX = cirX - radius;
            posY = cirY;
        } else if (cirAngle > 180 && cirAngle < 270) {
            arcAngle = (float) (Math.PI * (cirAngle - 180) / 180.0);
            posX = cirX - (float) (Math.cos(arcAngle)) * radius;
            posY = cirY - (float) (Math.sin(arcAngle)) * radius;
        } else if (cirAngle == 270) {
            posX = cirX;
            posY = cirY - radius;
        } else {
            arcAngle = (float) (Math.PI * (360 - cirAngle) / 180.0);
            posX = cirX + (float) (Math.cos(arcAngle)) * radius;
            posY = cirY - (float) (Math.sin(arcAngle)) * radius;
        }
        return new PointF(posX, posY);
    }
Copy the code

To achieve the gradient effect of color, it is to obtain the hexadecimal color value of equal proportion in the corresponding color segment of each scale, the code is as follows:

    /** * Gets the current gradient color value * from the scale@paramP Current scale *@paramSpecialScaleCorlors Color value for each range *@returnCurrently required color value */
    public static int evaluateColor(int p, int[] specialScaleCorlors) {
        // Define the color range
        int startInt = 0xFFbebebe;
        int endInt = 0xFFbebebe;
        float fraction = 0.5 f;
        
        if(p ! =0&& p ! =100) {
            startInt = specialScaleCorlors[p / 20];
            endInt = specialScaleCorlors[p / 20 + 1];
            fraction = (p - (p / 20) * 20) / 20f;
        }
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
                | (int) ((startR + (int) (fraction * (endR - startR))) << 16)
                | (int) ((startG + (int) (fraction * (endG - startG))) << 8)
                | (int) ((startB + (int) (fraction * (endB - startB))));
    }
Copy the code

The rest of the details and methods are left out, just the more general Paint method.

Chestnut gamma

This effect is similar to the above, except that this control can be dragged to select the scale, see below:

When drawing the Bitmap of “drag button”, the difficulty is to determine the coordinates of the Bitmap, that is, according to the coordinates of the center of the circle, the radius, the Angle of the sector to calculate the x and Y coordinates of the end ray of the sector and the intersection of the arc, is not said above, so that we can calculate the coordinates of the upper left corner of the Bitmap.

The dragging effect is within the allowed area. When the finger is pressed, slid and bounced, the corresponding progress P is constantly drawn, giving people the illusion that the ring is dragged animation. In fact, this is just the result of continuous redrawing. Here we need to listen for gestures and get the current coordinates using the onTouchEvent method. The difficulty lies in that this is an arc trajectory. How can we obtain the Angle through the current coordinate and then obtain the corresponding progress according to the Angle? A code example is as follows:

    @Override
    public synchronized boolean onTouchEvent(MotionEvent event) {
    
        int action = event.getAction();
        int x = (int) event.getX();
        int y = (int) event.getY();
        
        switch (action) {
            case MotionEvent.ACTION_DOWN:
            	// isOnRing
                if (isOnRing(x, y) && y <= radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace) {
                    updateProgress(x, y);
                    return true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (y <= radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace) {
                    updateProgress(x, y);
                }
                return true;
            case MotionEvent.ACTION_UP:
                invalidate();
                break;
        }
        
        return super.onTouchEvent(event);
    }
Copy the code

This is the method to calculate the Angle according to the current point position and then convert it into the current progress:

    private void updateProgress(int eventX, int eventY) {
    
        double angle = Math.atan2(eventY - (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace)
                , eventX - (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace)) / Math.PI;
        angle = ((2 + angle) % 2 + (-beginLocation / 180f)) % 2;
        
        if ((int) Math.round(angle * 100) > =0) {
            progress = (int) Math.round(angle * 100);
            realShowProgress = getShowProgress(progress);
        }
        
        invalidate();
    }
Copy the code

It is important to note that when we drag “drag the button”, we need to set a specific area of receive events, only when the user presses in the slide area, in order to allow the user to drag the progress bar, is not in any position can drag ICONS to change schedule, that is to judge whether the location of the touch screen currently in slide area:

    private boolean isOnRing(float eventX, float eventY) {
    
        boolean result = false;
        double distance = Math.sqrt(Math.pow(eventX - (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace), 2)
                + Math.pow(eventY - (radius+getPaddingLeft() + specialScaleLineLength + scaleToRingSpace), 2));
                
        if (distance < (2 * radius+getPaddingLeft() + getPaddingRight() + 2 * (specialScaleLineLength + scaleToRingSpace))
                && distance > radius - slideAbleLocation) {
            result = true;
        }
        
        return result;
    }
Copy the code

The rest of the details and methods are left out, just the more general Paint method.

Chestnut delta t.

This is the effect of ripple diffusion and rotation reduction of the sphere, as shown below:

The effect is created by constantly drawing an entire custom View. The ripple diffusion animation is realized by changing the ripple radius periodically. This ripple is composed of two hollow circles successively. In the realization process, attention should be paid to the time and size changes respectively.

    public void startAnima(a) {
        if(drawTimingThread ! =null) {
            drawTimingThread.sendEmptyMessage(MSG_DRAW0); // Start 1 ripple
            float time = (mRMaxRadius - mRMinRadius) / distance * 0.5 f; // select the integer first, then the integer middle
            drawTimingThread.sendEmptyMessageDelayed(MSG_DRAW1, (int)(animaBotIntervalTime * time));// Start 2 ripples}}Copy the code

This is the change in the radius of ripple 1. The reference code is as follows:

    if (mCurRadius0 <= mRMaxRadius) {
        mCurRadius0 += distance;
    } else {
        mCurRadius0 = mRMinRadius + distance;
    }
    circlePointF0 = drawCircleOnRipple(MSG_DRAW0, curIndex0);

    mRPaint0.setAlpha(getAlphaOfRipple(curIndex0));/ / transparency
    mCirclePaint0.setAlpha(getAlphaOfRipple(curIndex0));
    curRadius0 = getRadiusOnRipple(curIndex0);
    curIndex0++;
    if (curIndex0 > (mRMaxRadius - mRMinRadius) / distance)
        curIndex0 = 0;

    cancleHandle(MSG_DRAW0);
Copy the code

The animation effect of the sphere is drawn at the corresponding position every 200ms. Due to the short ripple diffusion period, I set the rotation period of the sphere at 45 degrees, which can be modified by myself. The difficulty here also lies in how to find the coordinates of the center of the sphere, that is, to find the x and Y coordinates of the intersection point between the end ray of the sector and the arc according to the coordinates of the center of the sphere, the radius and the Angle of the sector. The above has also been mentioned, and the code is as follows:

    private PointF drawCircleOnRipple(int msg, int index) {

        // The period starts at a random initial Angle
        if (index == 0 && msg == MSG_DRAW0) {
            cirAngel0 = (float) (Math.random() * -360 + 180);
        } else if (index == 0) {
            cirAngel1 = (float) (Math.random() * -360 + 180);
        }
            
        return CommentUtil.calcArcEndPointXY(
                mRMaxRadius + getPaddingLeft() + mStrokeWidth
                    , mRMaxRadius + getPaddingTop() + mStrokeWidth
                    , msg == MSG_DRAW0 ? mCurRadius0 : mCurRadius1
                    // Rotate 45 degrees per cycle
                    , (msg == MSG_DRAW0 ? curIndex0 : curIndex1) * 1.0 f 
                        / ((mRMaxRadius - mRMinRadius) / distance) * 45f
                    , msg == MSG_DRAW0 ? cirAngel0 : cirAngel1);
    }
Copy the code

The color gradient effect for ripples and spheres is not fully transparent, so my alpha value ranges from 105 to 255, as shown below:

    private int getAlphaOfRipple(int curIndex) {
        final int alpha = curIndex * 150 * distance / (mRMaxRadius - mRMinRadius); // Take only 150 binary values
        return 255 - alpha;
    }
Copy the code

The rest of the details and methods are left out, just the more general Paint method.

Chestnut epsilon.

This effect is achieved by fitting four Bessel curves, as shown below:

We can do a lot of things with Bezier curves. Todo: I’ll do another post on Bezier’s Chestnut. The following is a dynamic diagram and formula of a third-order Bezier curve, which creates and edits the graph by controlling four points on the curve: the starting point, the ending point, and two control points separated from each other. The value of parameter t is equal to the length of a point on the line segment from the starting point divided by the length of the line segment.

When a circle is fitted from an n-segment third-order Bezier curve, the optimal distance from the end of the curve to the nearest control point is (4/3)tan(π/(2n)). And the point at t=0.5 must fall on the arc.

Therefore, when we want to use four Bezier curves to fit a circle, we can simply derive the value of H:

Let’s take four Bezier curves (h = 0.552284749831) and combine them into a complete circle as our initial state. The other thing that we need to do to find this critical value of h, is we need b curves that are convex. Reference codes for starting points and control points are as follows:

    private void calculateCp(a) {
        b = 0.552284749831;

        if (startP == null || endP == null) {
            startP = new PointF(0, - mRadius);
            endP = new PointF(mRadius, 0);
        }

        // the canvas coordinates after translation, coordinates (0,0) is the center of the circle
        cp1 = new PointF((float) (mRadius * b), - mRadius);
        cp2 = new PointF(mRadius, - (float) (mRadius * b));
    }
Copy the code

The ring effect in motion is the result of constantly randomly changing the coordinates of the control point and adding an offset for the starting point. The code for the effect is shown below:

    private void calculateDynamicCp(a) {
        b = Math.random() * 0.44 + 0.55;

        // canvas coordinates after translation, coordinates (0,0) is the center of the circle, 8 control points and 4 start points, clockwise (12 ->3 ->6 ->9)
        if(points ! =null&& points.size() ! =0)
            points.clear();

        points.add(new PointF((float) (Math.random() * - 20 + 10) , - mRadius - (float) (Math.random() * 20)));
        points.add(new PointF((float) (mRadius * b), - mRadius - (float) (Math.random() * 20)));
        points.add(new PointF(mRadius + (float) (Math.random() * 20), - mRadius - (float) (Math.random() * 10 + 10)));

        points.add(new PointF(mRadius + (float) (Math.random() * 10 + 10), (float) (Math.random() * - 20 + 10)));
        points.add(new PointF(mRadius + (float) (Math.random() * 20), (float) (Math.random() * 0.5 * mRadius * b + 0.5 *mRadius * b)));
        points.add(new PointF((float) (mRadius * b + 10), mRadius + (float) (Math.random() * 20)));

        points.add(new PointF((float) (Math.random() * - 20 + 10), mRadius + (float) (Math.random() * 20)));
        points.add(new PointF((float) (- mRadius * b), mRadius + (float) (Math.random() * 20)));
        points.add(new PointF(- mRadius - (float) (Math.random() * 20), (float) (mRadius * b)));

        points.add(new PointF(- mRadius - (float) (Math.random() * 10 + 10), (float) (Math.random() * - 20 + 10)));
        points.add(new PointF(- mRadius - (float) (Math.random() * 20), (float) (- mRadius * b)));
        points.add(new PointF((float) (- mRadius * b), - mRadius - (float) (Math.random() * 20)));
    }
Copy the code

This is the way to draw it. Because the end coordinates of the initial circle are symmetric, you just need to keep rotating the canvas to draw. It’s very easy. However, because the endpoints of a dynamic ring have offsets, only four Bezier curves can be drawn in sequence, and each curve is connected by lineTo. The reference code is as follows:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas); canvas.save(); canvas.translate(mRadius + mStrokeWidth + getPaddingLeft() , mRadius + mStrokeWidth + getPaddingTop()); .//first
        for(int index = 0; index < 4; index ++) {
            canvas.rotate(90f); bPath.moveTo(startP.x, startP.y); bPath.cubicTo(cp1.x, cp1.y, cp2.x, cp2.y, endP.x, endP.y); canvas.drawPath(bPath, bPaint); bPath.reset(); }... canvas.restore(); }Copy the code

OnDraw sets 8 control points and 4 start points without rotating the canvas:

.for (int index = 0; index < 4; index ++) {
        if (index == 0) {
           bPath.moveTo(points.get(0).x, points.get(0).y);
        } else {
            bPath.lineTo(points.get(index * 3).x, points.get(index * 3).y);
        }
                
        bPath.cubicTo(points.get(index * 3 + 1).x, points.get(index * 3 + 1).y
                , points.get(index * 3 + 2).x, points.get(index * 3 + 2).y , index ! =3 ? points.get(index * 3 + 3).x : points.get(0).x , index ! =3 ? points.get(index * 3 + 3).y : points.get(0).y); } canvas.drawPath(bPath, bPaint); bPath.reset(); }...Copy the code

The rest of the details and methods are left out, just the more general Paint method.

That’s the end of this article. If this kind of article is well received, I will consider some more fun little chestnut in the future. If this article is useful to you, give it a thumbs up. Everyone’s affirmation is also the motivation for Dumb I to keep writing.