实现图片的缩放并不难,主要需要一些计算和对图片的平移及缩放操作
主布局:
<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()所返回的值来进行缩放。