-
需求
最近有一个需求是做类似百度地图那样子的点击图片的某个地方然后有相应的响应。
其实这种需求的话如果是3D的话,android如果最好的话可以使用游戏框架,或者前端的一些three.js等一些有元素绑定的框架来做那么就简单好多了,因为你所点击的某个地方是一个元素,它可以附带各种事件。
2D的话或者可以用svg图 然后解析对应的节点给每个节点做一些点击事件的处理,很多svg地图也是这样子做的,有兴趣大家可以百度一下。
这里呢,用了安卓源生最最最简单 不过也是最不可取的方法啦,不过自己没做事写了一下类似的东西,就记录一下吧~~~
效果图先看为敬.gif
大家看一下这种效果,就是加载一张图片,并且图片上方画上图形,然后给个上面画上一些圆点,然后点击做相应的操作。
下面说一下这个实现的过程吧,以及需要注意的一些点~~
- 先用画笔在
ImageView
上面画出对应的圆心,确定可点击的范围。 - 给个开关,开启的时候,圆点可以移动,关闭的时候圆点不可移动。
注意点 - 如果圆点被移除布局外,要重新在布局中画出来这个点,不然这个点就不见了。
- 要保持一时间只有一个点的响应点击移动事件,不然两个点或多个点一旦重合在一起就出现移动不出来,看起来是一个点实际上是两个点或者多个点。
相关的知识点无非就是onDraw()方法的先后绘画顺序,已经点击事件的一点分发知识点。或许还有一些绘画的paint、点击事件的坐标跟屏幕的坐标~ (很基础的)
- 为小圆点定义的实体类
open class BasePointBean : Serializable {
var id: Int = 0 //点的id
var name: String? = null //点的名字
var isTouch: Boolean = false //是否被点击
//圆心
var x: Double = 0.0
var y: Double = 0.0
}
/**
* Create by ldr
* on 2019/10/29 14:01.
* 用于存储图片上的点
*/
class OperationRoomBean : BasePointBean() {
//可点击的范围
var region: Region? = null
get() = Region((x - 40).toInt(), (y - 40).toInt(), (x + 40).toInt(), (y + 40).toInt())
//圆点的外围画笔
var paintOut: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
//圆心的内围画笔
var paintIn: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
init {
paintOut.color = Color.parseColor("#FFFFFF")
paintIn.color = Color.parseColor("#A5DCE9")
}
fun setIsTouch(select: Boolean) {
isTouch = select
if (select) {
paintOut.color = Color.parseColor("#DDD0D0")
paintIn.color = Color.parseColor("#5AD5E4")
} else {
paintOut.color = Color.parseColor("#FFFFFF")
paintIn.color = Color.parseColor("#A5DCE9")
}
}
companion object {
val TAG = "OperationRoomBean"
}
}
- 自定义
CustomPic
继承ImageView
重写一下绘画相关的方法跟点击事件相应的方法
class CustomPic : ImageView {
companion object {
val TAG = "CustomPic"
}
//switch控件开关值,可不可以移动
var isChangLocal: Boolean = false
//获取该布局的矩形,作用:用于判断圆点是不是在布局中,不在要重新绘制
var globalLocalRect = Rect()
//在某一些坐标上面画点
var arrayOperationRoomBean = ArrayList<OperationRoomBean>()
constructor(context: Context) : super(context) {
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
}
//传入点的时候 调用重新刷一下页面
fun setData(arrayOperationRoomBean: ArrayList<OperationRoomBean>){
this.arrayOperationRoomBean = arrayOperationRoomBean
invalidate()
}
fun getData():ArrayList<OperationRoomBean>{
return arrayOperationRoomBean
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
}
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
// 获取该Pic的可见范围
this.getLocalVisibleRect(globalLocalRect)
for (oper in arrayOperationRoomBean) {
drawCirCle(oper, canvas!!,globalLocalRect)
}
}
/**
* 画圆点 用于显示出要点击的点
* @param operationRoomBean 圆点的数据
* @param canvas 当前的画布
* @param rect 当前的这个CustomPic的可视范围 相对于
*
* 三点步骤:
* 第一点 先判断 operationRoomBean圆点的数据的x y ,如果不在getLocalVisibleRect的范围内就改变x,y,使其在getLocalVisibleRect的范围内
* 第二点 根据点画改点的可以点击范围
* 第三点 开始画圆的外圈跟圆的内圈
* 有关键一点需要注意: drawCircle的x跟y相对于当前父控件的canvas
*/
fun drawCirCle(operationRoomBean: OperationRoomBean, canvas: Canvas,rect: Rect) {
operationRoomBean.let {
//这里是防止被拉到不可见区域的时候 导致数据不可见
if (!rect.contains((it.x).toInt(), it.y.toInt())) {
it.x = 40.0
it.y = 40.0
it.region = Region(
(it.x - 40).toInt(),
(it.y - 40).toInt(),
(it.x + 40).toInt(),
(it.y + 40).toInt()
)
}
canvas?.drawCircle(it.x.toFloat(), it.y.toFloat(), 20f, it.paintOut)
canvas?.drawCircle(it.x.toFloat(), it.y.toFloat(), 10f, it.paintIn)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
var onOneBeSelect = true //这个节点是为了防止两个多个点在附近会同时被点击进而变成一个点
when (event!!.action) {
MotionEvent.ACTION_DOWN -> {
Log.i(TAG, "MotionEvent.ACTION_DOWN")
for (operationRoomBean in arrayOperationRoomBean) {
//没有点击到它 则将它的属性设置为没有被点击
if (!operationRoomBean.region!!.contains(event.x.toInt(), event.y.toInt())){
operationRoomBean.setIsTouch(false)
continue
}
//已经有其他点被选中了 则当前的属性也给改成没有被点击
if(!onOneBeSelect){
operationRoomBean.setIsTouch(false)
continue
}
onOneBeSelect = false
operationRoomBean.setIsTouch(true)
if (isChangLocal) {//这个节点判断是不是用于移动
Toast.makeText(context, "当前点击${operationRoomBean.name}", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "当前点击${operationRoomBean.name},不可移动", Toast.LENGTH_SHORT).show()
}
}
invalidate()
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG, "MotionEvent.ACTION_MOVE")
if (isChangLocal) {
for (operationRoomBean in arrayOperationRoomBean) {
if (!operationRoomBean.region!!.contains(event.x.toInt(), event.y.toInt())){
continue
}
if (!operationRoomBean.isTouch){
continue
}
// 将移动的坐标存入给圆点的实体operationRoomBean,并设置可点击区域 最后刷新页面
operationRoomBean.apply {
Log.i(TAG, "MotionEvent.ACTION_MOVE")
x = event.x.toDouble()
y = event.y.toDouble()
region = Region(
(x - 40).toInt(),
(y - 40).toInt(),
(x + 40).toInt(),
(y + 40).toInt()
)
invalidate()
}
}
}
}
MotionEvent.ACTION_UP -> {
Log.i(TAG, "MotionEvent.ACTION_UP")
onOneBeSelect = true
}
}
return true
}
//切换横竖屏操作
fun isScreenOriatationLandscape(context: Context): Boolean {
return context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
}
}
主要是上面这CustomPic
做一些点击事件的一些处理,再调用invalidate()
去刷新页面。(必要的注释都在上面的类中了)
ps:这个方案不适合多种机型,只适合在单独的机型上面或者在调试前可以先配置信息的机型上面,实际使用的时候不让用户移动点 直接固定在那里。
你可以将点的信息存储到本地,那么下次打开软件加载点的信息就可以直接用了,上面提供的类可以直接copy到本地使用,但是传入的数据要自己构造一下传入。
网友评论