最近项目需要做一个Android端图片浏览的功能,类似于常规的图片浏览器,可以支持缩放,拖拉等操作。由于对Android开发不熟悉,我在网上搜了个具有类似功能的图片浏览程序,其可以支持双指缩放和单指拖拉的基本手势,虽然不算完美,但程序中对Matrix的应用让我学到很多。我在这个程序的基础上进行改进,把它封装成了一个图片控件,完善了原有功能,并增加了双指旋转和双击缩放的功能。
进过测试和修改,这个控件基本上完美的达到了我预期的效果,出乎我的意料。这个图片控件一共支持4种手势操作:双指缩放、双指旋转、单指拖拉和双击缩放。其中双指旋转是一个亮点(目前还不知道有哪个Android图片浏览器实现了这个功能),旋转过程是平滑的,但松手后图片会固定在最接近0度、90度、180度、270度的一个角度上,这样保证了图片不会倾斜。另外这个控件还超级易用,可以和Android原生的ImageView一样使用(控件本身也是继承ImageView,我把它称作SuperImageView):
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/sample" />
超级控件的源码SuperImageView.java如下,使用者自行修改package名称:package com.c35.nmt.widgets;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.widget.ImageView;
public class SuperImageView extends ImageView {
static final float MAX_SCALE = 2.0f;
float imageW;
float imageH;
float rotatedImageW;
float rotatedImageH;
float viewW;
float viewH;
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
static final int NONE = 0;// 初始状态
static final int DRAG = 1;// 拖动
static final int ZOOM = 2;// 缩放
static final int ROTATE = 3;// 旋转
static final int ZOOM_OR_ROTATE = 4; // 缩放或旋转
int mode = NONE;
PointF pA = new PointF();
PointF pB = new PointF();
PointF mid = new PointF();
PointF lastClickPos = new PointF();
long lastClickTime = 0;
double rotation = 0.0;
float dist = 1f;
public SuperImageView(Context context) {
super(context);
init();
}
public SuperImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SuperImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setScaleType(ImageView.ScaleType.MATRIX);
}
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
setImageWidthHeight();
}
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
setImageWidthHeight();
}
public void setImageResource(int resId) {
super.setImageResource(resId);
setImageWidthHeight();
}
private void setImageWidthHeight() {
Drawable d = getDrawable();
if (d == null) {
return;
}
imageW = rotatedImageW = d.getIntrinsicWidth();
imageH = rotatedImageH = d.getIntrinsicHeight();
initImage();
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewW = w;
viewH = h;
if (oldw == 0) {
initImage();
} else {
fixScale();
fixTranslation();
setImageMatrix(matrix);
}
}
private void initImage() {
if (viewW <= 0 || viewH <= 0 || imageW <= 0 || imageH <= 0) {
return;
}
mode = NONE;
matrix.setScale(0, 0);
fixScale();
fixTranslation();
setImageMatrix(matrix);
}
private void fixScale() {
float p[] = new float[9];
matrix.getValues(p);
float curScale = Math.abs(p[0]) + Math.abs(p[1]);
float minScale = Math.min((float) viewW / (float) rotatedImageW,
(float) viewH / (float) rotatedImageH);
if (curScale < minScale) {
if (curScale > 0) {
double scale = minScale / curScale;
p[0] = (float) (p[0] * scale);
p[1] = (float) (p[1] * scale);
p[3] = (float) (p[3] * scale);
p[4] = (float) (p[4] * scale);
matrix.setValues(p);
} else {
matrix.setScale(minScale, minScale);
}
}
}
private float maxPostScale() {
float p[] = new float[9];
matrix.getValues(p);
float curScale = Math.abs(p[0]) + Math.abs(p[1]);
float minScale = Math.min((float) viewW / (float) rotatedImageW,
(float) viewH / (float) rotatedImageH);
float maxScale = Math.max(minScale, MAX_SCALE);
return maxScale / curScale;
}
private void fixTranslation() {
RectF rect = new RectF(0, 0, imageW, imageH);
matrix.mapRect(rect);
float height = rect.height();
float width = rect.width();
float deltaX = 0, deltaY = 0;
if (width < viewW) {
deltaX = (viewW - width) / 2 - rect.left;
} else if (rect.left > 0) {
deltaX = -rect.left;
} else if (rect.right < viewW) {
deltaX = viewW - rect.right;
}
if (height < viewH) {
deltaY = (viewH - height) / 2 - rect.top;
} else if (rect.top > 0) {
deltaY = -rect.top;
} else if (rect.bottom < viewH) {
deltaY = viewH - rect.bottom;
}
matrix.postTranslate(deltaX, deltaY);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
// 主点按下
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
pA.set(event.getX(), event.getY());
pB.set(event.getX(), event.getY());
mode = DRAG;
break;
// 副点按下
case MotionEvent.ACTION_POINTER_DOWN:
if (event.getActionIndex() > 1)
break;
dist = spacing(event.getX(0), event.getY(0), event.getX(1),
event.getY(1));
// 如果连续两点距离大于10,则判定为多点模式
if (dist > 10f) {
savedMatrix.set(matrix);
pA.set(event.getX(0), event.getY(0));
pB.set(event.getX(1), event.getY(1));
mid.set((event.getX(0) + event.getX(1)) / 2,
(event.getY(0) + event.getY(1)) / 2);
mode = ZOOM_OR_ROTATE;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (mode == DRAG) {
if (spacing(pA.x, pA.y, pB.x, pB.y) < 50) {
long now = System.currentTimeMillis();
if (now - lastClickTime < 500
&& spacing(pA.x, pA.y, lastClickPos.x,
lastClickPos.y) < 50) {
doubleClick(pA.x, pA.y);
now = 0;
}
lastClickPos.set(pA);
lastClickTime = now;
}
} else if (mode == ROTATE) {
int level = (int) Math.floor((rotation + Math.PI / 4)
/ (Math.PI / 2));
if (level == 4)
level = 0;
matrix.set(savedMatrix);
matrix.postRotate(90 * level, mid.x, mid.y);
if (level == 1 || level == 3) {
float tmp = rotatedImageW;
rotatedImageW = rotatedImageH;
rotatedImageH = tmp;
fixScale();
}
fixTranslation();
setImageMatrix(matrix);
}
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if (mode == ZOOM_OR_ROTATE) {
PointF pC = new PointF(event.getX(1) - event.getX(0) + pA.x,
event.getY(1) - event.getY(0) + pA.y);
double a = spacing(pB.x, pB.y, pC.x, pC.y);
double b = spacing(pA.x, pA.y, pC.x, pC.y);
double c = spacing(pA.x, pA.y, pB.x, pB.y);
if (a >= 10) {
double cosB = (a * a + c * c - b * b) / (2 * a * c);
double angleB = Math.acos(cosB);
double PID4 = Math.PI / 4;
if (angleB > PID4 && angleB < 3 * PID4) {
mode = ROTATE;
rotation = 0;
} else {
mode = ZOOM;
}
}
}
if (mode == DRAG) {
matrix.set(savedMatrix);
pB.set(event.getX(), event.getY());
matrix.postTranslate(event.getX() - pA.x, event.getY() - pA.y);
fixTranslation();
setImageMatrix(matrix);
} else if (mode == ZOOM) {
float newDist = spacing(event.getX(0), event.getY(0),
event.getX(1), event.getY(1));
if (newDist > 10f) {
matrix.set(savedMatrix);
float tScale = Math.min(newDist / dist, maxPostScale());
matrix.postScale(tScale, tScale, mid.x, mid.y);
fixScale();
fixTranslation();
setImageMatrix(matrix);
}
} else if (mode == ROTATE) {
PointF pC = new PointF(event.getX(1) - event.getX(0) + pA.x,
event.getY(1) - event.getY(0) + pA.y);
double a = spacing(pB.x, pB.y, pC.x, pC.y);
double b = spacing(pA.x, pA.y, pC.x, pC.y);
double c = spacing(pA.x, pA.y, pB.x, pB.y);
if (b > 10) {
double cosA = (b * b + c * c - a * a) / (2 * b * c);
double angleA = Math.acos(cosA);
double ta = pB.y - pA.y;
double tb = pA.x - pB.x;
double tc = pB.x * pA.y - pA.x * pB.y;
double td = ta * pC.x + tb * pC.y + tc;
if (td > 0) {
angleA = 2 * Math.PI - angleA;
}
rotation = angleA;
matrix.set(savedMatrix);
matrix.postRotate((float) (rotation * 180 / Math.PI),
mid.x, mid.y);
setImageMatrix(matrix);
}
}
break;
}
return true;
}
/**
* 两点的距离
*/
private float spacing(float x1, float y1, float x2, float y2) {
float x = x1 - x2;
float y = y1 - y2;
return FloatMath.sqrt(x * x + y * y);
}
private void doubleClick(float x, float y) {
float p[] = new float[9];
matrix.getValues(p);
float curScale = Math.abs(p[0]) + Math.abs(p[1]);
float minScale = Math.min((float) viewW / (float) rotatedImageW,
(float) viewH / (float) rotatedImageH);
if (curScale <= minScale + 0.01) { // 放大
float toScale = Math.max(minScale, MAX_SCALE) / curScale;
matrix.postScale(toScale, toScale, x, y);
} else { // 缩小
float toScale = minScale / curScale;
matrix.postScale(toScale, toScale, x, y);
fixTranslation();
}
setImageMatrix(matrix);
}
}