You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin 如何实现自定义View类谷歌地图双指缩放功能

自定义View实现谷歌地图式双指缩放平移方案

核心实现思路

  • 不推荐使用多子View嵌套方案:房间数量较多时,addView加布局计算的性能开销极大,缩放过程容易卡顿。直接在FloorView的onDraw中通过Canvas矩阵统一变换绘制所有房间,和谷歌地图的渲染逻辑一致,性能更高
  • 手势处理直接使用系统原生API:ScaleGestureDetector处理双指捏合缩放,GestureDetector处理单指拖拽平移,交互完全对齐谷歌地图:缩放中心点始终锚定双指中点,缩放过程中内容无偏移
  • 增加缩放范围、平移边界限制,避免缩放过小/过大、拖拽时楼层超出可视区域出现黑边
  • 所有房间、楼层的原始坐标保持固定,手势更新时仅修改画布变换矩阵,不需要逐个调整元素位置,渲染开销极低

完整实现代码

1. 房间绘制实体(替代独立RoomView,降低性能开销)

如果需要保留原有RoomView类结构,只需要把绘制逻辑抽成公开draw方法即可,不需要继承View减少布局层级:

import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF

data class Room(
    val rectF: RectF, // 房间原始坐标,固定存储不需要修改
    val fillColor: Int = Color.CYAN
) {
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL_AND_STROKE
    }

    fun draw(canvas: Canvas) {
        paint.color = fillColor
        canvas.drawRect(rectF, paint)
    }
}

2. 支持缩放平移的FloorView实现

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import kotlin.math.max
import kotlin.math.min

class FloorView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    // 楼层底图画笔
    private val floorPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL_AND_STROKE
        color = Color.RED
    }

    // 画布变换矩阵
    private val drawMatrix = Matrix()
    // 缩放范围限制,可根据业务调整
    private val minScale = 0.2f
    private val maxScale = 5f
    // 当前缩放比例、平移偏移量
    private var currentScale = 1f
    private var transX = 0f
    private var transY = 0f

    // 手势检测器
    private val scaleGestureDetector: ScaleGestureDetector
    private val gestureDetector: GestureDetector

    // 楼层原始尺寸
    private val floorWidth: Float
    private val floorHeight: Float
    private val floorRect: RectF

    // 房间数据集合
    private val roomList = mutableListOf<Room>()

    init {
        val screenHeight = context.resources.displayMetrics.heightPixels.toFloat()
        val screenWidth = context.resources.displayMetrics.widthPixels.toFloat()
        floorWidth = screenWidth
        floorHeight = screenHeight
        floorRect = RectF(0f, 0f, floorWidth, floorHeight)

        // 双指缩放监听
        scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
            override fun onScale(detector: ScaleGestureDetector): Boolean {
                val scaleFactor = detector.scaleFactor
                val preScale = currentScale
                currentScale *= scaleFactor
                // 限制缩放边界
                currentScale = max(minScale, min(currentScale, maxScale))
                val realScaleFactor = currentScale / preScale
                // 锚定双指中心点缩放,和谷歌地图交互一致
                val focusX = detector.focusX
                val focusY = detector.focusY
                transX = focusX - (focusX - transX) * realScaleFactor
                transY = focusY - (focusY - transY) * realScaleFactor
                // 平移边界校准
                checkBorder()
                invalidate()
                return true
            }
        })

        // 单指拖拽监听
        gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
            override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
                transX -= distanceX
                transY -= distanceY
                checkBorder()
                invalidate()
                return true
            }
        })
    }

    // 公开添加房间方法
    fun addRoom(room: Room) {
        roomList.add(room)
        invalidate()
    }

    // 边界校验:避免楼层拖出可视范围出现黑边
    private fun checkBorder() {
        val visibleWidth = width.toFloat()
        val visibleHeight = height.toFloat()
        val scaledFloorWidth = floorWidth * currentScale
        val scaledFloorHeight = floorHeight * currentScale

        // 左右边界限制
        transX = if (scaledFloorWidth <= visibleWidth) {
            (visibleWidth - scaledFloorWidth) / 2f
        } else {
            max(visibleWidth - scaledFloorWidth, min(transX, 0f))
        }
        // 上下边界限制
        transY = if (scaledFloorHeight <= visibleHeight) {
            (visibleHeight - scaledFloorHeight) / 2f
        } else {
            max(visibleHeight - scaledFloorHeight, min(transY, 0f))
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        // 首次加载时居中显示楼层
        transX = (w - floorWidth) / 2f
        transY = (h - floorHeight) / 2f
        checkBorder()
        invalidate()
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        scaleGestureDetector.onTouchEvent(event)
        gestureDetector.onTouchEvent(event)
        return true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.save()
        // 应用变换矩阵
        drawMatrix.reset()
        drawMatrix.postScale(currentScale, currentScale)
        drawMatrix.postTranslate(transX, transY)
        canvas.matrix = drawMatrix
        // 绘制楼层底图
        canvas.drawRect(floorRect, floorPaint)
        // 绘制所有房间,矩阵自动完成坐标变换
        roomList.forEach { it.draw(canvas) }
        canvas.restore()
    }
}

使用方式

在页面中直接给FloorView传入房间数据即可:

val floorView = findViewById<FloorView>(R.id.floorView)
// 传入实际业务的房间坐标
floorView.addRoom(Room(RectF(200f, 200f, 600f, 600f)))
floorView.addRoom(Room(RectF(800f, 300f, 1200f, 700f), Color.GREEN))

扩展说明

  • 如果必须保留RoomView作为子View的结构,只需要在手势更新时遍历所有子View,修改每个子View的translationX、translationY、scaleX、scaleY即可,但房间数量超过50个时会出现明显卡顿,不推荐使用
  • 房间点击交互只需要在onTouchEvent中记录点击坐标,通过矩阵逆运算把屏幕坐标转换为楼层原始坐标,再和房间RectF做包含判断即可,不需要给每个房间单独设置点击监听
  • 双击缩放、惯性滑动等扩展能力只需要给GestureDetector增加对应回调即可,和现有缩放平移逻辑完全兼容

内容的提问来源于stack exchange,提问作者meniman98

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 10:21:23