打造个性的图片预览与多点触控(自由移动,自由缩放,双击放大缩小)

先简单介绍一下用到的知识


《ScaleGestureDetector》

一、结构

public class ScaleGestureDetector extends Object

java.lang.Object

android.view.ScaleGestureDetector

  二、概述

  根据接收的MotionEvent, 侦测由多个触点(多点触控)引发的变形手势。callback方法ScaleGestureDetector.OnScaleGestureListener 会在特定手势事件发生时通知用户。该类仅能和Touch事件引发的MotionEvent配合使用。使用该类需要

    为你的View创建ScaleGestureDetector 实例

    确保在onTouchEvent(MotionEvent)方法中调用 onTouchEvent (MotionEvent). [译者注:前者为该类的onTouchEvent方法,后者为ViewonTouchEvent方法。在事件发生时,定义在callback中的方法会被调用。

    (译者注:ScaleGestureDetectorAndroid2.2新增的类,允许Views可以通过提供的MotionEvents检测和处理包括多点触摸在内的手势变化信息。)

  三、内部类

    interface ScaleGestureDetector.OnScaleGestureListener    

  手势发生时接收通知的监听器 

    class         ScaleGestureDetector.SimpleOnScaleGestureListener 

  一个方便使用的类。 若仅想监听一部分尺寸伸缩事件,可继承该类。

  四、构造函数

    public ScaleGestureDetector (Context context, ScaleGestureDetector.OnScaleGestureListener listener)

         构造函数

  五、公共方法

    public float getCurrentSpan ()

  返回手势过程中,组成该手势的两个触点的当前距离。

  返回值

  以像素为单位的触点距离。

    public long getEventTime ()

  返回事件被捕捉时的时间。

  返回值

  以毫秒为单位的事件时间。

    public float getFocusX ()

  返回当前手势焦点的X坐标。 如果手势正在进行中,焦点位于组成手势的两个触点之间。 如果手势正在结束,焦点为仍留在屏幕上的触点的位置。若isInProgress()返回false,该方法的返回值未定义。

  返回值

  返回焦点的X坐标值,以像素为单位。

    public float getFocusY ()

  返回当前手势焦点的Y坐标。 如果手势正在进行中,焦点位于组成手势的两个触点之间。 如果手势正在结束,焦点为仍留在屏幕上的触点的位置。若isInProgress()返回false,该方法的返回值未定义。

  返回值

  返回焦点的Y坐标值,以像素为单位。

    public float getPreviousSpan ()

  返回手势过程中,组成该手势的两个触点的前一次距离。

  返回值

  两点的前一次距离,以像素为单位。

    public float getScaleFactor ()

  返回从前一个伸缩事件至当前伸缩事件的伸缩比率。该值定义为 (getCurrentSpan() / getPreviousSpan())

  返回值

  当前伸缩比率.

    public long getTimeDelta ()

  返回前一次接收到的伸缩事件距当前伸缩事件的时间差,以毫秒为单位。

  返回值

  从前一次伸缩事件起始的时间差,以毫秒为单位。

    public boolean isInProgress ()

  如果手势处于进行过程中,返回true.

  返回值

  如果手势处于进行过程中,返回true。否则返回false


《Rect》

 new Rect(150, 75, 260, 120)  

  这个构造方法需要四个参数这四个参数 指明了什么位置 ?我们就来解释怎么画 这个 矩形 
这四个 参数 分别代表的意思是:left   top   right   bottom  上下左右呗。啊,不是     下。 下面给大家解释  
left  矩形左边的X坐标  150        
top:    矩形顶部的Y坐标   75        
right :  矩形右边的X坐标   260      
bottom 矩形底部的Y坐标 120     

说白了就是左上角的坐标是(150,75),右下角的坐标是(260,120),这样就好理解了


《Matrix》

在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类。Android中的Matrix是一个3 x 3的矩阵,其内容如下:

 

Matrix的对图像的处理可分为四类基本变换:

Translate           平移变换

Rotate                旋转变换

Scale                  缩放变换

Skew                  错切变换

 

从字面上理解,矩阵中的MSCALE用于处理缩放变换,MSKEW用于处理错切变换,MTRANS用于处理平移变换,MPERSP用于处理透视变换。实际中当然不能完全按照字面上的说法去理解Matrix。同时,在Android的文档中,未见到用Matrix进行透视变换的相关说明,所以本文也不讨论这方面的问题。

package com.imooc.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;

public class ZoomImageView extends ImageView implements OnGlobalLayoutListener, OnScaleGestureListener, OnTouchListener {

	private boolean mOnce;

	/**
	 * 初始化时放大的值
	 */
	private float mInitScale;
	/**
	 * 双击放大值到达的值
	 */
	private float mMidScale;
	/**
	 * 放大的最大
	 */
	private float mMaxScale;

	private Matrix mScaleMatrix;

	/**
	 * 铺货用户多指触碰时缩放比例
	 */
	private ScaleGestureDetector mScaleGestureDetector;

	// --------------自由移动
	/**
	 * 记录上一次多点触控的数量
	 */
	private int mLastPointerCount;
	private float mLastX;
	private float mLastY;

	private int mTouchSlop;

	private boolean isCanDrag;

	// ------------------双击放大与缩小
	private GestureDetector mGestureDetector;

	private boolean isAutoScale;

	public ZoomImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		// init
		mScaleMatrix = new Matrix();
		setScaleType(ScaleType.MATRIX);
		mScaleGestureDetector = new ScaleGestureDetector(context, this);
		setOnTouchListener(this);

		mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

		mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
			@Override
			public boolean onDoubleTap(MotionEvent e) {

				if (isAutoScale) {
					return true;
				}
				float x = e.getX();
				float y = e.getY();

				if (getScale() < mMidScale) {
					// 瞬间放大
					// mScaleMatrix.postScale(mMidScale/getScale(),
					// mMidScale/getScale(),x,y);
					// setImageMatrix(mScaleMatrix);

					// 缓慢放大
					postDelayed(new AutoScaleRunnable(mMidScale, x, y), 16);
					isAutoScale = true;
				} else {
					// mScaleMatrix.postScale(mInitScale/getScale(),
					// mInitScale/getScale(),x,y);
					// setImageMatrix(mScaleMatrix);

					postDelayed(new AutoScaleRunnable(mInitScale, x, y), 16);
					isAutoScale = true;
				}

				return true;
			}
		});
	}

	private class AutoScaleRunnable implements Runnable {

		/**
		 * 缩放的目标值
		 */
		private float mTargetScale;
		// 缩放的中心点
		private float x;
		private float y;

		private final float BIGGER = 1.02f;
		private final float SMALL = 0.98f;

		private float tmpScale;

		public AutoScaleRunnable(float mTargetScale, float x, float y) {
			super();
			this.mTargetScale = mTargetScale;
			this.x = x;
			this.y = y;

			if (getScale() < mTargetScale) {
				tmpScale = BIGGER;
			}
			if (getScale() > mTargetScale) {
				tmpScale = SMALL;
			}
		}

		@Override
		public void run() {

			// 进行缩放
			mScaleMatrix.postScale(tmpScale, tmpScale, x, y);
			checkBorderAndCenterWhenScale();
			setImageMatrix(mScaleMatrix);

			float currentScale = getScale();

			if ((tmpScale > 1.0f && currentScale < mTargetScale) || (tmpScale < 1.0f && currentScale > mTargetScale)) {
				postDelayed(this, 16);// 没16秒执行一次run方法
			} else {
				// 设置我们的目标值
				float scale = mTargetScale / currentScale;
				mScaleMatrix.postScale(scale, scale, x, y);
				checkBorderAndCenterWhenScale();
				setImageMatrix(mScaleMatrix);

				isAutoScale = false;
			}

		}

	}

	public ZoomImageView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public ZoomImageView(Context context) {
		this(context, null);
	}

	@Override
	protected void onAttachedToWindow() {
		// TODO Auto-generated method stub
		super.onAttachedToWindow();
		getViewTreeObserver().addOnGlobalLayoutListener(this);
	}

	@SuppressLint("NewApi")
	@Override
	protected void onDetachedFromWindow() {
		// TODO Auto-generated method stub
		super.onDetachedFromWindow();
		getViewTreeObserver().removeOnGlobalLayoutListener(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see 实现图片填满控件且居中android.view.ViewTreeObserver.OnGlobalLayoutListener#
	 * onGlobalLayout()
	 */
	@Override
	public void onGlobalLayout() {
		if (!mOnce) {
			// 得到控件的宽和高
			int width = getWidth();
			int height = getHeight();
			// 得到我们的图片及宽和高
			Drawable d = getDrawable();
			if (d == null) {
				return;
			}
			int dw = d.getIntrinsicWidth();
			int dh = d.getIntrinsicHeight();

			float scale = 1.0f;

			// 如果图片的宽度大于控件宽度,但是高度小于控件高度,将其缩小
			if (dw > width && dh < height) {
				scale = width * 1.0f / dw;
			}
			// 如果图片的高度大于控件gao度,但是宽度小于控件宽度,将其缩小
			if (dh > height && dw < width) {
				scale = height * 1.0f / dh;
			}
			// 如果图片的宽度大于控件宽度,但是高度da于控件高度
			if ((dw > width && dh > height) || (dw < width && dh < height)) {
				scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
			}

			// 得到了初始化是的缩放比例
			mInitScale = scale;
			mMidScale = mInitScale * 2;
			mMaxScale = mInitScale * 4;

			// 将图片移动到控件中心
			int dx = getWidth() / 2 - dw / 2;
			int dy = getHeight() / 2 - dh / 2;

			mScaleMatrix.postTranslate(dx, dy);// 平移
			mScaleMatrix.postScale(mInitScale, mInitScale, width / 2, height / 2);// 以控件的中心进行缩放
			setImageMatrix(mScaleMatrix);

			mOnce = true;
		}
	}

	/**
	 * 获取当前图片的缩放值
	 * 
	 * @return
	 */
	public float getScale() {
		float[] values = new float[9];
		mScaleMatrix.getValues(values);
		return values[Matrix.MSCALE_X];
	}

	// 缩放区间:initScale-----maxScale
	// 实现多指触碰缩放操作
	@Override
	public boolean onScale(ScaleGestureDetector detector) {
		float scale = getScale();
		float scaleFactor = detector.getScaleFactor();// 返回从前一个伸缩事件至当前伸缩事件的伸缩比率。

		if (getDrawable() == null) {
			return true;
		}
		// 缩放范围的控制,如果当前缩放值小于最大所放置,允许放大,如果当前缩放值大于最小缩放值,允许缩小
		if ((scale < mMaxScale && scaleFactor > 1.0f) || (scale > mInitScale && scaleFactor < 1.0f)) {
			if (scale * scaleFactor < mInitScale) {
				scaleFactor = mInitScale / scale;
			}
			if (scale * scaleFactor > mMaxScale) {
				scaleFactor = mMaxScale / scale;
			}
			// 以控件中心位置缩放
			// mScaleMatrix.postScale(scaleFactor, scaleFactor, getWidth() / 2,
			// getHeight() / 2);

			// 以多指触碰中心位置缩放 返回当前手势焦点的X坐标。
			// detector.getFocusX()如果手势正在进行中,焦点位于组成手势的两个触点之间。
			// 如果手势正在结束,焦点为仍留在屏幕上的触点的位置。
			mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());

			checkBorderAndCenterWhenScale();
			setImageMatrix(mScaleMatrix);
		}
		return true;
	}

	/**
	 * 获得图片放大缩小以后的宽和高以及t,b,l,r
	 * 
	 * @return
	 */
	private RectF getMatrixRectF() {
		Matrix matrix = mScaleMatrix;
		RectF rectF = new RectF();

		Drawable d = getDrawable();
		if (d != null) {
			rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
			matrix.mapRect(rectF);
		}
		return rectF;
	}

	/**
	 * 在缩放的时候进行边界控制
	 */
	private void checkBorderAndCenterWhenScale() {
		RectF rect = getMatrixRectF();

		float deltaX = 0;
		float deltaY = 0;

		int width = getWidth();
		int height = getHeight();

		// 缩放时进行边界检测,防止边沿出现空隙
		if (rect.width() >= width) {// 图片的宽大于等于控件的宽才需要移动
			if (rect.left > 0) {// 图片的左边沿距离控件的左边有距离
				deltaX = -rect.left;// 需左移的距离
			}
			if (rect.right < width) {// 图片的右边沿距离控件的右边有距离
				deltaX = width - rect.right;// 需右移的距离
			}
		}

		if (rect.height() >= height) {// 图片的高大于等于控件的高才需要移动
			if (rect.top > 0) {// 图片的上边沿距离控件的上边有距离
				deltaY = -rect.top;// 需上移的距离
			}
			if (rect.bottom < height) {// 图片的低边沿距离控件的低边有距离
				deltaY = height - rect.bottom;// 需上移的距离
			}
		}

		// 如果宽度或者高度小于控件的宽或高,则让其居中
		if (rect.width() < width) {
			deltaX = width / 2 - rect.right + rect.width() / 2;
		}
		if (rect.height() < height) {
			deltaY = height / 2 - rect.bottom + rect.height() / 2;
		}
		mScaleMatrix.postTranslate(deltaX, deltaY);
	}

	@Override
	public boolean onScaleBegin(ScaleGestureDetector detector) {
		// 一定要返回true才会进入onScale()这个函数
		return true;
	}

	@Override
	public void onScaleEnd(ScaleGestureDetector detector) {

	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {

		if (mGestureDetector.onTouchEvent(event)) {
			return true;
		}
		mScaleGestureDetector.onTouchEvent(event);
		// <------------------处理放大后移动查看隐藏的部分-------------------------------->
		float x = 0;
		float y = 0;
		// 拿到多点触控的数量
		int pointerCount = event.getPointerCount();// 返回MotionEvent中表示了多少手指数
		for (int i = 0; i < pointerCount; i++) {
			x += event.getX(i);
			y += event.getY(i);
		}
		x /= pointerCount;
		y /= pointerCount;

		if (mLastPointerCount != pointerCount) {
			isCanDrag = false;
			mLastX = x;
			mLastY = y;
		}
		mLastPointerCount = pointerCount;

		switch (event.getAction()) {
		case MotionEvent.ACTION_MOVE:

			float dx = x - mLastX;
			float dy = y - mLastY;

			if (!isCanDrag) {
				isCanDrag = isMoveAction(dx, dy);
			}
			if (isCanDrag) {
				RectF rectF = getMatrixRectF();
				if (getDrawable() != null) {
					// 如果宽度小于控件宽度,不允许横向移动
					if (rectF.width() < getWidth()) {
						dx = 0;
					}
					// 如果高度小于控件高度,不允许纵向移动
					if (rectF.height() < getHeight()) {
						dy = 0;
					}
					mScaleMatrix.postTranslate(dx, dy);
					checkBorderAndCenterWhenScale();// 平移的时候检查边沿
					setImageMatrix(mScaleMatrix);
				}
			}

			mLastX = x;
			mLastY = y;
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			mLastPointerCount = 0;
			break;

		default:
			break;
		}
		// <------------------------------------------------>

		return true;
	}

	/**
	 * 判断是否move
	 * 
	 * @param dx
	 * @param dy
	 * @return
	 */
	private boolean isMoveAction(float dx, float dy) {
		return Math.sqrt(dx * dx + dy * dy) > mTouchSlop;
	}

}

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
     >

     <com.imooc.view.ZoomImageView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:scaleType="matrix"
        android:background="#66cccc"
        android:src="@drawable/mingxing0403" />

</RelativeLayout>

package com.example.imooc_zoomimageview;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}


}


只演示了双击效果,多手指没法演示额。

源码:http://download.csdn.net/detail/gly742279097/8591381

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值