blow your mind

The BYM series aims to share not only technology but also ideas, not just being a porter of code.

1. Background

The company’s business needs to do a shutter animation for the camera, looking for the whole network, few cases. It took me two days to create a shutter animation by hand with a custom View. It is also a small progress of my own. The difficulty of the whole project lies not in drawing, but in mathematics.

2. Review a few points

Canvas

Canvas is our Android canvas, just like any other language, as long as you have the following Paint you can Paint anything you want.

Paint

Paint is commonly used method setColor, setStyle setStrokeWidth

Path

When some basic graphics functions of Canvas cannot meet our needs, we can use path for combined drawing, and finally call Canvas. drawPath, path.close() can be used to close the graph. If you set path’s.setstyle (paint.style.fill), it will FILL the entire path.

Mathematics knowledge

Sines, sines, cosines and cosines, review them, help with homework. 😝

3. Demand effect

4. Requirements analysis

We extract three images of Gif for analysis. The graph is composed of two parts, one part is the outer circle, the other part is the dynamic irregular graph.

5

A. Find two points on the circle

First, six equilateral triangles, gradually converging toward the center of the inner circle, the equilateral triangle spacing is shrinking. So these six equilateral triangles are just at different angles, so when we start writing our code, we can draw one of them, to get an equilateral triangle converging to the center of the circle.

(√3/2/R,R/2)
 float startX = 0;
 float startY = -mRadius;
 float stopX = (float) Math.sqrt(3) * mRadius / 2f;
 float stopY = -mRadius / 2f;
Copy the code

B. Find the third TagPoint.

If it’s easy to do, we can think of it as a third point, right on the blue line. In this case, we only need to solve the position of the third point when the value of the Angle θ between the third point and the edge A composed of (0, -r) and the edge B composed of (√3/2/R,R/2) changes dynamically. So theta is the variable from 0 to 60 degrees.

C. Derive the formula to solve x and y.

I’m not going to draw the auxiliary line, but I’m going to give you the formula

The conversion to Java code is as follows

  float tagX = (float) (Math.sin(Math.toRadians(60)) * mRadius 
  / Math.cos(Math.toRadians(30 - swipeAgenl)) 
  * Math.sin(Math.toRadians(60 - swipeAgenl)));
  
  float tagY = (float) (Math.sin(Math.toRadians(60)) * mRadius 
  / Math.cos(Math.toRadians(30 - swipeAgenl)) 
  * Math.cos(Math.toRadians(60 - swipeAgenl))) - mRadius;

Copy the code

Run it and see if it works as expected

4. The arc

The point 0, -r, to square root of 3/2/R,R/2, where it intersects the circle is going to be an arc. This can be drawn using either the path. addArc or canvas.drawArc methods. There are two ways to pass in parameters, but essentially you pass in a rectangle and draw an arc inside the rectangle.

Path.java
 public void addArc(RectF oval, float startAngle, float sweepAngle) {
        addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle);
    }
   Canvas.java
  public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
            @NonNull Paint paint) {
        drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
                paint);
    }
    
Copy the code

StartAngle is the Angle at which the arc starts, and sweepAngle is the Angle at which the arc passes. The code is as follows:

  RectF oval = new RectF(-mRadius, -mRadius, mRadius, mRadius);
  mPaint.setStyle(Paint.Style.FILL);
  path.addArc(oval, -90.60);
Copy the code

5. Look at the results

I’ve drawn one, so six are easy to draw, and I’ve made each one a different color for you to see.

Gee, something’s wrong. It’s not what I expected. We can look at the UI renderings again. Regardless of the rotation Angle of the graph, it can be observed that the graph is formed with the red border lengthening over time. The maximum length is R, the minimum length is 0, or whatever you start with, and the other thing to notice is that the Angle is always 60 degrees as the red line goes along.

6. Recalculate

Again, sine, cosine, figure out where the point is on the red line.

 float L1 = (float) Math.sqrt(Math.pow(mRadius, 2.0) - 
 Math.pow(Math.sin(Math.toRadians(60)) * a, 2.0));
 
 float consL1 = L1 / mRadius;
 float sinL1 = (float) Math.sin(Math.toRadians(60)) * a / mRadius;
 float L = (float) (L1 + Math.sin(Math.toRadians(30)) * a);

 float tagX = L * sinL;
 float tagY = L * consL - mRadius;
Copy the code

7. You’re done

/ * * *@authoer create by markfrain
 * @githubhttps://github.com/furuiCQ * Gao Huai see physical and naive * time: 4/28/21 * description: ShootView * /
public class ShootView extends View {
    private static final int DEGREE_60 = 60;

    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final RectF mBounds = new RectF();
    private int mRadius;
    private int mCenterX;
    private int mCenterY;



    private ValueAnimator mPlayAnimator;
    private float swipeAgenl = mRadius;// The extended distance


    private float mShootLineTotalRotateAngle;// Rotate the Angle
    private ValueAnimator mPreShootLineTotalRotateAnimator;


    private float padding = 20;
    private ValueAnimator paddingAnimator;

    private float fix = 4;
    private ValueAnimator fixAnimator;


    private static final float SHOOT_LINE_ROTATE_END_RADIANS = (float) (Math.PI / 6.0);// Rotate the end radian
    private static final float SHOOT_LINE_ROTATE_START_RADIANS = (float) (Math.PI / 2.0);// Rotate the starting radian
    private static final float SHOOT_LINE_ROTATE_START_DEGREE =
            (float) Math.toDegrees(SHOOT_LINE_ROTATE_END_RADIANS);// Rotate the starting Angle
    private static final int PRE_SHOOT_LINE_TOTAL_ROTATE_DURATION = 1000;

    public static final Property<ShootView, Float> SHOOT_LINE_TOTAL_ROTATE_DEGREE =
            new Property<ShootView, Float>(Float.class, null) {
                @Override
                public Float get(ShootView object) {
                    return object.mShootLineTotalRotateAngle;
                }

                @Override
                public void set(ShootView object, Float value) { object.mShootLineTotalRotateAngle = value; object.invalidate(); }};public ShootView(Context context) {
        this(context, null);
    }

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

    public ShootView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.parseColor("#ffc6c6c6"));

        paddingAnimator = ValueAnimator.ofFloat(20.0);
        paddingAnimator.setInterpolator(new LinearInterpolator());
        paddingAnimator.setDuration(PRE_SHOOT_LINE_TOTAL_ROTATE_DURATION);
        paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                padding = (float) animation.getAnimatedValue(); invalidate(); }}); fixAnimator = ValueAnimator.ofFloat(4.0);
        fixAnimator.setInterpolator(new LinearInterpolator());
        fixAnimator.setDuration(1200);
        fixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                fix = (float) animation.getAnimatedValue(); invalidate(); }}); mPreShootLineTotalRotateAnimator = ValueAnimator.ofFloat(-(SHOOT_LINE_ROTATE_START_DEGREE /2.0 f) - 240.0 f,
                        -(SHOOT_LINE_ROTATE_START_DEGREE / 2.0 f) - 120.0 f);
        mPreShootLineTotalRotateAnimator.setInterpolator(new LinearInterpolator());
        mPreShootLineTotalRotateAnimator.setDuration(PRE_SHOOT_LINE_TOTAL_ROTATE_DURATION);
        mPreShootLineTotalRotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mShootLineTotalRotateAngle = (float) animation.getAnimatedValue(); invalidate(); }}); }@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

// drawTest(canvas);
        drawArc(canvas);
        drawCircle(canvas);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mBounds.set(0 + getPaddingLeft(), 0 + getPaddingTop(), w - getPaddingRight(),
                h - getPaddingBottom());

        mRadius = (int) (Math.min(mBounds.width(), mBounds.height()) / 2);
        mCenterX = (int) mBounds.centerX();
        mCenterY = (int) mBounds.centerY();
        swipeAgenl = mRadius * 2 / 3f;
    }

    float mStartX;
    float mStartY;

    int colors[] = {Color.RED, Color.GREEN, Color.YELLOW, Color.MAGENTA, Color.CYAN, Color.GRAY};

    private void drawTest(Canvas canvas) {
        drawCircle(canvas);
        canvas.save();
        canvas.translate(mCenterX, mCenterY);

        int i = 0;
        canvas.save();
        canvas.rotate(-DEGREE_60 * i);
        Path path = new Path();

        float stopX = (float) Math.sqrt(3) * mRadius / 2f;
        float stopY = -mRadius / 2f;

        canvas.drawLine(0, -mRadius, stopX, stopY, mPaint);
        mPaint.setColor(Color.RED);
        canvas.drawPoint(stopX, stopY, mPaint);
        mPaint.setColor(Color.RED);

        //canvas.drawLine(0, 0, stopX, stopY, mPaint);

        RectF oval = new RectF(-mRadius, -mRadius, mRadius, mRadius);


        RectF rectF = new RectF(-mRadius, -2 * mRadius, mRadius, 0);

        mPaint.setStyle(Paint.Style.FILL);
        path.addArc(oval, -90.60);


        // Calculate the direct point
        float tagX = (float) (Math.sin(Math.toRadians(60)) * mRadius / Math.cos(Math.toRadians(30 - swipeAgenl)) * Math.sin(Math.toRadians(60 - swipeAgenl)));
        float tagY = (float) (Math.sin(Math.toRadians(60)) * mRadius / Math.cos(Math.toRadians(30 - swipeAgenl)) * Math.cos(Math.toRadians(60 - swipeAgenl))) - mRadius;

// float tagX = math.sqrt (math.pow (mRadius,2.0) - math.pow (math.sin (math.toradians (60) * swipeAgenl), 2.0));

// float L = (float) math.sqrt (math.pow (mRadius, 2.0) - math.pow (math.sin (math.toradians (60)) * swipeAgenl, 2.0)); // float L = (float) math.pow (math.pow (mRadius, 2.0) - math.pow (math.toradians (60)) * swipeAgenl);
// float consL = L / mRadius;
// float sinL = (float) Math.sin(Math.toRadians(60)) * swipeAgenl / mRadius;
// float LL = (float) (L + Math.sin(Math.toRadians(30)) * swipeAgenl);
//
// float tagX = LL * sinL;
// float tagY = LL * consL - mRadius;

         canvas.drawPoint(tagX, tagY, mPaint);

// path.moveTo(stopX, stopY);
// path.lineTo(tagX, tagY);
// path.lineTo(0, -mRadius);
        canvas.drawPath(path, mPaint);
/ /}


    }

    private void drawArc(Canvas canvas) {

        canvas.save();
        canvas.translate(mCenterX, mCenterY);
        canvas.rotate(mShootLineTotalRotateAngle);
        for (int i = 0; i < 6; i++) {
            canvas.save();
            canvas.rotate(-DEGREE_60 * i);
            Path path = new Path();
            mPaint.setColor(colors[i]);
            RectF oval = new RectF(-mRadius, -mRadius, mRadius, mRadius);
            mPaint.setStyle(Paint.Style.FILL);

// mRadius- padding
            double asin = Math.toDegrees(Math.asin((mRadius - padding) / mRadius / 2)) * 2;
            path.addArc(oval, -90, (float) asin);

            float stopX = (float) Math.sqrt(3) * mRadius / 2f;
            float stopY = -mRadius / 2f;
            float L = (float) Math.sqrt(Math.pow(mRadius, 2.0) - Math.pow(Math.sin(Math.toRadians(60)) * swipeAgenl, 2.0));
            float consL = L / mRadius;
            float sinL = (float) Math.sin(Math.toRadians(60)) * swipeAgenl / mRadius;
            float LL = (float) (L + Math.sin(Math.toRadians(30)) * swipeAgenl);

            float tagX = LL * sinL;
            float tagY = LL * consL - mRadius;

            path.moveTo(stopX - padding, stopY - padding - fix);
            path.lineTo(tagX - padding, tagY - padding);
            path.lineTo(0, -mRadius);
            canvas.drawPath(path, mPaint);
            canvas.restore();
        }
        canvas.restore();
    }

    private void drawCircle(Canvas canvas) {
        mPaint.setColor(colors[1]);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.save();
        canvas.translate(mCenterX, mCenterY);
        canvas.drawCircle(0.0 f.0.0 f, mRadius, mPaint);
        canvas.restore();
    }

    public void startPlay(a) {
        mPlayAnimator = ValueAnimator.ofFloat(mRadius, mRadius / 5f);
        mPlayAnimator.setDuration(PRE_SHOOT_LINE_TOTAL_ROTATE_DURATION);
        mPlayAnimator.setInterpolator(new LinearInterpolator());
        mPlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                swipeAgenl = (float) animation.getAnimatedValue();
                Log.i("swipeAgenl".""+ swipeAgenl); invalidate(); }}); mPlayAnimator.start(); mPreShootLineTotalRotateAnimator.start(); paddingAnimator.start(); fixAnimator.start(); }}Copy the code

Source code for making

reference

  • HenCoder Android development advanced: custom View 1-1 drawing basis
  • Github ShootRefreshView