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.