最近有一个项目需要一个量角的功能,简单说就是能够在屏幕上绘制两条线行成夹角并得到夹角角度,下面来看看具体实现流程:
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
以上这些就是整个绘制过程,希望对你我都有所帮助!