手势缩放/甩动后,ImageView触摸点转原始Bitmap像素的实现方法
嘿,这个问题我刚好处理过不少次!要把ImageView上的触摸坐标映射回原始Bitmap的像素位置,核心就是逆推ImageView对Bitmap做的变换——毕竟缩放、平移这些操作都是把Bitmap画到ImageView上的变换,反过来算就行。
核心思路
ImageView显示Bitmap时,会经过两类变换:一是系统scaleType的适配变换(比如centerCrop、fitXY这类),二是你手动加的手势缩放/平移变换。我们需要把触摸点的屏幕坐标,先反向抵消手势的变换,再反向抵消系统的适配变换,最终得到原始Bitmap的像素坐标。
具体实现步骤
我整理了一套可复用的方案,直接拿去用就行:
1. 获取ImageView的总变换矩阵
首先要拿到当前ImageView的总变换矩阵:
- 如果是你自己用
Matrix维护手势变换(比如自定义缩放平移),那总矩阵就是你维护的那个Matrix对象; - 如果用系统默认的scaleType,直接调用
imageView.getImageMatrix()就能拿到当前的显示矩阵。
2. 计算逆矩阵
因为要反向映射坐标,所以需要计算总变换矩阵的逆矩阵——这个矩阵能把屏幕触摸点转成原始Bitmap的坐标:
Matrix inverseMatrix = new Matrix(); // 尝试计算逆矩阵,注意矩阵可能不可逆(比如缩放为0时) if (yourTotalMatrix.invert(inverseMatrix)) { // 逆矩阵计算成功,继续下一步 } else { // 做容错处理,比如返回null或忽略本次触摸 return; }
3. 转换触摸坐标
触摸事件的getX()/getY()是相对于ImageView本身的坐标(如果是屏幕坐标,要先通过view.getLocationOnScreen()转成局部坐标),然后用逆矩阵映射:
// 把触摸点存进数组,mapPoints会直接修改数组里的值 float[] touchPoint = new float[]{event.getX(), event.getY()}; // 用逆矩阵转换坐标 inverseMatrix.mapPoints(touchPoint); // 现在touchPoint[0]是原始Bitmap的X像素坐标,touchPoint[1]是Y像素坐标 float bitmapX = touchPoint[0]; float bitmapY = touchPoint[1];
4. 边界校验
最后别忘了校验坐标是否在原始Bitmap的范围内,避免越界报错:
Bitmap originalBitmap = ...; // 你的原始Bitmap对象 if (bitmapX >= 0 && bitmapX < originalBitmap.getWidth() && bitmapY >= 0 && bitmapY < originalBitmap.getHeight()) { // 坐标有效,可正常使用 } else { // 触摸点在Bitmap范围外,忽略即可 }
完整示例(含手势缩放平移)
如果你是自己实现的手势缩放平移,这里给个完整的自定义ImageView示例:
public class TouchableImageView extends ImageView { private Matrix mTransformMatrix = new Matrix(); private Matrix mInverseMatrix = new Matrix(); private ScaleGestureDetector mScaleDetector; private GestureDetector mGestureDetector; private Bitmap mOriginalBitmap; private float mLastTouchX, mLastTouchY; public TouchableImageView(Context context) { super(context); init(); } public TouchableImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { setScaleType(ScaleType.MATRIX); // 必须设为MATRIX才能自定义变换 mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener()); mGestureDetector = new GestureDetector(getContext(), new GestureListener()); } public void setOriginalBitmap(Bitmap bitmap) { mOriginalBitmap = bitmap; setImageBitmap(bitmap); // 初始化矩阵,让Bitmap居中适配ImageView float scaleX = (float) getWidth() / bitmap.getWidth(); float scaleY = (float) getHeight() / bitmap.getHeight(); float fitScale = Math.min(scaleX, scaleY); mTransformMatrix.postScale(fitScale, fitScale); float translateX = (getWidth() - bitmap.getWidth() * fitScale) / 2; float translateY = (getHeight() - bitmap.getHeight() * fitScale) / 2; mTransformMatrix.postTranslate(translateX, translateY); setImageMatrix(mTransformMatrix); } @Override public boolean onTouchEvent(MotionEvent event) { mScaleDetector.onTouchEvent(event); mGestureDetector.onTouchEvent(event); // 处理抬手时的坐标转换 if (event.getAction() == MotionEvent.ACTION_UP) { if (mTransformMatrix.invert(mInverseMatrix)) { float[] touchPoint = new float[]{event.getX(), event.getY()}; mInverseMatrix.mapPoints(touchPoint); float bitmapX = touchPoint[0]; float bitmapY = touchPoint[1]; if (mOriginalBitmap != null && bitmapX >= 0 && bitmapX < mOriginalBitmap.getWidth() && bitmapY >= 0 && bitmapY < mOriginalBitmap.getHeight()) { // 这里拿到了原始Bitmap的像素坐标,做你需要的操作 Log.d("BitmapCoord", "X: " + bitmapX + ", Y: " + bitmapY); } } } return true; } // 缩放手势监听 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { float scaleFactor = detector.getScaleFactor(); // 以触摸焦点为中心缩放 mTransformMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); setImageMatrix(mTransformMatrix); return true; } } // 平移手势监听 private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { mLastTouchX = e.getX(); mLastTouchY = e.getY(); return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // 反向平移(因为distance是手指移动的距离,要让图片反方向动) mTransformMatrix.postTranslate(-distanceX, -distanceY); setImageMatrix(mTransformMatrix); return true; } } }
关键注意事项
- ScaleType必须设为MATRIX:如果用其他scaleType(比如centerCrop),系统会自动生成矩阵,此时直接用
getImageMatrix()拿到总矩阵即可; - 坐标转换顺序:一定要确保触摸点是ImageView的局部坐标,不是屏幕全局坐标;
- 矩阵不可逆处理:当缩放比例为0时矩阵会不可逆,这时候要做容错,比如忽略本次触摸事件。
内容的提问来源于stack exchange,提问作者Snake
相关产品推荐
相关产品推荐

