一、 任意区域可点击
的折线图
到这里我们也许在炫酷方面已经无从下手?对于很多的自定义View是否能够操作每一个文字
或者想要点击的区域
。然后去搞一些你敢想搞不了的事呢?例如我点击上面的文字。来个泡泡飞上去?哈哈接下来我们试着玩一玩了?
...下图可以看到的却我点击每个文字框被正确的计算到位置了,那到底如何来精确的计算想要点击的位置呢?
1.画布区域点击事件
- 接下来我在自定义的View中点击左上角,在onTouch里面打印一下坐标
(event.x,event.y)
:
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
Log.e("onTouchEvent", "onTouchEvent:x"+event.x )
Log.e("onTouchEvent", "onTouchEvent:y"+event.y )
return super.onTouchEvent(event)
}
}
//点击左上角结果:
E/onTouchEvent: onTouchEvent:x0.0
E/onTouchEvent: onTouchEvent:y8.996094
E/onTouchEvent: onTouchEvent:x0.0
E/onTouchEvent: onTouchEvent:y1.9501953
//点击坐下角
E/onTouchEvent: onTouchEvent:x0.0
E/onTouchEvent: onTouchEvent:y730.3877
//点击右上角
E/onTouchEvent: onTouchEvent:y23.08789
E/onTouchEvent: onTouchEvent:x1027.2656
E/onTouchEvent: onTouchEvent:y23.08789
E/onTouchEvent: onTouchEvent:x1027.2656
E/onTouchEvent: onTouchEvent:y23.08789
//点击右下角
E/onTouchEvent: onTouchEvent:x1027.2656
E/onTouchEvent: onTouchEvent:y723.3418
E/onTouchEvent: onTouchEvent:x1027.2656
E/onTouchEvent: onTouchEvent:y723.3418
点击event.x,和event.y并不和我们的转变之后的坐标系一样。我们只是对canvas的坐标系进行来变幻。但是View所在的屏幕点击事件坐标系并没有变换。
同一平面
的任意两个坐标系
都可以通过,旋转
,缩放
,平移
进行变换进行重合。也就意味着我们的点击事件坐标系和我们canvas画布坐标系不管如何变换都可以进行(event.x,envent.y)和(canvas.x,canvas.y)之间的一一映射
。而且两个坐标系都以像素
为单位。防止写不明白,上图绘制了两个重合的坐标系,红色为点击事件坐标系,黑色为canvas坐标系。由此可以推导出映射关系:
1.我们点击之后在点击事件坐标系中拿到的是(event.x,event.y)= (measureWidth,measureHeight)
2.canvas坐标系里面我们对应的是(canvas.x,canvas.y)=(measureWidth,0)
3.可以得出(canvas.x,canvas.y)= (event.x,measureWidth-event.y)
4.因为我们的canvas坐标系设置了margin这里我们得到(canvas.x,canvas.y)= (event.x-marginXAndY,measureHeight-event.y-marginXAndY)
到这里我们简单的拿到了两个坐标直接映射的关系:
(canvas.x,canvas.y)= (event.x-marginXAndY,measureHeight-event.y-marginXAndY)
在这里我们坐标映射原理上没啥问题了。至于有没有误差或正确与否如何验证呢?对于我们绘制的文字也是通过Paint
,在绘制中并没有提供Paint.setOnclick字样的点击事件。这就显得很被动,但是Rect提供了contais函数,用来判断点是否在某一个矩形区域内部。
当然了API还是需要常看和动手的。如何在一个平面坐标系内部判断一个点在某一个区域内呢?如图下图派下面代码就不多讲了吧。
x >= left && x < right && y >= top && y < bottom
这里给了我们很大的突破口。我们记得上面布局绘制了文字,而且绘制了文字的背景通过Rect。每一个定点对应一个文字和对应的文字Rect背景。所以在绘制文字背景时候我们将Rect存储起来
。当然了你觉得canvas中那些是你想进行点击事件操作的那么可以去通过Rect去判断。
- 存储Rect
rctArrayList.add(Rect(pointList[index].x.toInt(), pointList[index].y.toInt(), (pointList[index].x+titleWidth).toInt(), (pointList[index].y+getTextHeight(text_paint)).toInt()))
对于点击事件的拦截我们在onTachEvent进行操作,不清除的看另一片点击事件关的文章。
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
clickDow=true
return true
}
if (event.action == MotionEvent.ACTION_UP&&clickDow) {
for (index in 0 until rctArrayList.size){
//转换坐标为判断是否在文字背景框所在区域内部
val contais=rctArrayList[index].contains((event.x.toInt()-marginXAndY).toInt(), (measuredHeight-marginXAndY-event.y.toInt()).toInt())
if (contais){
ToastUtils.showLong("点击文字=$index")
break
}
}
clickDow=false
}
return super.onTouchEvent(event)
}
很精准有没有。探究到这里还有什么不能做的事呢?每次点击修改canvas右上角的内容,基本操作.
这里我们也能精确的定位点击事件,这样好的交互才能被创造
1.区域点击带来的精彩
Echars里面都有个特效我们能不能实现一下呢?
这里我们先实现一下上面效果"点击部分弹出一个框框显示内容
"。对于曲线什么动画都是基操,别说曲线了,学会了贝塞尔曲线基本的原理。我们刀,人,建筑......只要能看见的万物,讲道理都可以画出来。下图来个案例,稳住军心。
通过上面得点击事件和canvas坐标系得转换映射我们已经很精确定位到画布中位置,接下来代码操作。
1.全局定义一个Rect
2.点击事件里面,创建初始化一个动态🉐️Rect
3.设置定时器来设置显示消失时间等操作。
- 第一步简单,第二步我们创建图中点击圆圈下面的Rect即可,对于内容你看数据本身了。咋们就来个小黑框。内容咋就简单点,画个框框又得费好几分钟。Rect我们别学我写死哦。既然上面我们学会了字体测量那么你们就严格点,这里我直接写死了。
上面推导公式别忘记了:
(canvas.x,canvas.y)= (event.x-marginXAndY,measureHeight-event.y-marginXAndY)
var x=event.x-marginXAndY
var y=measureHeight-event.y-marginXAndY
明确坐标系的正负方向很重要
那么我们巨型在下面图2 :
Rect(left,top,right,bottom)=Rect(x-100,y,x+100,y-200)
- 步骤一,全局新建变量,在点击事件里面初始化。
//全局...当然拿到坐标也可以。都行咋么方便咋么来。
var blackRect: Rect? = null
//用来判断显示黑框不
var visible = false
//2.点击时间初始化Rect
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
clickDow = true
return true
}
if (event.action == MotionEvent.ACTION_UP && clickDow) {
for (index in 0 until rctArrayList.size) {
//转换坐标为
val contais = rctArrayList[index].contains((event.x.toInt() - marginXAndY).toInt(), (measuredHeight - marginXAndY - event.y.toInt()).toInt())
if (contais) {
ToastUtils.showLong("点击文字=$index")
rightTopSubject = titleList[index]
//初始化Rect
val x = event.x - marginXAndY
val y = measuredHeight - event.y - marginXAndY
//明确坐标系的正负方向很重要
blackRect = Rect((x - 70).toInt(), y.toInt(), (x + 70).toInt(), (y - 200).toInt())
//可显示
visible=true
invalidate()
//当然动画什么的都可以去刷新...这里比较简单的搞一搞效果而已
postDelayed({
visible=false
invalidate()
}, 2000)
break
}
}
clickDow = false
}
return super.onTouchEvent(event)
}
//3.绘制弹出框部分代码
private fun drawWindowRect(canvas: Canvas) {
if (blackRect != null && visible) {
val rrPaint = Paint()
rrPaint.color = Color.BLACK
rrPaint.style = Paint.Style.FILL
rrPaint.strokeWidth = 1f
rrPaint.setShadowLayer(5f, -5f, -5f, Color.argb(50, 111, 111, 111))
//这里搞个圆角吧...避免太丑
canvas.drawRoundRect(blackRect!!.left.toFloat(), blackRect!!.top.toFloat(), blackRect!!.right.toFloat(), blackRect!!.bottom.toFloat(), 10f, 10f, rrPaint)
canvas.save()
canvas.translate(blackRect!!.left.toFloat(), blackRect!!.top.toFloat())
canvas.scale(1f, -1f)
val ttPaint = Paint()
ttPaint.color = Color.WHITE
ttPaint.style = Paint.Style.FILL
ttPaint.strokeWidth = 1f
ttPaint.strokeCap = Paint.Cap.ROUND
ttPaint.textSize = 24f
//0f,0f表示圆点,这样看着可能好理解,20f,30f是我不打算测量文字直接写死的偏移。。
canvas.drawText("M:${rightTopSubject}", 0f + 20f, 0f + 30f, ttPaint)
}
}
看效果如下:样子就这样效果有了,至于弹出避免重复出现等你们可以优化...