Road to Advanced Android Development 1 - Custom Controls (Canvas, Paint, Bessel Curve)

Keywords: snapshot Android

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:

  1. 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)

  2. Draw a circle on the path, save it and form a line (animation)

  3. 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

 

Posted by e_00_3 on Thu, 29 Aug 2019 22:59:07 -0700