我们可以在 Android 中使用缩放手势检测器进行缩放吗?
Posted
技术标签:
【中文标题】我们可以在 Android 中使用缩放手势检测器进行缩放吗?【英文标题】:Can we use scale gesture detector for pinch zoom in Android? 【发布时间】:2011-08-13 00:39:33 【问题描述】:我们可以在 android 中使用缩放手势检测器进行捏缩放吗?
【问题讨论】:
【参考方案1】:你可以用这个
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
public class MyImageView extends View
private static final int INVALID_POINTER_ID = -1;
private Drawable mImage;
private float mPosX;
private float mPosY;
private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
public MyImageView(Context context)
this(context, null, 0);
mImage=act.getResources().getDrawable(context.getResources().getIdentifier("imagename", "drawable", "packagename"));
mImage.setBounds(0, 0, mImage.getIntrinsicWidth(), mImage.getIntrinsicHeight());
public MyImageView(Context context, AttributeSet attrs)
this(context, attrs, 0);
public MyImageView(Context context, AttributeSet attrs, int defStyle)
super(context, attrs, defStyle);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
@Override
public boolean onTouchEvent(MotionEvent ev)
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK)
case MotionEvent.ACTION_DOWN:
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress())
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
mLastTouchX = x;
mLastTouchY = y;
break;
case MotionEvent.ACTION_UP:
mActivePointerId = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_CANCEL:
mActivePointerId = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId)
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
break;
return true;
@Override
public void onDraw(Canvas canvas)
super.onDraw(canvas);
canvas.save();
Log.d("DEBUG", "X: "+mPosX+" Y: "+mPosY);
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
mImage.draw(canvas);
canvas.restore();
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
@Override
public boolean onScale(ScaleGestureDetector detector)
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
invalidate();
return true;
在您的 activity.setContentView(new MyImageView(this));
中调用它
【讨论】:
在缩小时会将您带到点 0,0。它不会放大手指的初始点。为什么会这样? sajjoo:我认为你可以使用ScaleGestureDetector.getFocusX,Y()
我试过了,但是缩放非常迟钝且不稳定(我正在以编程方式在画布上画线)。知道为什么吗?
如何添加 canvas.drawCircle 而不是 Image
感谢@Flexo 和 Jazz,你们为我节省了很多时间【参考方案2】:
您可以创建一个实现OnTouchListener
的可重用类来完成此操作。
public class MyScaleGestures implements OnTouchListener, OnScaleGestureListener
private View view;
private ScaleGestureDetector gestureScale;
private float scaleFactor = 1;
private boolean inScale = false;
public MyScaleGestures (Context c) gestureScale = new ScaleGestureDetector(c, this);
@Override
public boolean onTouch(View view, MotionEvent event)
this.view = view;
gestureScale.onTouchEvent(event);
return true;
@Override
public boolean onScale(ScaleGestureDetector detector)
scaleFactor *= detector.getScaleFactor();
scaleFactor = (scaleFactor < 1 ? 1 : scaleFactor); // prevent our view from becoming too small //
scaleFactor = ((float)((int)(scaleFactor * 100))) / 100; // Change precision to help with jitter when user just rests their fingers //
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
return true;
@Override
public boolean onScaleBegin(ScaleGestureDetector detector)
inScale = true;
return true;
@Override
public void onScaleEnd(ScaleGestureDetector detector) inScale = false;
然后像这样将其分配为您的View
的OnTouchListener
。
myView.setOnTouchListener(new MyScaleGestures(context));
如果您想为View
添加滚动功能,您需要从OnGestureListener
接口实现onScroll
。您可以将此覆盖添加到 MyScaleGestures
类来完成此操作。
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float x, float y)
float newX = view.getX();
float newY = view.getY();
if(!inScale)
newX -= x;
newY -= y;
WindowManager wm = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
Display d = wm.getDefaultDisplay();
Point p = new Point();
d.getSize(p);
if (newX > (view.getWidth() * scaleFactor - p.x) / 2)
newX = (view.getWidth() * scaleFactor - p.x) / 2;
else if (newX < -((view.getWidth() * scaleFactor - p.x) / 2))
newX = -((view.getWidth() * scaleFactor - p.x) / 2);
if (newY > (view.getHeight() * scaleFactor - p.y) / 2)
newY = (view.getHeight() * scaleFactor - p.y) / 2;
else if (newY < -((view.getHeight() * scaleFactor - p.y) / 2))
newY = -((view.getHeight() * scaleFactor - p.y) / 2);
view.setX(newX);
view.setY(newY);
return true;
以上所有操作的最终结果将给你这样的课程:
public class StandardGestures implements OnTouchListener, OnGestureListener, OnDoubleTapListener, OnScaleGestureListener
private View view;
private GestureDetector gesture;
private ScaleGestureDetector gestureScale;
private float scaleFactor = 1;
private boolean inScale;
public StandardGestures(Context c)
gesture = new GestureDetector(c, this);
gestureScale = new ScaleGestureDetector(c, this);
@Override
public boolean onTouch(View view, MotionEvent event)
this.view = view;
gesture.onTouchEvent(event);
gestureScale.onTouchEvent(event);
return true;
@Override
public boolean onDown(MotionEvent event)
return true;
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2, float x, float y)
return true;
@Override
public void onLongPress(MotionEvent event)
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float x, float y)
float newX = view.getX();
float newY = view.getY();
if(!inScale)
newX -= x;
newY -= y;
WindowManager wm = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
Display d = wm.getDefaultDisplay();
Point p = new Point();
d.getSize(p);
if (newX > (view.getWidth() * scaleFactor - p.x) / 2)
newX = (view.getWidth() * scaleFactor - p.x) / 2;
else if (newX < -((view.getWidth() * scaleFactor - p.x) / 2))
newX = -((view.getWidth() * scaleFactor - p.x) / 2);
if (newY > (view.getHeight() * scaleFactor - p.y) / 2)
newY = (view.getHeight() * scaleFactor - p.y) / 2;
else if (newY < -((view.getHeight() * scaleFactor - p.y) / 2))
newY = -((view.getHeight() * scaleFactor - p.y) / 2);
view.setX(newX);
view.setY(newY);
return true;
@Override
public void onShowPress(MotionEvent event)
@Override
public boolean onSingleTapUp(MotionEvent event)
return true;
@Override
public boolean onDoubleTap(MotionEvent event)
view.setVisibility(View.GONE);
return true;
@Override
public boolean onDoubleTapEvent(MotionEvent event)
return true;
@Override
public boolean onSingleTapConfirmed(MotionEvent event)
return true;
@Override
public boolean onScale(ScaleGestureDetector detector)
scaleFactor *= detector.getScaleFactor();
scaleFactor = scaleFactor < 1 ? 1 : scaleFactor; // prevent our image from becoming too small
scaleFactor = (float) (int) (scaleFactor * 100) / 100; // Change precision to help with jitter when user just rests their fingers //
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
onScroll(null, null, 0, 0); // call scroll to make sure our bounds are still ok //
return true;
@Override
public boolean onScaleBegin(ScaleGestureDetector detector)
inScale = true;
return true;
@Override
public void onScaleEnd(ScaleGestureDetector detector)
inScale = false;
onScroll(null, null, 0, 0); // call scroll to make sure our bounds are still ok //
【讨论】:
这是一个很好的答案,因为代码不涉及创建派生自ImageView
的新类。不幸的是,构造函数和类名不匹配,布尔值 inScale 丢失并且只有在存在 onScroll()
方法时才有意义。比上述更重要的是,onScroll()
method 不会覆盖任何东西(该类是否应该从另一个侦听器派生?),因此不起作用。或者,也许我错过了一些大事。这个答案可以改进吗?
@Baltasarq 感谢您指出这些。答案中的类使用的是我使用的更大类的代码。正如您发现的那样,我为了使其适用于答案而删减了太多内容。我已更新我的答案以更正您发现的错误。感谢您帮助我抓住它们并帮助我改进它。
感谢您的快速响应和修复。另一个问题,我认为可以用this.gestureScale.isInProgress()
代替inScale
。这是真的还是使用维护自己的布尔值更好?
还有一个问题。通过实现GestureDector.OnGestureListener
,我必须提供onLongPress()
、onDown()
、onFling()
、onShowPress()
和onSingleTapUp()
。
@Baltasarq,我不确定这是否有帮助,但可能值得一试。在onDown()
、onFling()
和onSingleTapUp()
中,我正在返回true
,并且我的代码中有一条注释说返回true
是为了不阻止这些事件中的输入。我已将完整课程附加到我的答案中,以防我遗漏了其他内容。【参考方案3】:
ScaleGestureDetector 从 Android 2.2(又名 Froyo,API 级别 8)开始可用。见:http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
在 2.0/2.1 中,您没有 ScaleGestureDetector,但您可以使用 Ed Burnette 的 ZDNet 博客条目(Pieter888 链接到上面)提供捏合缩放:http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-6-implementing-the-pinch-zoom-gesture/1847
【讨论】:
【参考方案4】:实际上有一个库使用这个类来缩放图像。
它叫做“TouchImageView”
【讨论】:
【参考方案5】:触摸图像视图
public class TouchImageView extends ImageView
Matrix matrix;
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
// Remember some things for zooming
PointF last = new PointF();
PointF start = new PointF();
float minScale = 1f;
float maxScale = 3f;
float[] m;
int viewWidth, viewHeight;
static final int CLICK = 3;
float saveScale = 1f;
protected float origWidth, origHeight;
int oldMeasuredWidth, oldMeasuredHeight;
ScaleGestureDetector mScaleDetector;
Context context;
public TouchImageView(Context context)
super(context);
sharedConstructing(context);
public TouchImageView(Context context, AttributeSet attrs)
super(context, attrs);
sharedConstructing(context);
private void sharedConstructing(Context context)
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix = new Matrix();
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener()
@Override
public boolean onTouch(View v, MotionEvent event)
mScaleDetector.onTouchEvent(event);
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction())
case MotionEvent.ACTION_DOWN:
last.set(curr);
start.set(last);
mode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG)
float deltaX = curr.x - last.x;
float deltaY = curr.y - last.y;
float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);
float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);
matrix.postTranslate(fixTransX, fixTransY);
fixTrans();
last.set(curr.x, curr.y);
break;
case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(curr.x - start.x);
int yDiff = (int) Math.abs(curr.y - start.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
setImageMatrix(matrix);
invalidate();
return true; // indicate event was handled
);
public void setMaxZoom(float x)
maxScale = x;
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
@Override
public boolean onScaleBegin(ScaleGestureDetector detector)
mode = ZOOM;
return true;
@Override
public boolean onScale(ScaleGestureDetector detector)
float mScaleFactor = detector.getScaleFactor();
float origScale = saveScale;
saveScale *= mScaleFactor;
if (saveScale > maxScale)
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
else if (saveScale < minScale)
saveScale = minScale;
mScaleFactor = minScale / origScale;
if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)
matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);
else
matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
fixTrans();
return true;
void fixTrans()
matrix.getValues(m);
float transX = m[Matrix.MTRANS_X];
float transY = m[Matrix.MTRANS_Y];
float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);
float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);
if (fixTransX != 0 || fixTransY != 0)
matrix.postTranslate(fixTransX, fixTransY);
float getFixTrans(float trans, float viewSize, float contentSize)
float minTrans, maxTrans;
if (contentSize <= viewSize)
minTrans = 0;
maxTrans = viewSize - contentSize;
else
minTrans = viewSize - contentSize;
maxTrans = 0;
if (trans < minTrans)
return -trans + minTrans;
if (trans > maxTrans)
return -trans + maxTrans;
return 0;
float getFixDragTrans(float delta, float viewSize, float contentSize)
if (contentSize <= viewSize)
return 0;
return delta;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
//
// Rescales image on rotation
//
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight
|| viewWidth == 0 || viewHeight == 0)
return;
oldMeasuredHeight = viewHeight;
oldMeasuredWidth = viewWidth;
if (saveScale == 1)
//Fit to screen.
float scale;
Drawable drawable = getDrawable();
if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)
return;
int bmWidth = drawable.getIntrinsicWidth();
int bmHeight = drawable.getIntrinsicHeight();
Log.e("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);
float scaleX = (float) viewWidth / (float) bmWidth;
float scaleY = (float) viewHeight / (float) bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
// Center the image
float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);
float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = viewWidth - 2 * redundantXSpace;
origHeight = viewHeight - 2 * redundantYSpace;
setImageMatrix(matrix);
fixTrans();
MainActivity.java
public class SecondActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
TouchImageView img = new TouchImageView(this);
img.setImageResource(R.drawable.ic_launcher);
img.setMaxZoom(4f);
setContentView(img);
【讨论】:
【参考方案6】:是的,我们可以在这里是示例代码,其中 onPinch() 和 onZoom() 是您自己实现的操作
public class simpleOnScaleGestureListener extends
SimpleOnScaleGestureListener
@Override
public boolean onScale(ScaleGestureDetector detector)
startScale = detector.getScaleFactor();
return true;
@Override
public boolean onScaleBegin(ScaleGestureDetector detector)
return true;
@Override
public void onScaleEnd(ScaleGestureDetector detector)
endScale = detector.getScaleFactor();
if (startScale > endScale)
Log.i("onScaleEnd", "Pinch Dection");
onPinch();
else if (startScale < endScale)
Log.i("onScaleEnd", "Zoom Dection");
onZoom();
【讨论】:
我认为您遗漏了太多内容,以至于这个答案没有用。这只是你的 ScaleGestureListener,剩下的呢? 我已经调用了 onpinch() 和 onzoom(),它们在我的 customeview 上实现了 pinchzoom,你可以自己实现 这就是我要找的以上是关于我们可以在 Android 中使用缩放手势检测器进行缩放吗?的主要内容,如果未能解决你的问题,请参考以下文章
Android使用GestureDetector进行手势检测