Custom View (Canvas, Paint, Bessel Curve)
Catalog
Custom View (Canvas, Paint, Bessel Curve)
Draw a circle on Canvas and save it with Bitmap
On the path, draw circles in turn to form line animation.
Drawing multiple paths at the same time
In the Android application layer, it is well known that customizing a View requires three steps: measurement, layout and drawing. Each step in depth will be able to write a long article. Today we mainly know about the part of onDraw drawing. Introduced through an example of TreeView, let's first look at the rendering.
// To do imports a rendering
Internal Work Tips: Point, Line and Surface.
To draw, four basic components are required:
- Bitmap container for storing pixels
- Host for Canvas to execute drawing commands
- Elements to be drawn by Rect/Path/text/Bitmap
- How Paint Draws
Here we divide it into the following steps:
-
Draw a circle on Canvas and save it using Bitmap (this step is critical, if you don't draw on bitmap, you can't save it)
-
Draw a circle on the path, save it and form a line (animation)
-
Drawing multiple paths at the same time
Draw a circle on Canvas and save it with Bitmap
Step: Create a new Canvas (Bitmap) - > Draw on the new Canvas - > Draw bitmap on the View Canvas
Create a new Canvas(Bitmap)
/** * Save the drawn bitmap */ class BlankBoard { var blankBoardCanvas: Canvas var blankBoardBitmap: Bitmap constructor(bitmap: Bitmap){ this.blankBoardBitmap = bitmap this.blankBoardCanvas = Canvas(this.blankBoardBitmap) } }
class DrawViewInBitmap : View { //... override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) blankBoard = BlankBoard(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)) } //... }
Drawing on New Canvas
class DrawViewInBitmap : View { //... override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) //.. //1. Draw a circle to bitmap drawCircle2BlankBoardBitmap(200f, 200f, 50f,blankBoard.blankBoardCanvas, paint) } /** * Draw a circle to a whiteboard * (x-coordinate, y-coordinate, radius of circle, brush) */ private fun drawCircle2BlankBoardBitmap(dx: Float, dy: Float , radius: Float, canvas: Canvas, mPaint: Paint){ canvas.save()//Save the previous canvas coordinate status canvas.translate(dx,dy) canvas.drawCircle(0f,0f,radius,mPaint) canvas.restore()//Restore the previous canvas coordinate state } //... }
Draw bitmap on Canvas of View
class DrawViewInBitmap : View { //... override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) //... //1. Draw a circle to bitmap drawCircle2BlankBoardBitmap(200f, 200f, 50f,blankBoard.blankBoardCanvas, paint) //2. Draw blankBoard.bitmap in canvas of custom view canvas?.drawBitmap(blankBoard.blankBoardBitmap,0f,0f,paint) } //... }
See the effect:
Here's just the key Code. I'll give you the source code later.
On the path, draw circles in turn to form line animation.
Effect:
By modifying the above file, after each drawing of a circle, y coordinates + 10, the radius of the circle is 97%, call invalidate() to redraw the layout, you can.
Here are some of the key code
class DrawViewInBitmap2 : View { //Current radius private var currentRadius: Float = 50f //Current y-coordinates of a circular point private var currentY: Float = 200f //... override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) //... //1. Draw a circle to bitmap drawCircle2BlankBoardBitmap(200f, currentY, currentRadius,blankBoard.blankBoardCanvas, paint) //2. Draw blankBoard.bitmap in canvas of custom view canvas?.drawBitmap(blankBoard.blankBoardBitmap,0f,0f,paint) //Step up by 10 currentY +=10 currentRadius*=0.97f if (currentY < 700) { //Redraw the layout and call onDraw() invalidate() } /*The point to note here is that the same Bitmap can be drawn on different Canvas. At first, Bitmap was drawn on blankBoard.blankBoardCanvas. Although the drawing was successful, we will show it to users. It must be drawn on the canvas obtained in the View.onDraw method. blankBoard.blankBoardBitmap In Canvas*/ } //... }
Drawing multiple paths at the same time
class TreeView: View { private val paint: Paint = Paint() private var hasEnd: Boolean = false private var linkedListBranch: LinkedList<Branch> = LinkedList() private lateinit var snapShot: SnapShot constructor(context: Context?) :this(context, null) constructor(context: Context?, attrs: AttributeSet?) :this(context, attrs,0) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){ linkedListBranch = LinkedList<Branch>() linkedListBranch.add(getBranches()) } private fun getBranches(): Branch { val bs = Array<IntArray>(3){ IntArray(10) } bs[0] = intArrayOf(0, -1, 340, 617, 210, 435, 324, 301, 30, 100) bs[1] = intArrayOf(1, 0, 285, 518, 220, 353, 154, 351, 15, 70) bs[2] = intArrayOf(2, 1, 237, 413, 221, 432, 155, 402, 6, 60) var temp = arrayOfNulls<Branch>(3) for (i in 0..bs.size-1) { temp[i] = Branch(bs[i]) val parentId = temp[i]!!.parentId if (parentId > -1) { temp[parentId]?.addChild(temp[i]) } } return temp[0]!! } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) snapShot = SnapShot(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)) } override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) Log.i("this","onDraw") drawBranches() canvas?.drawBitmap(snapShot.bitmap,0f,0f,paint) if (!hasEnd) { invalidate() } } private fun drawBranches() { if (!linkedListBranch.isEmpty()) { // if (linkedListBranch != null) { var nextRankBranch: LinkedList<Branch>? = null var it = linkedListBranch.iterator() snapShot.canvas.save() while (it.hasNext()) { var itemBranch = it.next() if (!itemBranch.drawGrowingBranch(snapShot.canvas, paint, 1)) { it.remove() if (itemBranch.childBranch != null) { if (nextRankBranch == null) { nextRankBranch = itemBranch.childBranch } else { nextRankBranch.addAll(itemBranch.childBranch) } } } } snapShot.canvas.restore() //Solve the problem of not adding branches if (nextRankBranch != null) { linkedListBranch.addAll(nextRankBranch) } if (linkedListBranch.isEmpty()) { hasEnd = true } } } /** * The whole tree is drawn from many points, each drawing needs to save the previous record, so it can not be placed in the onDraw of TreeView. * Image rendering class, saved in bitmap */ class SnapShot { constructor(createBitmap: Bitmap?){ this.bitmap = createBitmap this.canvas = Canvas(bitmap) /*canvas = Canvas() var p = Paint() p.color = Color.RED canvas.drawBitmap(createBitmap,0f,0f,p)*/ } var bitmap: Bitmap? var canvas: Canvas } /** * Stems */ class Branch { private val TAG: String? = "Branch" private var currentX: Float = 0.0f private var currentY: Float = 0.0f private var currentLenght: Int = 0 public lateinit var childBranch: LinkedList<Branch> private var maxLength: Int = 0 private var radius: Float = 0f var parentId: Int = 0 private var id: Int = 0 private lateinit var endPoint: Point private lateinit var controlPoint: Point private lateinit var startPoint: Point constructor(data: IntArray) { this.id = data[0] this.parentId = data[1] this.startPoint = Point(data[2], data[3]) this.controlPoint = Point(data[4], data[5]) this.endPoint = Point(data[6], data[7]) this.radius = data[8]*1.0f this.maxLength = data[9] childBranch = LinkedList<Branch>() } fun addChild(branch: Branch?) { if (childBranch == null) { childBranch = LinkedList<Branch>() } branch?.let { childBranch.add(it) } } fun drawGrowingBranch(canvas: Canvas, paint: Paint, scaleFactor: Int): Boolean { Log.i(TAG,"grow $currentLenght") if (currentLenght < maxLength) { bezier() draw(canvas,paint,scaleFactor*1f) radius *= 0.97f currentLenght++ return true }else{ return false } } fun draw(canvas: Canvas, paint: Paint, i: Float) { paint.color = Color.RED canvas.save() canvas.scale(i,i) canvas.translate(currentX,currentY) Log.i(TAG,"radius is $radius") canvas.drawCircle(0f,0f,radius,paint) canvas.restore() } /** * New coordinates calculated by Bessel algorithm */ private fun bezier() { var t = currentLenght*1f / maxLength var c0 = (1 - t) * (1 - t) var c1 = 2 * t * (1 - t) var c2 = t * t currentX = c0 * startPoint.x + c1 * controlPoint.x + c2 * endPoint.x currentY = c0 * startPoint.y + c1 * controlPoint.y + c2 * endPoint.y Log.i(TAG,"x,y -> ($currentX,$currentY)") } } }
With the above foundation, we can draw all kinds of patterns with a little modification, as follows:
// todo missing a tree diagram and code