700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Android高级开发进阶之路1——自定义控件(Canvas Paint 贝塞尔曲线)

Android高级开发进阶之路1——自定义控件(Canvas Paint 贝塞尔曲线)

时间:2019-04-17 21:09:33

相关推荐

Android高级开发进阶之路1——自定义控件(Canvas Paint 贝塞尔曲线)

自定义View(Canvas,Paint,贝塞尔曲线)

目录

自定义View(Canvas,Paint,贝塞尔曲线)

在Canvas上绘制圆,利用Bitmap保存起来

在路径上,依次绘制圆,形成画线动画

同时绘制多条路径

在Android应用层来说,众所周知,自定义一个View,需要经过测量,布局,绘制三个步骤。每个步骤深入将都可以写成长篇大论。今天我们主要了解的是onDraw绘制的部分。通过一个TreeView的例子来引入,下面先看看效果图:

//todo 导入一张效果图

内功口诀:点、线、面。

要绘图,需要4个基本组件:

Bitmap 保存像素的容器Canvas 执行绘图命令的宿主Rect/Path/text/Bitmap 要绘制的元素Paint 用什么样的方式绘制

下面我们分成以下几个步骤:

在Canvas上绘制圆,利用Bitmap保存起来(这一步很关键,如果不在bitmap上画,则无法保存)

在路径上绘制圆,保存起来,形成线(形成动画)

同时绘制多条路径

在Canvas上绘制圆,利用Bitmap保存起来

步骤:创建新Canvas(Bitmap)---> 在新Canvas上绘制---> 在View的Canvas上绘制bitmap

创建新Canvas(Bitmap)

/*** 保存所绘制的bitmap*/class BlankBoard {var blankBoardCanvas: Canvasvar blankBoardBitmap: Bitmapconstructor(bitmap: Bitmap){this.blankBoardBitmap = bitmapthis.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))}//...}

在新Canvas上绘制

class DrawViewInBitmap : View {//...override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)//..//1、画一个圆到bitmapdrawCircle2BlankBoardBitmap(200f, 200f, 50f,blankBoard.blankBoardCanvas, paint)}/*** 绘制一个圆到白板上* (圆点x坐标,圆点y坐标,圆半径,画笔)*/private fun drawCircle2BlankBoardBitmap(dx: Float, dy: Float, radius: Float, canvas: Canvas, mPaint: Paint){canvas.save()//保存之前的canvas坐标状态canvas.translate(dx,dy)canvas.drawCircle(0f,0f,radius,mPaint)canvas.restore()//恢复之前的canvas坐标状态}//...}

在View的Canvas上绘制bitmap

class DrawViewInBitmap : View {//...override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)//...//1、画一个圆到bitmapdrawCircle2BlankBoardBitmap(200f, 200f, 50f,blankBoard.blankBoardCanvas, paint)//2、在自定义view的canvas中绘制blankBoard.bitmapcanvas?.drawBitmap(blankBoard.blankBoardBitmap,0f,0f,paint)}//...}

看看效果:

这里只是关键Code,源码我会在后面给到大家。

在路径上,依次绘制圆,形成画线动画

效果:

通过修改上面的文件,每次绘制完一圆之后,y坐标+10,圆半径大小为原来97%,调用invalidate()重绘布局,即可。

下面是部分关键代码

class DrawViewInBitmap2 : View {//当前半径private var currentRadius: Float = 50f//当前圆点y坐标private var currentY: Float = 200f//...override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)//...//1、画一个圆到bitmapdrawCircle2BlankBoardBitmap(200f, currentY, currentRadius,blankBoard.blankBoardCanvas, paint)//2、在自定义view的canvas中绘制blankBoard.bitmapcanvas?.drawBitmap(blankBoard.blankBoardBitmap,0f,0f,paint)//步增10currentY +=10currentRadius*=0.97fif (currentY < 700) {//重绘布局,重新调用onDraw()invalidate()}/*这里要注意点是:同一个Bitmap可以绘制在不同的Canvas上,一开始Bitmap在blankBoard.blankBoardCanvas上绘制,虽然是绘制成功了但是我们要展示给用户看,必须要画在View.onDraw方法中得到的canvas上。blankBoard.blankBoardBitmap在Canvas*/}//...}

同时绘制多条路径

class TreeView: View {private val paint: Paint = Paint()private var hasEnd: Boolean = falseprivate var linkedListBranch: LinkedList<Branch> = LinkedList()private lateinit var snapShot: SnapShotconstructor(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]!!.parentIdif (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>? = nullvar 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()//解决无法添加树枝的if (nextRankBranch != null) {linkedListBranch.addAll(nextRankBranch)}if (linkedListBranch.isEmpty()) {hasEnd = true}}}/*** 整棵树是由很多点绘制而成,每次绘制需要保存上一次的记录,所以不能放到TreeView的onDraw中* 图像绘制类,保存在bitmap中*/class SnapShot {constructor(createBitmap: Bitmap?){this.bitmap = createBitmapthis.canvas = Canvas(bitmap)/*canvas = Canvas()var p = Paint()p.color = Color.REDcanvas.drawBitmap(createBitmap,0f,0f,p)*/}var bitmap: Bitmap?var canvas: Canvas}/*** 树干类*/class Branch {private val TAG: String? = "Branch"private var currentX: Float = 0.0fprivate var currentY: Float = 0.0fprivate var currentLenght: Int = 0public lateinit var childBranch: LinkedList<Branch>private var maxLength: Int = 0private var radius: Float = 0fvar parentId: Int = 0private var id: Int = 0private lateinit var endPoint: Pointprivate lateinit var controlPoint: Pointprivate lateinit var startPoint: Pointconstructor(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.0fthis.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.97fcurrentLenght++return true}else{return false}}fun draw(canvas: Canvas, paint: Paint, i: Float) {paint.color = Color.REDcanvas.save()canvas.scale(i,i)canvas.translate(currentX,currentY)Log.i(TAG,"radius is $radius")canvas.drawCircle(0f,0f,radius,paint)canvas.restore()}/*** 贝塞尔算法计算新的坐标*/private fun bezier() {var t = currentLenght*1f / maxLengthvar c0 = (1 - t) * (1 - t)var c1 = 2 * t * (1 - t)var c2 = t * tcurrentX = c0 * startPoint.x + c1 * controlPoint.x + c2 * endPoint.xcurrentY = c0 * startPoint.y + c1 * controlPoint.y + c2 * endPoint.yLog.i(TAG,"x,y -> ($currentX,$currentY)")}}}

有了上面的基础,稍作修改就可以画好各种图案了,如下:

//todo 缺一张树图和代码

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。