Android实现图片多点触控自由伸缩

简介

作为Android开发者,我们经常需要自定义控件,比如下面我们说的实现图片的多点触控和伸缩释放,这也是由于用户已经有这样的常识了,那就是看见有图片的地方就可以点击查看大图,并且可以通过手指对图片进行伸缩和移动,如果应用没有实现这一点,那么对用户来说将会是很糟糕的体验,用户很“愤怒”。所以作为Android开发者,我们的任务就是让用户“爽”。哈哈哈。。。。下面我们将通过自定义ImageView实现以上功能。

涉及技术

一、Matrix(矩阵),Android是通过Matrix去控制图片的伸缩和平移的。

二、ScaleGestureDetector(伸缩手势探测器),实现对用户操作图片伸缩的监听。

实现原理

一、创建ZoomImageView类,通过重写ImageViewOnAttachedToWindow()注册全局布局监听器getViewTreeObserver().addOnGlobalLayoutListener(this),实现对图片的初始化控制

二、在全局布局监听器里面通过Matrix控制初始化图片居中显示和缩放。

三、注册onTouch()事件,通过代码mScaleGestureDetector.onTouchEvent(motionEvent) ;实现ScaleGestureDetector监听手势。

四、在ScaleGestureDetector的回调方法onScale(ScaleGestureDetector detector)里面实现对根据用户的操作,实现对图片的伸缩和移动。

 

代码

package com.liujun.liujunzoomimagedemo.view;

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

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

	private ScaleGestureDetector mScaleGestureDetector = null;

	private Matrix matrix;

	private float[] matrixValues = new float[9];// 矩阵的九个值

	private boolean isOnceLayout = true;

	// 设置缩放比
	private float initScale = 1.0f;
	private float midScale = 2.0f;
	private float maxScale = 4.0f;

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

		super(context, attrs, defStyle);

		super.setScaleType(ScaleType.MATRIX);// 设置图片通过矩阵控制

		// 实例化伸缩手势探测器
		mScaleGestureDetector = new ScaleGestureDetector(context, this);

		matrix = new Matrix();

		this.setOnTouchListener(this);

	}

	public ZoomImageView(Context context, AttributeSet attrs) {

		this(context, attrs, 0);
	}

	public ZoomImageView(Context context) {

		this(context, null);

	}

	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();

		// 注册全局布局监听器
		getViewTreeObserver().addOnGlobalLayoutListener(this);

	}

	@SuppressWarnings("deprecation")
	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();

		// 取消全局布局监听器
		getViewTreeObserver().removeGlobalOnLayoutListener(this);
	}

	/**
	 * 获取布局的参数(这个方法会调用两次)
	 */
	@Override
	public void onGlobalLayout() {

		if (isOnceLayout) {// 将图片居中显示,并且伸缩图片

			Drawable drawable = this.getDrawable();

			if (drawable == null) {
				return;
			}

			// 获取父控制的宽高
			int parentWidgetWidth = this.getWidth();
			int parentWidgetHeight = this.getHeight();

			// 获取图片的宽高
			int drawableHeight = drawable.getIntrinsicHeight();
			int drawableWidth = drawable.getIntrinsicWidth();

			// 定义缩放比
			float scale = 1.0f;

			// 当图片宽度大于父控件的宽度,当高度小于父控件高度时(缩小)
			if (drawableWidth > parentWidgetWidth && drawableHeight <= parentWidgetHeight) {

				scale = parentWidgetWidth * 1.0f / drawableWidth;

			}

			// 当图片高度高于父控件,但宽度小于父控件时(缩小)
			if (drawableHeight > parentWidgetHeight && drawableWidth <= parentWidgetWidth) {

				scale = parentWidgetHeight * 1.0f / drawableHeight;

			}

			// 当图片宽度和高度都大于父控件时(缩小)
			if (drawableHeight > parentWidgetHeight && drawableWidth > parentWidgetWidth) {

				scale = Math.min(parentWidgetHeight * 1.0f / drawableHeight, parentWidgetWidth * 1.0f / drawableWidth);

			}

			// 当图片宽度和高度都小于父控件(扩大)
			if (drawableHeight < parentWidgetHeight && drawableWidth < parentWidgetWidth) {

				scale = Math.min(parentWidgetHeight * 1.0f / drawableHeight, parentWidgetWidth * 1.0f / drawableWidth);

			}

			// 设置初始化的缩放比
			initScale = scale;

			// 将图片缩放并且将图片移动到父控件中间
			float dx = (parentWidgetWidth - drawableWidth) / 2;
			float dy = (parentWidgetHeight - drawableHeight) / 2;
			matrix.postTranslate(dx, dy);

			// 将图片缩放
			matrix.postScale(scale, scale, parentWidgetWidth / 2, parentWidgetHeight / 2);

			// 将矩阵运用到图片中
			this.setImageMatrix(matrix);

			isOnceLayout = false;

		}

	}

	/**
	 * 获取图片当前的缩放比
	 * 
	 * @return
	 */
	private float getCurrentImageScale() {

		matrix.getValues(matrixValues);

		return matrixValues[Matrix.MSCALE_X];

	}

	@Override
	public boolean onScale(ScaleGestureDetector detector) {

		// 获取图片当前的缩放比
		float currentScalse = getCurrentImageScale();

		// 拿到图片将要的缩放比例
		float scaleFactor = detector.getScaleFactor();

		if (this.getDrawable() == null) {
			return true;
		}

		// 用户将要放大图片或者用户将要缩小图片
		if ((scaleFactor > 1.0f && currentScalse < maxScale) || (scaleFactor < 1.0f && currentScalse > initScale)) {

			// 缩小时
			if (scaleFactor * currentScalse < initScale) {
				scaleFactor = initScale / currentScalse;
			}

			// 放大时
			if (scaleFactor * currentScalse > maxScale) {
				scaleFactor = maxScale / currentScalse;
			}

			matrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());

			// 检查边界和中心点
			checkBorderAndCenterWhenScale();

			setImageMatrix(matrix);

		}

		return true;

	}

	@Override
	public boolean onScaleBegin(ScaleGestureDetector detector) {

		return true;
	}

	@Override
	public void onScaleEnd(ScaleGestureDetector detector) {

	}

	/**
	 * 当在缩放的时候,对图片的边界和中心进行控制
	 */
	private void checkBorderAndCenterWhenScale() {

		// 获取当前缩放过程中的图片的矩形
		RectF rectF = getMatrixRectF();

		float deltaX = 0;
		float deltaY = 0;

		// 获取父控件的宽高
		int parentWidth = getWidth();
		int parentHeight = getHeight();

		// 如果宽度大于屏幕宽度
		if (rectF.width() >= parentWidth) {

			if (rectF.left > 0) {// 左边出现了空白

				deltaX = -rectF.left;// 往左移动

			}

			if (rectF.right < parentWidth) {// 右边出现了空白

				deltaX = parentWidth - rectF.right;// 往右移动

			}

		}

		// 如果高度大于屏幕高度
		if (rectF.height() >= parentHeight) {

			if (rectF.top > 0) {// 上边出现了空白

				deltaY = -rectF.top;// 往下移动

			}

			if (rectF.bottom < parentHeight) {// 下面出现了空白

				deltaY = parentHeight - rectF.bottom;// 往下移动

			}

		}

		// 如果宽度小于父控件的宽度
		if (rectF.width() < parentWidth) {// 要基中显示

			deltaX = parentWidth * 0.5f - rectF.right + 0.5f * rectF.width();

		}

		// 如果高度消息小于父控件的高度
		if (rectF.height() < parentHeight) {// 需要基中显示

			deltaY = parentHeight * 0.5f - rectF.bottom + 0.5f * rectF.height();

		}

		// 将图片移动到父控件中心
		matrix.postTranslate(deltaX, deltaY);

	}

	/**
	 * 获取图片通过矩阵控制缩放之后的矩形
	 * 
	 * @return
	 */
	private RectF getMatrixRectF() {

		Matrix matrix2 = matrix;

		RectF rectF = new RectF();

		Drawable drawable = this.getDrawable();

		if (drawable != null) {

			rectF.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());

			matrix2.mapRect(rectF);

		}

		return rectF;

	}

	@Override
	public boolean onTouch(View view, MotionEvent motionEvent) {

		// 用户缩放手机探测器处理触摸事件
		mScaleGestureDetector.onTouchEvent(motionEvent);

		return true;

	}

}
实现效果图

技术分享

总结

对图片的伸缩释放已经实现了,但是相信读者也能够知道,单单只实现多点触控自由伸缩是不够的,用户还希望可以左右滑动实现图片切换浏览,该部分的实现将在近期发布,尽情期待。


代码下载地址






郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。