加了大部分注释,看注释应该可以明白基本的思路。欢迎大神留言拍砖,此文适合小白观看。
package com.example.imagedeal;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
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.ViewConfiguration;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
/*
* @OnGlobalLayoutListener 手势缩放监听
* @OnScaleGestureListener 获得控件宽高
* @OnTouchListener 触摸事件监听器
*/
public class CustomImage extends ImageView implements OnGlobalLayoutListener,OnScaleGestureListener,OnTouchListener{
private Matrix matrix;
private ScaleGestureDetector scaleGestureDetector;
private int touchSlop; //判断滑动距离的最小值,大于此值,才认为滑动
private GestureDetector gestureDetector;
public CustomImage(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
matrix = new Matrix();
scaleGestureDetector = new ScaleGestureDetector(context, this);
setOnTouchListener(this);
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
//@Focous实现此功能的第六步,双击缩小放大
gestureDetector = new GestureDetector(context, new SimpleOnGestureListener(){
@Override
public boolean onDoubleTap(MotionEvent e) {
float x = e.getX();
float y = e.getY();
float currScale = getCurrScale();
//缩小
if (currScale>=doubleScale) {
//matrix.postScale(defScale/currScale, defScale/currScale, x, y);
post(new SlowScale(x, y, defScale));
}else{
post(new SlowScale(x, y, doubleScale));
//matrix.postScale(doubleScale/currScale, doubleScale/currScale, x, y);
}
checkBorderByScale();
setImageMatrix(matrix);
return true;
}
});
}
/*
* 实现梯度缩放
*/
class SlowScale implements Runnable{
private float x,y,targetScale;
private float tempScale;//临时缩放比例
private static final float BIGGER = 1.05f;
private static final float SMALLER = 0.95f;
public SlowScale(float x, float y, float targetScale) {
this.x = x;
this.y = y;
this.targetScale = targetScale;
float scale = getCurrScale();
if (scale>=targetScale) {
tempScale = SMALLER;
}else{
tempScale = BIGGER;
}
}
@Override
public void run() {
matrix.postScale(tempScale, tempScale, x, y);
checkBorderByScale();
setImageMatrix(matrix);
if ((getCurrScale()>targetScale&&tempScale<1.0f)||
getCurrScale()<targetScale&&tempScale>1.0f) {
postDelayed(this, 20);
}else{
matrix.postScale(targetScale/getCurrScale(), targetScale/getCurrScale(), x, y);
checkBorderByScale();
setImageMatrix(matrix);
}
}
}
public CustomImage(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomImage(Context context) {
this(context,null);
}
/*@Focus 实现此功能的第一步
*@来源 实现View类就可以重写此方法
*@onAttachedToWindow() 在第一次调用onDraw方法之前调用,在此用来注册观察者类(getViewTreeObserver())
*@getViewTreeObserver() 时间的观察者,用来注册addOnGlobalLayoutListener监听器。实现对手势的监听
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);//详见http://blog.csdn.net/x1617044578/article/details/39668667
}
/*@Focus 实现此功能的第一步
*@来源 实现View类就可以重写此方法
*@onDetachedFromWindow() 在销毁View之后调用,做收尾工.在此用来取消注册观察者类(getViewTreeObserver())
*/
@SuppressWarnings("deprecation")
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
/*
* @isInited 是否要触摸 初始值为false
* @defScale 默认的缩放比率
* @doubleScale 双击缩放比率
* @maxScale 最大缩放比率
* @Focus 实现此功能的第二步,测量控件和图片的宽高,并且计算缩放率
*/
private boolean isInited; //是否要触摸
private int width,height,dw,dh;//with,height:控件的宽高 dw,dh图片的宽高
private float defScale,doubleScale,maxScale;
@Override
public void onGlobalLayout() {
if (!isInited) {
width = getWidth();
height = getHeight();
Drawable drawable = getDrawable();
if (drawable==null) {
return;
}
dw = drawable.getIntrinsicWidth();
dh = drawable.getIntrinsicHeight();
float scaleX = width*1.0f/dw; //X轴上的缩放量,即宽的缩放量
float scaleY = height*1.0f/dh; //Y轴上的缩放量,即高的缩放量
float scale = 1.0f; //初始的缩放量,1.0,即不缩放。
if ((dw>width&&dh>height)||(dw<width&&dh<height)) { //图片比屏幕大或者图片比屏幕小都进入
scale = Math.min(scaleX, scaleY); //缩小时,取小是因为不管哪种情况,说明最小的那个值对应的图片的属性最大,让它显示,属性小的那一方绝对会正确显示。放大时自己思考!
}else if(dw>width||dh>height){ //图片的一个属性比屏幕大都会进入此
scale = (scaleX>scaleY)?scaleY:scaleX; //缩放时取最小值保证dw与dh较大的那一方可以铺满屏幕,较小的留白显示,上面的也是一个效果。
}
defScale = scale;
doubleScale = defScale*2;
maxScale = defScale*4;
float dx = (width-dw)/2; //平移时X轴的中心点
float dy = (height-dh)/2; //平移时Y轴的中心点
matrix.postTranslate(dx, dy); //先平移
matrix.postScale(defScale, defScale, width/2, height/2); //再缩放
setImageMatrix(matrix);
isInited = true;
}
}
/*
* 获得当前的缩放比率
*/
private float getCurrScale(){
float[] values = new float[9];
matrix.getValues(values );
return values[Matrix.MSCALE_X];
}
/*
*@onScale() 缩放时时时调用此方法
*@URL http://blog.csdn.net/lmj623565791/article/details/39474553 对Matrix和缩放的应用介绍的还好
*/
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor(); //缩放前的缩放比率
float currScale = getCurrScale(); //当前的缩放比率
float scale = currScale*scaleFactor;
//当前的缩放值比最大缩放值小 想放大 当前的缩放值大于初始缩放值 想缩小
if ((currScale<maxScale&&scaleFactor>1.0f)||
(currScale>defScale&&scaleFactor<1.0f)) {
if (scale>maxScale) {
scaleFactor = maxScale/currScale; //放大时,控制缩放比率,不超过最大值
}
if (scale<defScale) {
scaleFactor = defScale/currScale;//缩小时,控制缩放比率,不超过最小值
}
matrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); //在detector.getFocusX()(X轴上的触摸焦点)点与在detector.getFocus()(轴上的触摸焦点)上进行缩放
checkBorderByScale(); //检查缩放时不能留白
setImageMatrix(matrix);
}
return true;
}
private RectF getMatrixRectF(){
RectF rectF = new RectF(0, 0, dw, dh);
matrix.mapRect(rectF);
return rectF;
}
/*
* @Focus 实现此功能的第三步,保证缩放时不越界,实现办法是平移图片至控件中心点
* @width 控件的宽
* @height 控件的高
*/
private void checkBorderByScale() {
RectF rectF = getMatrixRectF(); //将当前图像抽象为矩形,获得长宽
float dx=0 ,dy = 0; //X,Y轴上的平移量
//防止出现白边 缩小
if (rectF.width()>=width) {
//判断是左边和右边哪边越界
if (rectF.left>0) {
dx = -rectF.left;
}
if (rectF.right<width) {
dx = width - rectF.right;
}
}
//判断是上下哪边越界
if (rectF.height()>=height) {
if (rectF.top>0) {
dy = -rectF.top;
}
if (rectF.bottom<height) {
dy = height-rectF.bottom;
}
}
//图片居中
if (rectF.width()<width) {
dx = width/2f -rectF.right+rectF.width()/2f;
}
if (rectF.height()<height) {
dy = height/2f-rectF.bottom+rectF.height()/2f;
}
matrix.postTranslate(dx, dy);
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
// 返回true
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
/*
* @Focus 实现此功能的第四步,拖动
*/
private int prePointerCount; //触摸时前一次的触摸点的数
private float preX,preY; //触摸时前一次的X点和Y点的坐标
private boolean isDrag; //是否拖拽
private boolean checkLeftAndRight,checkTopAndBottom;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true; //阅读http://blog.csdn.net/tianfeng701/article/details/7556366
}
scaleGestureDetector.onTouchEvent(event);
int pointerCount = event.getPointerCount(); //获得触摸点的数
//记录中心点的坐标
float x =0,y = 0;
for(int i = 0 ; i<pointerCount;i++){
x+= event.getX(i);
y+= event.getY(i);
}
//计算触摸时的中心点
x/=pointerCount;
y/=pointerCount;
if (prePointerCount!=pointerCount) {
preX = x;
preY = y;
}
prePointerCount = pointerCount;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isDrag = false;
break;
case MotionEvent.ACTION_MOVE:
RectF rectF = getMatrixRectF();
float dx = x - preX;
float dy = y - preY;
if (!isDrag) {
checkLeftAndRight = checkTopAndBottom = true;
isDrag = checkIsDrag(dx,dy); //判断是否要开始拖拽
}
if (isDrag) {
if (rectF.width()<width) { //判断X方向有没有超过屏幕
checkLeftAndRight = false;
dx = 0;
}
if (rectF.height()<height) {//判断Y方向有没有超过屏幕
checkTopAndBottom = false;
dy = 0 ;
}
matrix.postTranslate(dx, dy);
checkBorderByTransLate(); //检查边框
setImageMatrix(matrix);
}
preX = x;
preY = y;
break;
case MotionEvent.ACTION_UP:
prePointerCount = 0; //松手时触摸点归零
break;
}
return true;
}
private void checkBorderByTransLate() {
RectF rectF = getMatrixRectF();
float dx=0,dy=0;
if (checkLeftAndRight&&rectF.left>0) {
dx = -rectF.left;
}
if (checkLeftAndRight&&rectF.right<width) {
dx = width - rectF.right;
}
if (checkTopAndBottom&&rectF.top>0) {
dy = -rectF.top;
}
if (checkTopAndBottom&&rectF.bottom<height) {
dy = height-rectF.bottom;
}
matrix.postTranslate(dx, dy);
}
private boolean checkIsDrag(float dx, float dy) {
return Math.sqrt(dx*dx+dy*dy)>touchSlop;
}
}
原文:http://my.oschina.net/u/2282721/blog/487788