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

如何通过scale gesture在Canvas上对Bitmap进行水平或垂直非等比缩放?

实现Canvas上Bitmap的单独水平/垂直非等比缩放

嘿,我完全懂你要的是什么——就是那种能单独拉宽或者拉高图片的非等比缩放,而不是网上大多数示例里那种死死保持宽高比的缩放对吧?这个需求确实有点小众,我来给你唠唠怎么用ScaleGestureDetector在Canvas上实现:

核心思路

默认的ScaleGestureDetector是基于两点间的总距离计算缩放比例,自然会保持宽高比。我们要做的就是拆分x和y方向的手势跨度,分别计算水平和垂直的缩放因子,而不是用同一个全局的scale值。

具体实现步骤

1. 定义必要的变量

首先在自定义View里声明需要的变量,包括Bitmap对象、水平/垂直缩放因子、缩放偏移量(防止缩放时图片跳位),还有手势检测器:

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View

class NonUniformScaleView(context: Context) : View(context) {
    private lateinit var scaleGestureDetector: ScaleGestureDetector
    private var bitmap: Bitmap? = null
    // 水平和垂直方向的独立缩放因子,初始值为1(原始大小)
    private var scaleX = 1f
    private var scaleY = 1f
    // 记录缩放中心的偏移,让缩放围绕触摸中心进行
    private var offsetX = 0f
    private var offsetY = 0f

2. 初始化手势检测器和Bitmap

在View的初始化块里,创建自定义的手势监听器,同时加载你要缩放的Bitmap:

init {
        scaleGestureDetector = ScaleGestureDetector(context, CustomScaleListener())
        // 替换成你的图片资源
        bitmap = BitmapFactory.decodeResource(resources, R.drawable.your_image)
    }

3. 自定义手势监听器(关键!)

这里是实现非等比缩放的核心:在onScaleBegin记录手势开始时x、y方向的跨度,在onScale里分别计算两个方向的缩放比例,更新对应的缩放因子:

private inner class CustomScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        private var initialSpanX = 0f
        private var initialSpanY = 0f

        override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
            // 记录手势启动时,两个触摸点在x和y方向的距离
            initialSpanX = detector.currentSpanX
            initialSpanY = detector.currentSpanY
            return true // 返回true表示处理这个手势
        }

        override fun onScale(detector: ScaleGestureDetector): Boolean {
            // 计算x和y方向各自的缩放比例
            val scaleFactorX = detector.currentSpanX / initialSpanX
            val scaleFactorY = detector.currentSpanY / initialSpanY

            // 更新水平和垂直缩放因子
            scaleX *= scaleFactorX
            scaleY *= scaleFactorY

            // 计算偏移量,让缩放围绕触摸中心进行,避免图片跳走
            val focusX = detector.focusX
            val focusY = detector.focusY
            offsetX = focusX - scaleX * focusX
            offsetY = focusY - scaleY * focusY

            // 刷新视图,触发重绘
            invalidate()
            return true
        }
    }

4. 处理触摸事件

把View的触摸事件交给手势检测器处理:

override fun onTouchEvent(event: MotionEvent): Boolean {
        // 让手势检测器处理触摸事件
        scaleGestureDetector.onTouchEvent(event)
        return true // 消费触摸事件,避免传递给父View
    }

5. 在Canvas上绘制缩放后的Bitmap

最后在onDraw方法里,应用缩放因子和偏移量绘制Bitmap:

override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        bitmap?.let { bmp ->
            canvas.save() // 保存画布状态
            // 先平移到偏移位置,再应用缩放
            canvas.translate(offsetX, offsetY)
            canvas.scale(scaleX, scaleY)
            // 绘制原始Bitmap
            canvas.drawBitmap(bmp, 0f, 0f, null)
            canvas.restore() // 恢复画布状态
        }
    }
}

额外优化建议

  • 限制缩放范围:可以给scaleX和scaleY加个上下限,防止缩得太小或太大,比如:
    scaleX = scaleX.coerceIn(0.1f, 5f)
    scaleY = scaleY.coerceIn(0.1f, 5f)
    
  • 支持拖动:如果需要同时拖动图片,可以在onTouchEvent里处理ACTION_MOVE事件,更新offsetX和offsetY。
  • 内存优化:如果是大图片,记得先压缩再加载,避免OOM。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:18:50