android使用canvas绘制量角器(kotlin)

最近有一个项目需要一个量角的功能,简单说就是能够在屏幕上绘制两条线行成夹角并得到夹角角度,下面来看看具体实现流程:

1、在layout布局中放置一个ImageView(iv_canvas)

2、首先定义默认画笔paint,画笔的几个属性一看就知道是什么意思,这里就不在讲解了:

paint = Paint()
paint!!.strokeWidth = 16f
paint!!.color = Color.RED
paint!!.style = Paint.Style.STROKE

3、为ImageView设置触摸监听事件:

iv_canvas!!.setOnTouchListener(touch)

private val touch = object : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean {
         when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                 
            }
            MotionEvent.ACTION_MOVE -> {
                
            }
            MotionEvent.ACTION_UP -> {
                
            }
        return true
    }
}

4、绘制思路:

(1)当手指按下的时候,将按下那一刻的点记为A点坐标;当手指以直线移动的时候,将移动到的点记为B点坐标;当手指移动不在AB这条线上的时候,固定上一个坐标点为B,将移动的点记为C点;当手指抬起保持不变;

(2)有了三个点,开始绘制线,以B为顶点,绘制BA和BC两条线,根据ABC三点坐标形成的三角形计算出B点角度(根据数学三角函数进行计算),计算公式如下:

/**
  * 计算角度
  *
  * @param ax
  * @param ay
  * @param bx
  * @param by
  * @param cx
  * @param cy
  * @return
  */
private fun countAngel(ax: Float, ay: Float, bx: Float, by: Float, cx: Float, cy:Float):Int
{
    val cL2 = (ax - bx) * (ax - bx) + (ay - by) * (ay - by)
    val bL2 = (ax - cx) * (ax - cx) + (ay - cy) * (ay - cy)
    val aL2 = (bx - cx) * (bx - cx) + (by - cy) * (by - cy)
    val cosB = (aL2 + cL2 - bL2) /(2.0*Math.sqrt(cL2.toDouble())*Math.sqrt(aL2.toDouble()))
    return Math.round(Math.toDegrees(Math.acos(cosB))).toInt()
}

(3)接下来绘制弧线,由于Android的坐标系是从左上角开始的,向右为x轴,向下为y轴,而弧线则是以水平方向向右为起点,因此在绘制角度弧线的时候需要先进行偏移角度的计算,只有计算好偏移角度才能将角度的起点设置到AB或者CB线上,这样才能让弧线绘制到两条线之间,计算思路其实很简单,就是以B点为原点,向右为正半轴,顺时针距离最近的那条线(AB或者BC)与x轴正半轴形成的角度,就是我们需要的偏移角度,用到的数学知识有(计算直线的斜率和反三角函数),计算公式如下:

/**
     * 计算偏移角度
     *
     * @param ax
     * @param ay
     * @param bx
     * @param by
     * @param cx
     * @param cy
     * @return
     */
    private fun drawAngle(ax: Float, ay: Float, bx: Float, by: Float, cx: Float, cy: Float): Int {
        var startAngle: Int = 0
        val k1 = (ay - by) / (ax - bx)
        val k2 = (cy - by) / (cx - bx)
        Log.e(TAG, "drawAngle: $k1~~~~$k2")
        if (k1 < 0 && k2 < 0) {
            if (k1 > k2) {
                if (ay < by && cy < by) {
                    startAngle = countOffset(cx, cy, bx, by, 360)
                } else if (ay > by && cy > by) {
                    startAngle = countOffset(cx, cy, bx, by, 180)
                } else if (cy > by && by > ay) {
                    startAngle = countOffset(ax, ay, bx, by, 1)
                } else if (cy < by && ay > by) {
                    startAngle = countOffset(ax, ay, bx, by, 180)
                }
            } else {
                if (ay < by && cy < by) {
                    startAngle = countOffset(ax, ay, bx, by, 360)
                } else if (ay > by && cy > by) {
                    startAngle = countOffset(ax, ay, bx, by, 180)
                } else if (cy > by && by > ay) {
                    startAngle = countOffset(cx, cy, bx, by, 180)
                } else if (cy < by && ay > by) {
                    startAngle = countOffset(cx, cy, bx, by, 1)
                }
            }
        } else if (k1 > 0 && k2 > 0) {
            if (k1 > k2) {
                if (ay < by && cy < by) {
                    startAngle = countOffset(cx, cy, bx, by, 180)
                } else if (ay < by && cy > by) {
                    startAngle = countOffset(ax, ay, bx, by, 180)
                } else if (cy < by && ay > by) {
                    startAngle = countOffset(ax, ay, bx, by, 1)
                } else if (cy > by && cy > by) {
                    startAngle = countOffset(cx, cy, bx, by, 1)
                }
            } else {
                if (ay < by && cy < by) {
                    startAngle = countOffset(ax, ay, bx, by, 180)
                } else if (ay < by && cy > by) {
                    startAngle = countOffset(cx, cy, bx, by, 1)
                } else if (cy < by && ay > by) {
                    startAngle = countOffset(cx, cy, bx, by, 180)
                } else if (cy > by && cy > by) {
                    startAngle = countOffset(ax, ay, bx, by, 1)
                }
            }
        } else if (k1 > 0 && k2 < 0) {
            if (ay > by && by > cy) {
                startAngle = countOffset(cx, cy, bx, by, 1)
            } else if (ay < by && cy > by) {
                startAngle = countOffset(cx, cy, bx, by, 180)
            } else if (ay > by && cy > by) {
                startAngle = countOffset(ax, ay, bx, by, 1)
            } else if (by > ay && by > cy) {
                startAngle = countOffset(ax, ay, bx, by, 180)
            }
        } else if (k1 < 0 && k2 > 0) {
            if (ay > by && by > cy) {
                startAngle = countOffset(ax, ay, bx, by, 180)
            } else if (ay < by && cy > by) {
                startAngle = countOffset(ax, ay, bx, by, 1)
            } else if (ay > by && cy > by) {
                startAngle = countOffset(cx, cy, bx, by, 1)
            } else if (by > ay && by > cy) {
                startAngle = countOffset(cx, cy, bx, by, 180)
            }

        }
        return startAngle
    }


private fun countOffset(x: Float, y: Float, Bx: Float, By: Float, flag: Int): Int {
        val tanL = ((y - By) / (x - Bx)).toDouble()
        var angel = Math.round(Math.toDegrees(Math.atan(tanL))).toInt()
        Log.e(TAG, "drawAngle: $angel")
        if (flag == 180) {
            angel += 180
        } else if (flag == -180) {
            angel = 180 - angel
        } else if (flag == -360) {
            angel = 360 - angel
        } else if (flag == -1) {
            angel = -angel
        } else if (flag == 360) {
            angel += 360
        }
        Log.e(TAG, "drawAngle: $angel")
        return angel
    }

(4)然后在弧线上绘制角度文字即可,文字是在弧线正上方,所以也有偏移角度,这里的偏移角是以AB或者BC为起点进行偏移的,因此偏移角只要相对线偏移B角度除以2再减去差不多一半的文字角度即可。

(5)简单的绘制,到上一步就可以了,但是,当我们再次按下手指 并移动的时候,你有没有想过还需要什么效果?那就是当手指按下的时候,我们要获取到距离手指最近的点(A/B/C)并进行移动改变绘制的线和角度。

(6)最后,将绘制的放到ImageView中即可。以上的这些就是绘制的基本思路。

5、具体绘制:

(1)MotionEvent.ACTION_DOWN

if (isFirst) {
                        if (baseBitmap == null) {
                            baseBitmap = Bitmap.createBitmap(iv_canvas!!.width,
                                    iv_canvas!!.height, Bitmap.Config.ARGB_8888)
                            canvas = Canvas(baseBitmap!!)
                        }

                        startX = event.x
                        startY = event.y
                        Ax = event.x
                        Ay = event.y
                        Bx = event.x
                        By = event.y
                    } else {
                        val x = event.x
                        val y = event.y

                        val aL = (x - Ax) * (x - Ax) + (y - Ay) * (y - Ay)
                        val bL = (x - Bx) * (x - Bx) + (y - By) * (y - By)
                        val cL = (x - Cx) * (x - Cx) + (y - Cy) * (y - Cy)
                        var minL = if (aL > bL) bL else aL
                        minL = if (minL > cL) cL else minL
                        if (minL == cL) {
                            Cx = x
                            Cy = y
                            minFlag = 3
                        } else if (minL == bL) {
                            Bx = x
                            By = y
                            minFlag = 2
                        } else if (minL == aL) {
                            Ax = x
                            Ay = y
                            minFlag = 1
                        }
                    }

(2)MotionEvent.ACTION_MOVE

val stopX = event.x
                    val stopY = event.y
                    if (isFirst) {
                        if (stopY - startY != 0f && stopY - Ay != 0f) {
                            if (Math.round((stopX - startX) / (stopY - startY)) == Math.round((stopX - Ax) / (stopY - Ay))) {
                                Bx = stopX
                                By = stopY
                            } else {
                                Cx = stopX
                                Cy = stopY
                            }
                        }

                        startX = event.x
                        startY = event.y

                    } else {
                        if (minFlag == 1) {
                            Ax = stopX
                            Ay = stopY
                        } else if (minFlag == 2) {
                            Bx = stopX
                            By = stopY
                        } else if (minFlag == 3) {
                            Cx = stopX
                            Cy = stopY
                        }

                    }
                    // 绘制夹角线
                    canvas!!.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
                    paint!!.strokeWidth = 16f
                    paint!!.color = resources.getColor(R.color.theme)
                    paint!!.pathEffect = null
                    canvas!!.drawLine(Ax, Ay, Bx, By, paint!!)
                    canvas!!.drawLine(Bx, By, Cx, Cy, paint!!)

                    // 绘制夹角弧线
                    paint!!.strokeWidth = 8f
                    paint!!.pathEffect = DashPathEffect(floatArrayOf(8f, 8f), 0f)
                    mAngel = countAngel(Ax, Ay, Bx, By, Cx, Cy)
                    val offset = drawAngle(Ax, Ay, Bx, By, Cx, Cy).toFloat()
                    val offsetPx = 200f
                    val oval = RectF(0f, 0f, offsetPx, offsetPx)
                    oval.offset(Bx - offsetPx/2, By - offsetPx/2)
                    canvas!!.drawArc(oval, offset, mAngel.toFloat(), false, paint!!)

                    // 绘制弧线上角度文字
                    paint!!.textSize = 30f
                    paint!!.color = resources.getColor(R.color.bn_red_dark)
                    paint!!.pathEffect = DashPathEffect(floatArrayOf(8f, 0f), 0f)
                    paint!!.strokeWidth = 3f
                    val path = Path()
                    path.addArc(oval, offset + mAngel.toFloat() / 2-10, 50f)
                    path.addOval(oval, Path.Direction.CW);
                    canvas!!.drawTextOnPath(mAngel.toString() + "°", path, 0f, -50f, paint)

                    iv_canvas!!.setImageBitmap(baseBitmap)

(3)MotionEvent.ACTION_UP(可不写)

val x = event.x
                    val y = event.y
                    if (minFlag == 1) {
                        Ax = x
                        Ay = y
                    } else if (minFlag == 2) {
                        Bx = x
                        By = y
                    } else if (minFlag == 3) {
                        Cx = x
                        Cy = y
                    }
                    isFirst = false

以上这些就是整个绘制过程,希望对你我都有所帮助!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值