当我研究在图像上触摸平移/缩放的方法时,我通常会发现有效、简单的代码——但没有什么能完全满足我的需求。图像不需要在实际图像(位图)的边缘与其视图之间显示空白区域。如果位图是 200x100 而视图是 50x50,用户应该只能缩小到 100x50,允许他们水平滑动图像,但不能垂直滑动。
我的代码在移动(翻译)图像时做得很好——直到图像被缩放。然后有些东西被扔掉了;我可以将位图移动得足够远以看到它周围的间隙。这可能是与当前比例因子分解像素测量相关的简单而明显的事情,但我找不到它。我怀疑这与下面 onDraw() 中 maxX 和 maxY 的计算有关。有任何想法吗?
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
/**
* Most code from
* http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
*
* @author Chad Schultz
*
*/
public class PanZoomImageView extends ImageView {
public static final String TAG = PanZoomImageView.class.getName();
private static final int INVALID_POINTER_ID = -1;
// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
private Bitmap bitmap;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float minScaleFactor;
private float mPosX;
private float mPosY;
private float mLastTouchX, mLastTouchY;
private boolean firstDraw = true;
private boolean panEnabled = true;
private boolean zoomEnabled = true;
public PanZoomImageView(Context context) {
super(context);
setup();
}
public PanZoomImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
}
public PanZoomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
private void setup() {
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
@Override
public void setImageBitmap(Bitmap bmp) {
super.setImageBitmap(bmp);
bitmap = bmp;
firstDraw = true;
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
bitmap = ((BitmapDrawable) drawable).getBitmap();
firstDraw = true;
}
public void onDraw(Canvas canvas) {
Log.v(TAG, "onDraw()");
if (bitmap == null) {
Log.w(TAG, "nothing to draw - bitmap is null");
super.onDraw(canvas);
return;
}
if (firstDraw
&& (bitmap.getHeight() > 0)
&& (bitmap.getWidth() > 0)
&& (canvas.getHeight() > 0)
&& (canvas.getWidth() > 0)) {
//Don't let the user zoom out so much that the image is smaller
//than its containing frame
float minXScaleFactor = (float) canvas.getWidth() / (float) bitmap.getWidth();
float minYScaleFactor = (float) canvas.getHeight() / (float) bitmap.getHeight();
minScaleFactor = Math.max(minXScaleFactor, minYScaleFactor);
Log.d(TAG, "minScaleFactor: " + minScaleFactor);
firstDraw = false;
}
mScaleFactor = Math.max(mScaleFactor, minScaleFactor);
Log.d(TAG, "mScaleFactor: " + mScaleFactor);
//Save the canvas without translating (panning) or scaling (zooming)
//After each change, restore to this state, instead of compounding
//changes upon changes
canvas.save();
int maxX, minX, maxY, minY;
//How far can we move the image horizontally without having a gap between image and frame?
maxX = (int) (mScaleFactor * (bitmap.getWidth() / 2) - (canvas.getWidth() / 2));
minX = -1 * maxX;
//How far can we move the image vertically without having a gap between image and frame?
maxY = (int) (mScaleFactor * (bitmap.getHeight() / 2) - (canvas.getWidth() / 2));
minY = -1 * maxY;
//Do not go beyond the boundaries of the image
if (mPosX > maxX) {
mPosX = maxX;
}
if (mPosX < minX) {
mPosX = minX;
}
if (mPosY > maxY) {
mPosY = maxY;
}
if (mPosY < minY) {
mPosY = minY;
}
Log.d(TAG, "canvas width: " + canvas.getWidth() + " canvas height: "
+ canvas.getHeight());
Log.d(TAG, "bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight());
Log.d(TAG, "translating mPosX: " + mPosX + " mPosY: " + mPosY);
if (zoomEnabled) {
Log.d(TAG, "zooming to scale factor of " + mScaleFactor);
canvas.scale(mScaleFactor, mScaleFactor);
} else {
Log.d(TAG, "zooming disabled");
}
if (panEnabled) {
Log.d(TAG, "panning to " + mPosX + "," + mPosY);
canvas.translate(mPosX, mPosY);
} else {
Log.d(TAG, "panning disabled");
}
super.onDraw(canvas);
canvas.restore(); //clear translation/scaling
}
@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()) {
float dx = x - mLastTouchX;
float dy = y - mLastTouchY;
//Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels
//at 200% zoom causes the image to slide 20 pixels instead of perfectly
//following the user's touch
dx /= mScaleFactor;
dy /= mScaleFactor;
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;
}
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, 5.0f));
Log.d(TAG, "detector scale factor: " + detector.getScaleFactor() + " mscalefactor: " + mScaleFactor);
invalidate();
return true;
}
}
//Currently zoomEnabled/panEnabled can only be set programmatically, not in XML
public boolean isPanEnabled() {
return panEnabled;
}
public void setPanEnabled(boolean panEnabled) {
this.panEnabled = panEnabled;
}
public boolean isZoomEnabled() {
return zoomEnabled;
}
public void setZoomEnabled(boolean zoomEnabled) {
this.zoomEnabled = zoomEnabled;
}
}