现在的位置: 首页 > 综合 > 正文

支持双指旋转的Android超级图片控件

2016年10月18日 ⁄ 综合 ⁄ 共 7016字 ⁄ 字号 评论关闭

最近项目需要做一个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);
	}

}

抱歉!评论已关闭.