高仿微信图片放大拖动浏览,自定义ImageView

</pre>现在基本上每个项目中都有大量的图片,用户习惯了双击放大,然后拖动平移它,如果还是简单的只能看下图那用户不得骂死,最近的项目中就有类似朋友圈的功能模块,查看用户发的图片必须要求能放大。</p><p>也许你会说这还不简单,github就有现成的,对photoView就是,而且很有名,但是项目中都遇到bug,我也亲手继承了一下,确实有,如果自己改源码的话,文件还是挺多 ,而且项目比较紧也没太多时间,所以我就有了自己写一个的想法,思路倒不复杂,但是细节需要处理好。</p><p>我们先来看别人做好的轮子,我很多也是参考了这篇文章<a target=_blank href="http://blog.csdn.net/lmj623565791/article/details/39474553" target="_blank">http://blog.csdn.net/lmj623565791/article/details/39474553</a>,博主很强大。</p><p>好了,我们进正题,如果我们直接继承View,自己写的东西就太多了,我们可以直接继承ImageView</p><p>自定义控件无非就是这几个步骤:</p><p>1,定义属性</p><p>2,布局中使用控件,设置属性</p><p>3,控件中得到属性,并作出逻辑处理,合理调用onMeasure,onDraw方法等</p><p>可是我们这个图片查看器没有必须做太多的指定属性,这里需要控制放大的最小/大比例,我们可以在代码中定义常量,如果需要修改的话,可以直接修改值,就省去了好多步骤</p><p>直接看核心类的代码</p><p><span style="font-family:Courier New;background-color: rgb(240, 240, 240);"></span><pre class="java" name="code">package com.example.test.view;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
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.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;

/**
 * http://my.csdn.net/xingliuhua
 * 
 * @author xingliuhua
 * 
 */
public class XlhZoomImageView extends ImageView implements OnTouchListener,
		OnGlobalLayoutListener, OnScaleGestureListener {
	private GestureDetector mDetector;// 双击手势
	private ScaleGestureDetector mScaleGestureDetector;// 缩放手势
	private Context mContext;
	private float MIN_SCALE = 0.5f;// 最小的缩放比例
	private float MAX_SCALE = 2.5f;// 最大的缩放比例
	private boolean isFirst = true;// 是否是第一次(第一次呈现出来)
	private Matrix mMatrix;// 缩放的矩阵
	private float lastX, lastY;// 手指最后一次触碰的X,y坐标
	private float initScale;// 刚开始的时候要把宽或高适应屏幕的宽高,此时的缩放比例

	public XlhZoomImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		init();
	}

	public XlhZoomImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mContext = context;
		init();
	}

	public XlhZoomImageView(Context context) {
		super(context);
		mContext = context;
		init();
	}

	/**
	 * 初始化控件
	 */
	private void init() {
		// 一定要设置成按矩阵方式缩放图片,不然肯定无效的,防止在xml中遗漏改属性,索性在这里统统设置一下
		this.setScaleType(ScaleType.MATRIX);
		mMatrix = new Matrix();
		this.setOnTouchListener(this);
		mDetector = new GestureDetector(mContext,
				new SimpleOnGestureListener() {
					/**
					 * 图片进行滑翔状态
					 */
					@Override
					public boolean onFling(MotionEvent e1, MotionEvent e2,
							float velocityX, float velocityY) {
						//暂不处理
						return super.onFling(e1, e2, velocityX, velocityY);
					}

					/**
					 * 对图片进行了双击
					 */
					@Override
					public boolean onDoubleTap(MotionEvent e) {
						boolean isScaled;// 已经是放大2倍
						if (getScale() > initScale * 1.5f) {
							// 如果已经放大了超过1.5倍就认为是放大了
							isScaled = true;
						} else {
							isScaled = false;
						}
						if (isScaled) {
							// 如果已经放大,双击当然就是要缩小了
							mMatrix.postScale(initScale / getScale(), initScale
									/ getScale(), e.getX(), e.getY());
							// 采用mMatrix.setScale(sx, sy, px,
							// py);方式发现缩放中心不是很准确,不知道什么原因,请大家一起探讨

						} else {
							mMatrix.postScale(2 * initScale / getScale(), 2
									* initScale / getScale(), e.getX(),
									e.getY());
						}
						updateView();
						checkBorderAndCenter();
						return true;
					}

				});
		mScaleGestureDetector = new ScaleGestureDetector(mContext, this);
		// 监听布局变化,我们只需要在刚开始的时候进行监听一下,把图片进行缩放和放到中心
		getViewTreeObserver().addOnGlobalLayoutListener(this);
	}

	/**
	 * 得到图片此时的缩放比例
	 * 
	 * @return 图片此时的缩放比例
	 */
	private float getScale() {
		float[] matrixValues = new float[9];
		mMatrix.getValues(matrixValues);
		return matrixValues[Matrix.MSCALE_X];
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		// 先让它处理双击滑翔事件,如果是双击直接返回
		if (mDetector.onTouchEvent(event)) {
			return true;
		}
		// 放大事件好像每次都能响应,所以不能像上面的双一样进行返回,因为还要处理按下,滑动事件
		mScaleGestureDetector.onTouchEvent(event);
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			break;
		case MotionEvent.ACTION_MOVE:
			
			mMatrix.postTranslate(event.getX() - lastX, event.getY() - lastY);
			break;
		case MotionEvent.ACTION_UP:

			break;

		}
		lastX = event.getX();
		lastY = event.getY();
		updateView();
		checkBorderAndCenter();
		return true;
	}

	@Override
	public boolean onScale(ScaleGestureDetector detector) {
		// 取出此次缩放手势的缩放比例
		float scaleFactor = detector.getScaleFactor();
		// 如果此时的缩放比例已经是最大或最小,肯定就不用再缩放啦
		if (getScale() < MIN_SCALE * initScale
				|| getScale() > MAX_SCALE * initScale) {
			return true;
		}
		// detector.getFocusY()是得到缩放时感兴趣的焦点Y坐标,x坐标同理。我们缩放图片时经常是想看图片的某一部分,比如MM的大眼睛or樱桃口~~
		mMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(),
				detector.getFocusY());

		updateView();
		checkBorderAndCenter();
		return true;
	}

	private void updateView() {
		this.setImageMatrix(mMatrix);
	}

	@Override
	public boolean onScaleBegin(ScaleGestureDetector detector) {
		return true;
	}

	// 缩放完成后要进行缩放比例的校验,不能大于最大缩放比或小于最小缩放比
	@Override
	public void onScaleEnd(ScaleGestureDetector detector) {
		if (getScale() >= MAX_SCALE * initScale) {

			mMatrix.postScale(MAX_SCALE * initScale / getScale(), MAX_SCALE
					* initScale / getScale(), detector.getFocusX(),
					detector.getFocusY());
		} else if (getScale() <= initScale) {
			mMatrix.postScale(initScale / getScale(), initScale / getScale(),
					detector.getFocusX(), detector.getFocusY());
		}
		updateView();

	}

	/**
	 * 根据矩阵得到图片所在的矩形,相当于给出了图片的边界
	 * 
	 * @return
	 */
	private RectF getMatrixRectF() {
		Matrix matrix = mMatrix;
		RectF rect = new RectF();
		Drawable d = getDrawable();
		if (null != d) {
			rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
			// 把这个矩阵应用到矩形,并把它写回转换后的矩形。这是通过改变矩形的4个角来完成的,然后将其设置为点的界限
			// 还有这个方法,也挺有用的matrix.mapRadius(radius)
			matrix.mapRect(rect);
		}
		return rect;
	}

	/**
	 * 图片放大要检查边界是否出界,缩小时要判断是否在中心
	 */
	private void checkBorderAndCenter() {
		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 * 0.5f - rect.right + 0.5f * rect.width();

		}
		if (rect.height() < height) {
			deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
		}
		mMatrix.postTranslate(deltaX, deltaY);
		updateView();
		if (rect.right <= getWidth() || rect.left >= 0) {
			// 滑动viewpager,让viewpager来处理
			getParent().requestDisallowInterceptTouchEvent(false);
		} else {
			// 滑动图片,自己来处理
			getParent().requestDisallowInterceptTouchEvent(true);
		}
		
	}

	/**
	 * 布局方式变化的时候调用,在这里我们只需要使用一次,就是刚进入的时候,图片是在左上角的, 我们要进行适度的缩放及平移,让他在中心
	 */
	@Override
	public void onGlobalLayout() {
		if (isFirst) {
			fixImage();
			isFirst = false;
		}
	}

	private void fixImage() {
		Drawable d = getDrawable();
		if (d == null)
			return;
		int width = getWidth();
		int height = getHeight();
		// 拿到图片的宽和高
		int dw = d.getIntrinsicWidth();
		int dh = d.getIntrinsicHeight();
		float scale = 1.0f;
		// 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高
		if (dw > width && dh <= height) {

			scale = width * 1.0f / dw;
		}
		if (dh > height && dw <= width) {
			scale = height * 1.0f / dh;
		}

		// 如果宽和高都大于屏幕,则让其按按比例适应屏幕大小
		if (dw > width && dh > height) {
			scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
		}
		// 如果宽和高都小于于屏幕,则让其按按比例适应屏幕大小
		if (dw < width && dh < height) {
			scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
		}
		initScale = scale;

		mMatrix.postTranslate((width - dw) / 2, (height - dh) / 2);
		mMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);
		// 图片移动至屏幕中心
		updateView();
		getParent().requestDisallowInterceptTouchEvent(false);
	}

}


代码并不是很多,我们一一来看

放大图片需要双击放大,或者缩小,只有放大状态下才能拖动,拖动到边缘就进入到下个图片(这里根据用户的需求,有的需求拖到边缘不能进入下个图片),那刚进入页面的时候,要把图片适应的宽和高,图片比屏幕小了显得有边缘,大了不能看全部,这当然都是不行的。

监听双击我们可以使用GestureDetector,里面传入SimpleOnGestureListener手势监听器,它里面有滑动,双击等事件的回调,不用我们再判断滑动的距离,或双击的时间差等,方便多了好多。

缩放手势我们可以使用ScaleGestureDetector来处理,它也为我们方便的提供了缩放的回调,我们可以方便的得到此次缩放的比例。

我们进入页面的时候需要getViewTreeObserver().addOnGlobalLayoutListener(this);来摆放图片的比例及位置,那必须是放到最中间了,这里有个回调方法
public void onGlobalLayout(),我们在这里判断如果是第一次进入,就得到屏幕和图片的实际宽高,计算出需要缩放的比例即可

这里有个非常重要方法,就是我们必须知道该图片的范围

/**
	 * 根据矩阵得到图片所在的矩形,相当于给出了图片的边界
	 * 
	 * @return
	 */
	private RectF getMatrixRectF() {
		Matrix matrix = mMatrix;
		RectF rect = new RectF();
		Drawable d = getDrawable();
		if (null != d) {
			rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
			// 把这个矩阵应用到矩形,并把它写回转换后的矩形。这是通过改变矩形的4个角来完成的,然后将其设置为点的界限
			// 还有这个方法,也挺有用的matrix.mapRadius(radius)
			matrix.mapRect(rect);
		}
		return rect;
	}
最后,我们的iamgeView大多数情况是放到viewpager中的,那事件的分发,拦截及处理我们必须深入的理解才能处理好冲突
我们在图片放大的状态且没超过屏幕范围的情况下,让iamgeview来处理,当没有放大或超过图片范围的情况下就交给viewpager来处理
getParent().requestDisallowInterceptTouchEvent(false);就可以控制父控件是不是拦截事件
试用下吧,oh,bug来了,拖动图片的时候偶尔会出异常,查了查资料终于找到了解决办法,我们不能直接用v4包中的viewpager,我们需要重写了
其实也简单,重写viewPager两个方法即可:

@Override
	public boolean onInterceptTouchEvent(MotionEvent arg0) {
		try {
			return super.onInterceptTouchEvent(arg0);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}
	@Override
	public boolean onTouchEvent(MotionEvent arg0) {
		try {
			return super.onTouchEvent(arg0);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

好了,就写到这吧,欢迎大家提出宝贵的修改意见,一起进步


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值