Android custom Bezier curve tool

Please visit my personal website to view this article

When I was studying Bessel curve before, I found that there were too many duplicate materials on the Internet when SEARCHING for relevant materials. Moreover, Android Canvas only provides quadTo and cubicTo methods to draw second-order and third-order Bessel curves. Online Bessel curve drawing is very few, (in this provide a bezier website online, according to the online documentation), and short of no similar tools in the android mobile phone, in the design or using Bessel curve has increased a lot of work, just in learning related knowledge, do a more complete android Bezier curve tool.

Bessel curve

I’m going to leave you with the basic Bezier curves, but if you’re interested, you can refer to the notes on bezier curves that I’ll do later

It’s actually very easy to understand bezier curves, you can think of them as a recursive form. Calculate the points in the current line segment according to the proportionality coefficient, connect the line segments in sequence after obtaining all the points, and repeat the above steps until only one point remains, which is in the Bezier curve. Calculate the points under each coefficient of proportionality, and the set of these points is the Bezier curve.

The basic function

Plot common Bezier curves

You can plot common second – and third-order Bezier curves

Draw multi-order Bezier curves

You can plot unusual Bezier curves

Turn on/off the auxiliary cable

You can open auxiliary line segments with different color levels

Draw an unlimited Bezier curve

Break the limit of 15 key points and draw without limit. The fun of the mystery)

Fine tuning key points to draw new Bezier curves

Fine-tune key points to draw new Bezier curves

Sets the drawing time of bezier curves

Set the drawing time of Bezier curve. The longer the drawing time, the smoother the Bezier curve will be

The design process

Two custom views are designed, one of which is used to collect touch events on the screen, and show the added control point and the line between control points, and achieve long press the screen to drag the nearest point within a certain range. Another custom view is used to receive the parameters of the control point and draw bezier curves and auxiliary information according to the control point.

Bezier curve drawing layer

By recursive method, line segments between current control points are drawn in each layer. In addition to the fixed style of the first layer, auxiliary line segments and control points under a certain order can be controlled whether or not to display. In unrestricted mode, there is no upper limit to the control points of the bezier curve currently drawn, but the style of the auxiliary line segments in the current mode is consistent for display purposes.

Screen touch event monitoring layer

Monitor the click events on the screen and add control points. In addition, the monitor whether the nearest point within a certain range needs to be moved to the touch position will also be enabled behind the touch screen for a long time. And can provide a list of current points for bezier curve drawing layer drawing Bezier curve drawing layer drawing Bezier curve.

Code implementation

Screen touch event monitoring layer

Mainly lies in the monitoring of touch events on the screen

override fun onTouchEvent(event: MotionEvent): Boolean {


    touchX = event.x
    touchY = event.y
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            toFindChageCounts = true
            findPointChangeIndex = -1
            // Add the point clicked before the point to the screen
            if (controlIndex < maxPoint || isMore == true) {
                addPoints(BezierCurveView.Point(touchX, touchY))
            }
            invalidate()
        }
        MotionEvent.ACTION_MOVE ->{
            checkLevel++
            // Determine whether the coordinate of the replacement point needs to be detected
            if (inChangePoint){
                // Determine whether the current long-press point is used to start looking for attachments
                if (touchX == lastPoint.x && touchY == lastPoint.y){
                    changePoint = true
                    lastPoint.x = -1F
                    lastPoint.y = -1F
                }else{
                    lastPoint.x = touchX
                    lastPoint.y = touchY
                }
                // Start looking for nearby points
                if (changePoint){
                    if (toFindChageCounts){
                        findPointChangeIndex = findNearlyPoint(touchX , touchY)
                    }
                }

                // Determine if there are nearby points
                if (findPointChangeIndex == -1) {if (checkLevel > 1){
                        changePoint = false}}else{
                    // Update the coordinates of nearby points and redraw the page content
                    points[findPointChangeIndex].x = touchX
                    points[findPointChangeIndex].y = touchY
                    toFindChageCounts = false
                    invalidate(a)
                }
            }

        }
        MotionEvent.ACTION_UP ->{
            checkLevel = -1
            changePoint = false
            toFindChageCounts = false}}return true
}
Copy the code

For the detection of the nearest point, the Pythagorean theorem can be obtained.

// Determine if there are drawn points near the current touched point
private fun findNearlyPoint(touchX: Float, touchY: Float): Int {
    Log.d("bsr"  , "touchX: ${touchX} , touchY: ${touchY}")
    var index = -1
    var tempLength = 100000F
    for (i in 0..points.size - 1){
        val lengthX = Math.abs(touchX - points[i].x)
        val lengthY = Math.abs(touchY - points[i].y)
        val length = Math.sqrt((lengthX * lengthX + lengthY * lengthY).toDouble()).toFloat()
        if (length < tempLength){
            tempLength = length

            if (tempLength < minLength){
                toFindChageCounts = false
                index = i
            }
        }
    }

    return index
}
Copy the code

Comparatively speaking, the main difficulty is the touch detection of the screen, which needs to control the time and the movement of changan after finding the appropriate point. Beyond that, simply add a line segment to the touch point.

Bezier curve drawing layer

The main Bezier curve is achieved by recursion

// Draw the Bezier curve recursively
private fun  drawBezier(canvas: Canvas, per: Float, points: MutableList<Point>) {

    val inBase: Boolean

    // Determine whether the current level needs to draw a line segment
    if (level == 0 || drawControl){
        inBase = true
    }else{
        inBase = false
    }


    // Select the line segment and text color based on the current level and whether the mode is unrestricted
    if (isMore){
        linePaint.color = 0x3F000000
        textPaint.color = 0x3F000000
    }else {
        linePaint.color = colorSequence[level].toInt()
        textPaint.color = colorSequence[level].toInt()
    }

    // Move to the starting position
    path.moveTo(points[0].x , points[0].y)

    // If there is only one point
    // This point is on the Bezier curve according to the definition of bezier curve
    // Add this point to the Bezier curve point set (after the page is redrawn, the previously drawn data will be lost and need to go back to the previous curve path)
    // Draw the current point to the page
    if (points.size == 1){
        bezierPoints.add(Point(points[0].x , points[0].y))
        drawBezierPoint(bezierPoints , canvas)
        val paint = Paint()
        paint.strokeWidth = 10F
        paint.style = Paint.Style.FILL
        canvas.drawPoint(points[0].x , points[0].y , paint)
        return
    }


    val nextPoints: MutableList<Point> = ArrayList()

    // Update path information
    // Calculate the coordinates of the next level control point
    for (index in 1..points.size - 1){
        path.lineTo(points[index].x , points[index].y)

        val nextPointX = points[index - 1].x -(points[index - 1].x - points[index].x) * per
        val nextPointY = points[index - 1].y -(points[index - 1].y - points[index].y) * per

        nextPoints.add(Point(nextPointX , nextPointY))
    }

    // Draw the text information of the control point
    if(! (level ! =0 && (per==0F || per == 1F))) {if (inBase) {
            if(isMore && level ! =0){
                canvas.drawText("At", points[0].x, points[0].y, textPaint)
            }else {
                canvas.drawText("${charSequence[level]}0", points[0].x, points[0].y, textPaint)
            }
            for (index in 1..points.size - 1) {if(isMore && level ! =0){
                    canvas.drawText( "${index}:${index}" ,points[index].x , points[index].y , textPaint)
                }else {
                    canvas.drawText( "${charSequence[level]}${index}" ,points[index].x , points[index].y , textPaint)
                }
            }
        }
    }

    // Draw the current hierarchy
    if(! (level ! =0 && (per==0F || per == 1F))) {if (inBase) {
            canvas.drawPath(path, linePaint)
        }
    }
    path.reset()

    // Update the hierarchy information
    level++

    // Draw the next layer
    drawBezier(canvas, per, nextPoints)

}
Copy the code

In addition, since each calculation results in points on the Bezier curve, you need to collect these points and plot all the points collected previously

// Draw the front bezier curve
private fun  drawBezierPoint(bezierPoints: MutableList<Point> , canvas: Canvas) {
    val paintBse = Paint()
    paintBse.color = Color.RED
    paintBse.strokeWidth = 5F
    paintBse.style = Paint.Style.STROKE

    val path = Path()
    path.moveTo(bezierPoints[0].x , bezierPoints[0].y)

    for (index in 1..bezierPoints.size -1){
        path.lineTo(bezierPoints[index].x , bezierPoints[index].y)
    }

    canvas.drawPath(path , paintBse)

}
Copy the code

Related code can visit my Github, everyone is welcome to star or make suggestions.