由于项目需求的原因,最近一直在研究可缩放性ImageView,用本文来记录一下最近所学:
该ImageView的实现功能有:
1)初步打开时,图片按比例满屏(填充ImageView)显示。
2)在放大缩小过程中,可以控制最大放大比例和最小缩小比例。
3)在缩放过程中,若图片的宽或高小于ImageView,则在图片在宽或高居中显示。
4)在放大后,可以移动图片,并且限制好移动的边界,不会超出图片。
5)实现双击放大或缩小的功能。(若当前图片显示为最大的比例则缩小为最小比例,若不是最小比例则放大了最大比例)
在讲代码之前,首先应该说说一个类,Matrix。因为我们在处理图片的过程中,需要图片的位移,缩放等等,而Matrix刚好就是帮我们封装好了这些数据,具体的,大家可以看看这篇文章:android 从matrix获取处理过的图片的实际宽度重点是了解Matrix里面数组的含义。
上代码,具体的说明代码都有一一介绍:
MyZoomImageView.java文件:
- package com.xiaoyan.doubletouch;
- import android.content.Context;
- import android.graphics.Matrix;
- import android.graphics.PointF;
- import android.graphics.drawable.Drawable;
- import android.util.AttributeSet;
- import android.util.FloatMath;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.ViewTreeObserver;
- import android.view.ViewTreeObserver.OnGlobalLayoutListener;
- import android.widget.ImageView;
- /**
- * 缩放ImageView
- *
- * @author xiejinxiong
- *
- */
- public class MyZoomImageView extends ImageView {
- /** ImageView高度 */
- private int imgHeight;
- /** ImageView宽度 */
- private int imgWidth;
- /** 图片高度 */
- private int intrinsicHeight;
- /** 图片宽度 */
- private int intrinsicWidth;
- /** 最大缩放级别 */
- private float mMaxScale = 2.0f;
- /** 最小缩放级别 */
- private float mMinScale = 0.2f;
- /** 用于记录拖拉图片移动的坐标位置 */
- private Matrix matrix = new Matrix();
- /** 用于记录图片要进行拖拉时候的坐标位置 */
- private Matrix currentMatrix = new Matrix();
- /** 记录第一次点击的时间 */
- private long firstTouchTime = 0;
- /** 时间点击的间隔 */
- private int intervalTime = 250;
- /** 第一次点完坐标 */
- private PointF firstPointF;
- public MyZoomImageView(Context context) {
- super(context);
- initUI();
- }
- public MyZoomImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- initUI();
- }
- public MyZoomImageView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- // TODO Auto-generated constructor stub
- initUI();
- }
- /**
- * 初始化UI
- */
- private void initUI() {
- this.setScaleType(ScaleType.FIT_CENTER);
- this.setOnTouchListener(new TouchListener());
- getImageViewWidthHeight();
- getIntrinsicWidthHeight();
- }
- /**
- * 获得图片内在宽高
- */
- private void getIntrinsicWidthHeight() {
- Drawable drawable = this.getDrawable();
- // 初始化bitmap的宽高
- intrinsicHeight = drawable.getIntrinsicHeight();
- intrinsicWidth = drawable.getIntrinsicWidth();
- }
- private final class TouchListener implements OnTouchListener {
- /** 记录是拖拉照片模式还是放大缩小照片模式 */
- private int mode = 0;// 初始状态
- /** 拖拉照片模式 */
- private static final int MODE_DRAG = 1;
- /** 放大缩小照片模式 */
- private static final int MODE_ZOOM = 2;
- /** 用于记录开始时候的坐标位置 */
- private PointF startPoint = new PointF();
- /** 两个手指的开始距离 */
- private float startDis;
- /** 两个手指的中间点 */
- private PointF midPoint;
- public boolean onTouch(View v, MotionEvent event) {
- /** 通过与运算保留最后八位 MotionEvent.ACTION_MASK = 255 */
- switch (event.getAction() & MotionEvent.ACTION_MASK) {// 单点监听和多点触碰监听
- // 手指压下屏幕
- case MotionEvent.ACTION_DOWN:
- mode = MODE_DRAG;
- // 记录ImageView当前的移动位置
- currentMatrix.set(getImageMatrix());
- startPoint.set(event.getX(), event.getY());
- matrix.set(currentMatrix);
- makeImageViewFit();
- break;
- // 手指在屏幕上移动,改事件会被不断触发
- case MotionEvent.ACTION_MOVE:
- // 拖拉图片
- if (mode == MODE_DRAG) {
- // System.out.println("ACTION_MOVE_____MODE_DRAG");
- float dx = event.getX() - startPoint.x; // 得到x轴的移动距离
- float dy = event.getY() - startPoint.y; // 得到x轴的移动距离
- // 在没有移动之前的位置上进行移动z
- matrix.set(currentMatrix);
- float[] values = new float[9];
- matrix.getValues(values);
- dx = checkDxBound(values, dx);
- dy = checkDyBound(values, dy);
- matrix.postTranslate(dx, dy);
- }
- // 放大缩小图片
- else if (mode == MODE_ZOOM) {
- float endDis = distance(event);// 结束距离
- if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
- float scale = endDis / startDis;// 得到缩放倍数
- matrix.set(currentMatrix);
- float[] values = new float[9];
- matrix.getValues(values);
- scale = checkFitScale(scale, values);
- matrix.postScale(scale, scale, midPoint.x, midPoint.y);
- }
- }
- break;
- // 手指离开屏幕
- case MotionEvent.ACTION_UP:
- setDoubleTouchEvent(event);
- case MotionEvent.ACTION_POINTER_UP:
- // System.out.println("ACTION_POINTER_UP");
- mode = 0;
- // matrix.set(currentMatrix);
- float[] values = new float[9];
- matrix.getValues(values);
- makeImgCenter(values);
- break;
- // 当屏幕上已经有触点(手指),再有一个触点压下屏幕
- case MotionEvent.ACTION_POINTER_DOWN:
- // System.out.println("ACTION_POINTER_DOWN");
- mode = MODE_ZOOM;
- /** 计算两个手指间的距离 */
- startDis = distance(event);
- /** 计算两个手指间的中间点 */
- if (startDis > 10f) { // 两个手指并拢在一起的时候像素大于10
- midPoint = mid(event);
- // 记录当前ImageView的缩放倍数
- currentMatrix.set(getImageMatrix());
- }
- break;
- }
- setImageMatrix(matrix);
- return true;
- }
- /** 计算两个手指间的距离 */
- private float distance(MotionEvent event) {
- float dx = event.getX(1) - event.getX(0);
- float dy = event.getY(1) - event.getY(0);
- /** 使用勾股定理返回两点之间的距离 */
- return FloatMath.sqrt(dx * dx + dy * dy);
- }
- /** 计算两个手指间的中间点 */
- private PointF mid(MotionEvent event) {
- float midX = (event.getX(1) + event.getX(0)) / 2;
- float midY = (event.getY(1) + event.getY(0)) / 2;
- return new PointF(midX, midY);
- }
- /**
- * 和当前矩阵对比,检验dy,使图像移动后不会超出ImageView边界
- *
- * @param values
- * @param dy
- * @return
- */
- private float checkDyBound(float[] values, float dy) {
- float height = imgHeight;
- if (intrinsicHeight * values[Matrix.MSCALE_Y] < height)
- return 0;
- if (values[Matrix.MTRANS_Y] + dy > 0)
- dy = -values[Matrix.MTRANS_Y];
- else if (values[Matrix.MTRANS_Y] + dy < -(intrinsicHeight
- * values[Matrix.MSCALE_Y] - height))
- dy = -(intrinsicHeight * values[Matrix.MSCALE_Y] - height)
- - values[Matrix.MTRANS_Y];
- return dy;
- }
- /**
- * 和当前矩阵对比,检验dx,使图像移动后不会超出ImageView边界
- *
- * @param values
- * @param dx
- * @return
- */
- private float checkDxBound(float[] values, float dx) {
- float width = imgWidth;
- if (intrinsicWidth * values[Matrix.MSCALE_X] < width)
- return 0;
- if (values[Matrix.MTRANS_X] + dx > 0)
- dx = -values[Matrix.MTRANS_X];
- else if (values[Matrix.MTRANS_X] + dx < -(intrinsicWidth
- * values[Matrix.MSCALE_X] - width))
- dx = -(intrinsicWidth * values[Matrix.MSCALE_X] - width)
- - values[Matrix.MTRANS_X];
- return dx;
- }
- /**
- * MSCALE用于处理缩放变换
- *
- *
- * MSKEW用于处理错切变换
- *
- *
- * MTRANS用于处理平移变换
- */
- /**
- * 检验scale,使图像缩放后不会超出最大倍数
- *
- * @param scale
- * @param values
- * @return
- */
- private float checkFitScale(float scale, float[] values) {
- if (scale * values[Matrix.MSCALE_X] > mMaxScale)
- scale = mMaxScale / values[Matrix.MSCALE_X];
- if (scale * values[Matrix.MSCALE_X] < mMinScale)
- scale = mMinScale / values[Matrix.MSCALE_X];
- return scale;
- }
- /**
- * 促使图片居中
- *
- * @param values
- * (包含着图片变化信息)
- */
- private void makeImgCenter(float[] values) {
- // 缩放后图片的宽高
- float zoomY = intrinsicHeight * values[Matrix.MSCALE_Y];
- float zoomX = intrinsicWidth * values[Matrix.MSCALE_X];
- // 图片左上角Y坐标
- float leftY = values[Matrix.MTRANS_Y];
- // 图片左上角X坐标
- float leftX = values[Matrix.MTRANS_X];
- // 图片右下角Y坐标
- float rightY = leftY + zoomY;
- // 图片右下角X坐标
- float rightX = leftX + zoomX;
- // 使图片垂直居中
- if (zoomY < imgHeight) {
- float marY = (imgHeight - zoomY) / 2.0f;
- matrix.postTranslate(0, marY - leftY);
- }
- // 使图片水平居中
- if (zoomX < imgWidth) {
- float marX = (imgWidth - zoomX) / 2.0f;
- matrix.postTranslate(marX - leftX, 0);
- }
- // 使图片缩放后上下不留白(即当缩放后图片的大小大于imageView的大小,但是上面或下面留出一点空白的话,将图片移动占满空白处)
- if (zoomY >= imgHeight) {
- if (leftY > 0) {// 判断图片上面留白
- matrix.postTranslate(0, -leftY);
- }
- if (rightY < imgHeight) {// 判断图片下面留白
- matrix.postTranslate(0, imgHeight - rightY);
- }
- }
- // 使图片缩放后左右不留白
- if (zoomX >= imgWidth) {
- if (leftX > 0) {// 判断图片左边留白
- matrix.postTranslate(-leftX, 0);
- }
- if (rightX < imgWidth) {// 判断图片右边不留白
- matrix.postTranslate(imgWidth - rightX, 0);
- }
- }
- }
- }
- /**
- * 获取ImageView的宽高
- */
- private void getImageViewWidthHeight() {
- ViewTreeObserver vto2 = getViewTreeObserver();
- vto2.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
- @SuppressWarnings("deprecation")
- public void onGlobalLayout() {
- getViewTreeObserver().removeGlobalOnLayoutListener(this);
- imgWidth = getWidth();
- imgHeight = getHeight();
- }
- });
- }
- /**
- * 使得ImageView一开始便显示最适合的宽高比例,便是刚好容下的样子
- */
- private void makeImageViewFit() {
- if (getScaleType() != ScaleType.MATRIX) {
- setScaleType(ScaleType.MATRIX);
- matrix.postScale(1.0f, 1.0f, imgWidth / 2, imgHeight / 2);
- }
- }
- /**
- * 双击事件触发
- *
- * @param values
- */
- private void setDoubleTouchEvent(MotionEvent event) {
- float values[] = new float[9];
- matrix.getValues(values);
- // 存储当前时间
- long currentTime = System.currentTimeMillis();
- // 判断两次点击间距时间是否符合
- if (currentTime - firstTouchTime >= intervalTime) {
- firstTouchTime = currentTime;
- firstPointF = new PointF(event.getX(), event.getY());
- } else {
- // 判断两次点击之间的距离是否小于30f
- if (Math.abs(event.getX() - firstPointF.x) < 30f
- && Math.abs(event.getY() - firstPointF.y) < 30f) {
- // 判断当前缩放比例与最大最小的比例
- if (values[Matrix.MSCALE_X] < mMaxScale) {
- matrix.postScale(mMaxScale / values[Matrix.MSCALE_X],
- mMaxScale / values[Matrix.MSCALE_X], event.getX(),
- event.getY());
- } else {
- matrix.postScale(mMinScale / values[Matrix.MSCALE_X],
- mMinScale / values[Matrix.MSCALE_X], event.getX(),
- event.getY());
- }
- }
- }
- }
- /**
- * 设置图片的最大和最小的缩放比例
- *
- * @param mMaxScale
- * @param mMinScale
- */
- public void setPicZoomHeightWidth(float mMaxScale, float mMinScale) {
- this.mMaxScale = mMaxScale;
- this.mMinScale = mMinScale;
- }
- }
- <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"
- tools:context="com.xiaoyan.doubletouch.MainActivity" >
- <com.xiaoyan.doubletouch.MyZoomImageView
- android:id="@+id/imageView"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:src="@drawable/a" />
- </RelativeLayout>
具体实现效果:
最初显示:
放大显示: