实现图片的缩放并不难,主要需要一些计算和对图片的平移及缩放操作
主布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.zoomimageview.MainActivity" > <com.view.ZoomImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="matrix" android:src="@drawable/m3" /> </LinearLayout>
上面使用自定义View
如下:
package com.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.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.ImageView; import android.widget.SectionIndexer; public class ZoomImageView extends ImageView implements OnGlobalLayoutListener, OnScaleGestureListener, OnTouchListener { private boolean once; // 缩放得最小值 private float minScale; // 双击放大值 private float doubleTouch; // 缩放最大值 private float maxScale; private Matrix matrix; // 控件的宽高 private float width; private float height; private ScaleGestureDetector scaleGestureDetector; public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); matrix = new Matrix(); setScaleType(ScaleType.MATRIX); scaleGestureDetector = new ScaleGestureDetector(context, this); setOnTouchListener(this); } public ZoomImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ZoomImageView(Context context) { this(context, null); } public void onGlobalLayout() { if (!once) { // 得到控件的宽和高 width = getWidth(); height = getHeight(); // 得到资源的宽和高 Drawable drawable = getDrawable(); int imgWidth = drawable.getIntrinsicWidth(); int imgHeight = drawable.getIntrinsicHeight(); float scale = 1.0f; if (imgWidth > width && imgHeight < height) { scale = width * 1.0f / imgWidth; } if (imgWidth < width && imgHeight > height) { scale = height * 1.0f / imgHeight; } if ((imgWidth > width && imgHeight > height) || (imgWidth < width && imgHeight < height)) { scale = Math.min(width * 1.0f / imgWidth, height * 1.0f / imgHeight); } minScale = scale; maxScale = scale * 4; // 偏移量 float dx = width * 1 / 2 - imgWidth * 1 / 2; float dy = height * 1 / 2- imgHeight * 1 / 2; // 将图片移动到控件的中心 matrix.postTranslate(dx, dy); // 缩放 matrix.postScale(minScale, minScale, width * 1 / 2, height * 1 / 2); setImageMatrix(matrix); once = true; } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getViewTreeObserver().removeGlobalOnLayoutListener(this); } // 取得缩放值 private float getScale() { float values[] = new float[9]; matrix.getValues(values); return values[Matrix.MSCALE_X]; } // 将缩放的图片放到矩形中 来获得缩放之后图片的宽高 private RectF getMatrixRectF() { Matrix newmMatrix = matrix; RectF rectf = new RectF(); Drawable drawable = getDrawable(); if (drawable != null) { rectf.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); newmMatrix.mapRect(rectf); } return rectf; } // 对缩放的位置及边界控制 private void controlScaleImgState() { // 边界空白消除: RectF rectf = getMatrixRectF(); float transX = 0; float transY = 0; if (width <= rectf.width()) { if (rectf.left > 0) { transX = -rectf.left; } if (rectf.right < width) { transX = width - rectf.right; } } if (height <= rectf.height()) { if (rectf.top > 0) { transY = rectf.top; } if (rectf.bottom < height) { transY = height - rectf.bottom; } } // 控制居中 当图片小于屏幕 if (width > rectf.width()) { transX = width * 1 / 2 - rectf.right + rectf.width() * 1 / 2; } if (height > rectf.height()) { transY = height * 1 / 2 - rectf.bottom + rectf.height() * 1 / 2; } //设置平移 matrix.postTranslate(transX, transY); } // onScaleGestureListener 需要复写的方法 public boolean onScale(ScaleGestureDetector detector) { // 取得当前缩放值 float scale = getScale(); // 取得根据手指判断的缩放值 float scaleFactor = detector.getScaleFactor(); if (getDrawable() == null) { return true; } // 如果在放大 当前缩放值不大于最大缩放值 或者 如果在缩小 当前缩放值不小于最小缩放值 if ((scale < maxScale && scaleFactor > 1.0f) || (scale > minScale && scaleFactor < 1.0f)) { // 如果乘积大于最大值 则设为最大值 if (scale * scaleFactor > maxScale) { scaleFactor = maxScale / scale; } // 如果乘积小于最小值 则设为最小值 if (scale * scaleFactor < minScale) { scaleFactor = minScale / scale; } controlScaleImgState(); matrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusX()); setImageMatrix(matrix); } return true; } public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } public void onScaleEnd(ScaleGestureDetector detector) { } // onTouchListener 复写的方法 public boolean onTouch(View v, MotionEvent event) { scaleGestureDetector.onTouchEvent(event); return true; } }
说一下上面实现的大致过程:
1、首先继承了ImageView 实现了 OnGlobalLayoutListener 接口
为什么要实现OnGlobalLayoutListener:
当一个视图树的布局发生改变时,可以被ViewTreeObserver监听到, 这是一个注册监听视图树的观察者(observer),在视图树的全局事件改变时得到通知。ViewTreeObserver不能直接实例化,而是通过 getViewTreeObserver()获得。 在oncreate中View.getWidth和View.getHeight无法获得一个view的高度和宽度,这是因为View组件布局要 在onResume回调后完成。所以现在需要使用getViewTreeObserver().addOnGlobalLayoutListener() 来获得宽度或者高度。这是获得一个view的宽度和高度的方法之一。
2、之后又实现了 OnScaleGestureListener 和 OnTouchListener 接口
说一下它们的作用:
为View创建scaleGestureDetector,它会提供多点触摸在的手势变化信息,实例化它时需要OnScaleGestureListener 作为参数。callback方法ScaleGestureDetector.OnScaleGestureListener 会在特定手势事件时发出通知。而该类需要和Touch事件引发的MotionEvent配合使用。所以需要实现OnTouchListener 接口,来侦测多点触控。
因此,需要在public boolean onTouch(View v, MotionEvent event) 方法中
调用scaleGestureDetector.onTouchEvent(event) 将MotionEvent 传出;
3、onGlobalLayout()
实现OnTouchListener 接口时,需要复写onGlobalLayout() ,在这个方法中计算了缩放值和平移值
来保证初始化视图的大小及位置
4、onAttachedToWindow()和onDetachedFromWindow()
用来添加和移除OnGlobalLayoutListener(this)
5、onScale(ScaleGestureDetector detector)
onScale 、onScaleBegin 、onScaleEnd是实现OnScaleGestureListener接口需要复写的
根据detector.getScaleFactor()所返回的值来进行缩放。