Android自定义-任意区域可点击的折线图

1,954 阅读4分钟

一、 任意区域可点击的折线图

到这里我们也许在炫酷方面已经无从下手?对于很多的自定义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,03.可以得出(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)
        }

    }
   

看效果如下:样子就这样效果有了,至于弹出避免重复出现等你们可以优化...