最近项目需要做一个Android端图片浏览的功能,类似于常规的图片浏览器,可以支持缩放,拖拉等操作。由于对Android开发不熟悉,我在网上搜了个具有类似功能的图片浏览程序,其可以支持双指缩放和单指拖拉的基本手势,虽然不算完美,但程序中对Matrix的应用让我学到很多。我在这个程序的基础上进行改进,把它封装成了一个图片控件,完善了原有功能,并增加了双指旋转和双击缩放的功能。
进过测试和修改,这个控件基本上完美的达到了我预期的效果,出乎我的意料。这个图片控件一共支持4种手势操作:双指缩放、双指旋转、单指拖拉和双击缩放。其中双指旋转是一个亮点(目前还不知道有哪个Android图片浏览器实现了这个功能),旋转过程是平滑的,但松手后图片会固定在最接近0度、90度、180度、270度的一个角度上,这样保证了图片不会倾斜。另外这个控件还超级易用,可以和Android原生的ImageView一样使用(控件本身也是继承ImageView,我把它称作SuperImageView):
<com.c35.nmt.widgets.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); } }