如何通过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
相关产品推荐
相关产品推荐

