Last time we talked about the basic operation of Path. This time we will talk about the advanced use of Path — bezier curve.

So what is a Bezier curve? What can Bessel curves do in Android? And how? This article tells you all about it.

What is a Bezier curve?

Bessel curve was published by Pierre Bessel, who mainly applied to the design of the main body of the car, and later became an important parameter curve in computer graphics.

What does a Bessel curve consist of? It usually consists of two parts: data point and control point. So what are data points and control points? Please look at the table below:

type role
The data points The beginning and end of a curve
The control points Controls the degree of bending of the curve

This might sound a little abstract, but let’s go straight to the picture above.

First order Bezier curve:

The first-order Bezier curve is actually a straight line with no control points but only data points P0 and P1, as shown below:

Android provides method: lineTo()

Second-order Bezier curve:

A second-order Bezier curve has a control point P1 and two data points P0 and P2. The diagram below:

Android provides methods: quadTo()

Third-order Bezier curve:

The third-order Bezier curve has two control points P1 and P2 and two data points P0 and P3. The diagram below:

Android cubicTo()

Android does not provide API, so in this will only introduce the second and third order curves, if you are interested in higher order curves, you can go to The Bessel curve – wikipedia and Bessel curve dynamic demo these two websites to learn more.

How do bezier curves come into being

So how did this curve form? First, analyze it from the second-order curve:

Second order Bezier curve formation principle:

1. Connect A and B to form line segment AB, and connect B and C to form line segment BC.

2. Select a point D on the AB segment and a point E on the BC segment to meet the following conditions: AD/AB = BE/BC; connect D and E to form line segment DE.

3. Select a point F at DE and make it meet the following conditions: AD/AB = BE/BC = DF/DE.

4. The trajectory formed by all F points meeting these conditions is the second-order Bezier curve, and the dynamic process is as follows:

The formation principle of third-order Bezier curve:

1. Connect A and B to form AB segment, connect B and C to form BC segment, and connect C and D to form CD segment.

2. Select a point E on the AB segment, a point F on the BC segment, and a point G on the CD segment, so that AE/AB = BF/BE = CG/CD. Connect E and F to form line segment EF, and connect F and G to form line segment FG.

3. Take a point H on the EF line segment and a point I on the FG line segment to meet the following conditions: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG. Join H and I to form line segment HI.

4. Select a point J on the HI line segment and make it meet the following conditions: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG = HJ/HI.

5. The trajectories formed by all J points meeting these conditions are third-order Bezier curves, and the dynamic process is as follows:

Using bezier curves in Android

Now that we’ve talked about the principles, it’s time to figure out how to apply bezier curves. Here I will use two examples to illustrate the use of second – and third-order Bezier curves:

Application of second-order curved Bessel curve:
Method preview:
public void quadTo (float x1, float y1, float x2, float y2)
Copy the code
What does it do:

Draw a second order Bezier curve

How to use:

Since the second-order Bezier curve needs three points to be determined, the four parameters in the quadTo method are used to determine the second and third points respectively. The first point is the point where path operated last time. Now practice this method with an example:

Water ripple effect:
Effect:

Implementation idea:
  1. Draw at least two sections of the ripple
  2. Use ValueAnimator to continuously get the shifted value offset
  3. Use offset to constantly change the position of the ripple

Now step by step:

1. Draw at least two ripples

We’re going to draw two ripples first. A ripple contains two curves. Each of these curves we can draw using the quadTo() method.

To make it easier to understand, take a look at the picture below:

MWL is the length of a ripple and mCenterY is half the height of the screen.

  • Draw the first curve of the first wave:
mPath.moveTo(-mWL, mCenterY); QuadTo ((-mwl * 3/4), mCenterY + 60, (-mwl / 2), mCenterY); // Move the starting point of the path operation to (-mwl,mCenterY) mPath. // Draw the first curve of the first waveCopy the code
  • Draw the second curve of the first wave:
mPath.quadTo((-mWL / 4) , mCenterY - 60, 0, mCenterY); // Draw the second curve of the first waveCopy the code
  • Draw the first curve of the second wave:
mPath.quadTo((mWL /4) , mCenterY + 60, (mWL / 2), mCenterY); // Draw the first curve of the second waveCopy the code
  • Draw the second curve of the second wave:
mPath.quadTo((mWL * 3/ 4) , mCenterY - 60, mWL, mCenterY); // Draw the second curve of the second waveCopy the code
2. Use ValueAnimator to continuously obtain the offset value

So now think about how to make these ripples move? We need a shift of offset. This value should be added to the x-coordinate of each point, and the offset is constantly changing to create a shift effect to the right. So how do I get the value of this changed offset? The answer is to use ValueAnimator. Usage:

ValueAnimator animator = ValueAnimator.ofInt(0, mWL); //mWL is the length of a ripple animator.setDuration(1000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { offset = (Integer) animation.getAnimatedValue();  // The value of offset is between [0,mWL]. postInvalidate(); }}); animator.start(); }Copy the code

The offset will keep changing from 0 to mWL as long as the animation starts.

3. Use offset to continuously change the position of the ripple

Now I’m going to add offset to all of the x-coordinates of the curve. This will create a panning effect, and to simplify the code, use the for loop to draw the curve.

    mPath.moveTo(-mWL + mOffset, mCenterY);
    for (int i = 0; i < 2; i++) {
        mPath.quadTo((-mWL * 3 / 4) + (i * mWL) + offset, mCenterY + 60, (-mWL / 2) + (i * mWL) + offset, mCenterY);
        mPath.quadTo((-mWL / 4) + (i * mWL) + offset, mCenterY - 60, i * mWL + offset, mCenterY);
      }
Copy the code

Next, in order to fit various screens, we need to calculate the number of ripples required according to the width of the phone:

MWaveCount = (int) Math. Round (mScreenWidth/mWL + 1.5); // This ensures that the ripple covers the entire screenCopy the code

The for loop above could also be changed to:

    mPath.moveTo(-mWL + mOffset, mCenterY);
    for (int i = 0; i < mWaveCount; i++) {
        mPath.quadTo((-mWL * 3 / 4) + (i * mWL) + offset, mCenterY + 60, (-mWL / 2) + (i * mWL) + offset, mCenterY);
        mPath.quadTo((-mWL / 4) + (i * mWL) + offset, mCenterY - 60, i * mWL + offset, mCenterY);
      }
Copy the code
Application of third-order Bessel curve:
An elastic circle:
Effect:

Implementation idea:
  1. Use bezier curves to draw perfect circles
  2. The circle shrinks by modifying the size of the coordinates
1. Use bezier curves to draw perfect circles

We first need to know how to use the cubicTo() method to draw a perfect circle of radius R. In fact, four third-order Bezier curves are needed to construct a perfect circle using cubicTo(). As shown in the figure:

If I were to draw P0P3 how would I draw that curve? We want to know the coordinates of P0, P1, P2, P3. P0 and P3, we already know, are (0,-r),(r,0). So what are the coordinates of P1 and P2? There’s an argument for Approximate a circle with Cubic Bezier curves in this article, if you’re interested. I’m just saying the result, and I end up with a number, and that number is c = 0.551915024494. So the coordinates of P1 and P2 are cr minus r, r minus cr. The coordinates of the other points were obtained in the same way, but I won’t go into details here.

To make it easier to manage these points, I’ve encapsulated them into two classes. These are HorizontalLine and VerticalLine. The two lines above and below the circle belong to the HorizontalLine and the two lines to the left and right of the circle belong to the VerticalLine.

Here is the code for the two classes:

private floatC = 0.551915024494 f; class HorizontalLine { public PointF left = new PointF(); //P7 P11 public PointF middle = new PointF(); //P0 P6 public PointF right = new PointF(); //P1 P5 public HorizontalLine(float x,float y) {
            left.x = -radius*c;
            left.y = y;
            middle.x = x;
            middle.y = y;
            right.x = radius*c;
            right.y = y;
        }

        public void setY(float y) {
            left.y = y;
            middle.y = y;
            right.y = y;
        }

    }

    class VerticalLine {
        public PointF top = new PointF(); //P2 P10
        public PointF middle = new PointF(); //P3 P9
        public PointF bottom = new PointF(); //P4 P8


        public VerticalLine(float x,float y) {
            top.x = x;
            top.y = -radius*c;
            middle.x = x;
            middle.y = y;
            bottom.x = x;
            bottom.y = radius*c;
        }


        public void setX(floatx) { top.x = x; middle.x = x; bottom.x = x; }}Copy the code

Here is the code to draw a circle with the cubicTo() method:

private Paint mPaint; private Path mPath; private int mScreenHeight; Private int mScreenWidth; // Screen width privatefloat radius = 100;

    private void initPaint() {
        mPath = new Path();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.parseColor("#59c3e2"));
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    }

    private void initPoint() { mTopLine = new HorizontalLine(0,-radius); mBottomLine = new HorizontalLine(0,radius); mLeftLine = new VerticalLine(-radius,0); mRightLine = new VerticalLine(radius,0); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.reset(); Canvas. Translate (mScreenWidth / 2, mScreenHeight / 2); // Move the canvas to the center of the screen. mPath.moveTo(mTopLine.middle.x,mTopLine.middle.y); mPath.cubicTo(mTopLine.right.x,mTopLine.right.y,mRightLine.top.x,mRightLine.top.y, mRightLine.middle.x,mRightLine.middle.y); mPath.cubicTo(mRightLine.bottom.x,mRightLine.bottom.y,mBottomLine.right.x,mBottomLine.right.y, mBottomLine.middle.x,mBottomLine.middle.y); mPath.cubicTo(mBottomLine.left.x,mBottomLine.left.y,mLeftLine.bottom.x,mLeftLine.bottom.y, mLeftLine.middle.x,mLeftLine.middle.y); mPath.cubicTo(mLeftLine.top.x,mLeftLine.top.y,mTopLine.left.x,mTopLine.left.y, mTopLine.middle.x,mTopLine.middle.y); canvas.drawPath(mPath,mPaint); }Copy the code
The effect is as follows:

2. Modify the size of the coordinates to create a circle contraction effect

You can actually shrink the circle by adding and subtracting certain coordinates. For example, if I want to achieve this effect, what should I do?

You can do this by just increasing the horizontal coordinate of P2, P3, and P4.

Now try shrinking the circle:

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.reset(); Canvas. Translate (mScreenWidth / 2, mScreenHeight / 2); Mrightline.setx (radius * 1.5f); mPath.moveTo(mTopLine.middle.x,mTopLine.middle.y); mPath.cubicTo(mTopLine.right.x,mTopLine.right.y,mRightLine.top.x,mRightLine.top.y, mRightLine.middle.x,mRightLine.middle.y); mPath.cubicTo(mRightLine.bottom.x,mRightLine.bottom.y,mBottomLine.right.x,mBottomLine.right.y, mBottomLine.middle.x,mBottomLine.middle.y); mPath.cubicTo(mBottomLine.left.x,mBottomLine.left.y,mLeftLine.bottom.x,mLeftLine.bottom.y, mLeftLine.middle.x,mLeftLine.middle.y); mPath.cubicTo(mLeftLine.top.x,mLeftLine.top.y,mTopLine.left.x,mTopLine.left.y, mTopLine.middle.x,mTopLine.middle.y); canvas.drawPath(mPath,mPaint); }Copy the code
The effect is as follows:

Similarly, to achieve the effect of the previous GIF, you need to decrease the ordinate of the points above and below the line, and then keep moving the canvas around. Specific code can be downloaded to see the source code.

Source code download:

  • BezierDemo

References:

  • Bezier curve of Path
  • Four steps to achieve chromelike wipelayout effect
  • The elastic circle of three Bezier curves exercises
  • Bessel curve literacy
  • Android custom View – Bezier curve to achieve water ripple effect