You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android中如何在ImageView点击位置绘制可重复十字标记?

在ImageView上绘制多次点击的十字标记

嘿,这个需求其实很好实现,核心思路就是记录所有点击坐标,然后在图片上累积绘制十字标记,下面是具体的步骤和代码:

核心思路

  1. 将ImageView的原始图片转为可编辑的Bitmap,确保每次点击都能在上面叠加绘制内容;
  2. 用列表保存所有用户点击的坐标(需要转换为Bitmap对应的实际坐标,因为ImageView可能有缩放);
  3. 每次点击后,重新绘制所有十字标记并更新ImageView显示。

具体实现代码

第一步:初始化关键变量

在你的Activity/Fragment里先定义几个变量:

private lateinit var originalBitmap: Bitmap
private val crossPoints = mutableListOf<Pair<Float, Float>>()
private lateinit var imageviewMap: ImageView

第二步:初始化ImageView与Bitmap

onCreate(Activity)或onViewCreated(Fragment)中完成初始化:

imageviewMap = findViewById(R.id.imageviewMap)
// 注意:如果是网络图片,要等图片加载完成后再执行这一步(比如用Glide的加载回调)
val drawable = imageviewMap.drawable as BitmapDrawable
// 复制一份可编辑的Bitmap,避免修改原始图片
originalBitmap = drawable.bitmap.copy(Bitmap.Config.ARGB_8888, true)

第三步:实现drawCross方法

这个方法会处理坐标转换、保存点击点、绘制所有十字并更新ImageView:

private fun drawCross(touchX: Float, touchY: Float) {
    // 1. 将ImageView的触摸坐标转换为Bitmap的实际坐标
    val scaleX = originalBitmap.width.toFloat() / imageviewMap.width.toFloat()
    val scaleY = originalBitmap.height.toFloat() / imageviewMap.height.toFloat()
    val bitmapX = touchX * scaleX
    val bitmapY = touchY * scaleY

    // 2. 保存当前点击的坐标
    crossPoints.add(Pair(bitmapX, bitmapY))

    // 3. 创建可绘制的画布
    val mutableBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)
    val canvas = Canvas(mutableBitmap)
    val paint = Paint().apply {
        color = Color.RED // 十字标记的颜色
        strokeWidth = 3f // 线条宽度
        isAntiAlias = true // 开启抗锯齿,让线条更平滑
    }

    // 4. 绘制所有保存的十字标记
    crossPoints.forEach { (x, y) ->
        // 横向线条:左右各延伸20dp(可根据需求调整长度)
        canvas.drawLine(x - 20f, y, x + 20f, y, paint)
        // 纵向线条:上下各延伸20dp
        canvas.drawLine(x, y - 20f, x, y + 20f, paint)
    }

    // 5. 更新ImageView显示
    imageviewMap.setImageBitmap(mutableBitmap)
}

第四步:修正触摸监听

只处理ACTION_DOWN事件,避免重复触发绘制:

imageviewMap.setOnTouchListener(object : View.OnTouchListener {
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        if (event?.action == MotionEvent.ACTION_DOWN) {
            event.let { e -> drawCross(e.x, e.y) }
        }
        return true
    }
})

优化方案:自定义ImageView

如果点击次数较多,每次复制Bitmap会有点影响效率,推荐自定义ImageView直接在onDraw里绘制:

class CrossMarkImageView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
    private val crossPoints = mutableListOf<Pair<Float, Float>>()
    private val paint = Paint().apply {
        color = Color.RED
        strokeWidth = 3f
        isAntiAlias = true
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (event?.action == MotionEvent.ACTION_DOWN) {
            // 转换坐标为图片的实际坐标
            val scaleX = drawable.intrinsicWidth.toFloat() / width.toFloat()
            val scaleY = drawable.intrinsicHeight.toFloat() / height.toFloat()
            val x = event.x * scaleX
            val y = event.y * scaleY
            crossPoints.add(Pair(x, y))
            invalidate() // 触发重绘
        }
        return true
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas ?: return
        // 绘制所有十字标记
        crossPoints.forEach { (x, y) ->
            canvas.drawLine(x - 20f, y, x + 20f, y, paint)
            canvas.drawLine(x, y - 20f, x, y + 20f, paint)
        }
    }
}

之后直接在布局里用这个自定义View代替原ImageView即可,效率更高。

注意事项

  • 如果是网络图片,必须等图片加载完成后再初始化Bitmap,可通过图片加载库(如Glide)的回调实现;
  • 十字的长度(20f)可根据需求调整,也可以改成动态值;
  • 若ImageView使用centerCrop缩放类型,坐标转换需要额外计算偏移量,可根据图片和ImageView的尺寸差调整。

内容的提问来源于stack exchange,提问作者A.K.

火山引擎 最新活动