手把手教你打造ImageView支持手势放大缩小

时间:2023-02-20 20:17:15

写在前面

最近有了新的任务,学习的时间比以前少了不少,Java回炉的文估计是得缓缓了,不过每周一篇尽量保质保量。最近感觉我文写的有点不好,因为我写东西除非必要,不然概念性的东西我基本上都是一笔带过……最近感觉这对看我文的人好像不是很友好,恩,我决定改一改,尽量写的详细而有趣一些。

手把手教你打造ImageView支持手势放大缩小

好了废话时间过了,前面也说了最近有了新任务,我现在是搞定用户信息这一块。一般来说现在用户都会有个头像什么的,光有个头像还不够,你还得能点击看个大图吧?光看个大图也不够啊,不说多的,你最起码得支持用户手势放大缩小什么的吧?当时脑海里第一个想到的是PhotoView,不过整个项目好像也只有这一块涉及到用户手势放大缩小,算了,自己实现一个吧。

实现思路

做一个东西之前我们肯定要分析需求,分析完之后我们就可以利用我们会的,或者知道可以实现但是现在不会的去尝试解决这个需求。放大缩小图片,脑子里第一个反应就是矩阵,Android里貌似有个可以通过矩阵处理图像的东西,不过说真的,以前也没有用过几次,不过好歹有个想法了。至于让图片跟随用户手势放大缩小,肯定是需要支持手势检测了。恩,我的思路暂时就是这样了,接下来先去了解一下手势检测。

手势检测

当用户触摸屏幕时,会产生许多手势,down、up、scroll、fling等。一般情况下我们通过实现OnTouchListener是可以满足我们处理一般手势的需求的,说实话,实现手势放大缩小的ImageView是可以通过自己在OnTouch方法里面处理距离,滑动什么的去算缩放的。但是人总是要对自己好一点,如果有更简单的实现方式为什么不用呢?Android中提供了GestureDetector给程序员去判断不同的手势。另外也提供了 ScaleGestureDetector 来检测缩放手势。虽然后者很像前者的子类,但事实上并不是,后者也是一个独立的类。下面用一个简单的demo来演示一下这两者的触发。

package com.example.luo_pc.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Button;
import android.widget.Toast; public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener,
View.OnClickListener, ScaleGestureDetector.OnScaleGestureListener { //定义手势检测
GestureDetector detector = null;
//缩放检测
ScaleGestureDetector scDetector = null; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button testGet = (Button) findViewById(R.id.bt_test_ges);
Button testScges = (Button) findViewById(R.id.bt_test_scges);
testGet.setOnClickListener(this);
testScges.setOnClickListener(this);
detector = new GestureDetector(this, this);
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_test_ges:
detector = new GestureDetector(this, this);
scDetector = null;
break;
case R.id.bt_test_scges:
scDetector = new ScaleGestureDetector(this, this);
detector = null;
break;
}
} //-------------------------implement OnGestureListener's method-----------------------// @Override
public boolean onTouchEvent(MotionEvent me) {
if (detector != null)
return detector.onTouchEvent(me);
else
return scDetector.onTouchEvent(me);
} //用户按下屏幕就会触发
@Override
public boolean onDown(MotionEvent arg0) {
Toast.makeText(this, "onDown", Toast.LENGTH_SHORT).show();
return false;
} //用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN,
//多个ACTION_MOVE, 1个ACTION_UP触发
//e1:第1个ACTION_DOWN MotionEvent
//e2:最后一个ACTION_MOVE MotionEvent
//velocityX:X轴上的移动速度,像素/秒
//velocityY:Y轴上的移动速度,像素/秒
@Override
public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2,float arg3) {
Toast.makeText(this, "onFling", Toast.LENGTH_SHORT).show();
return false;
} //用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发
@Override
public void onLongPress(MotionEvent arg0) {
Toast.makeText(this, "onLongPress", Toast.LENGTH_SHORT).show();
} //用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
@Override
public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2,
float arg3) {
Toast.makeText(this, "onScroll", Toast.LENGTH_SHORT).show();
return false;
} //如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,
// 那么onShowPress就会执行
@Override
public void onShowPress(MotionEvent arg0) {
Toast.makeText(this, "onShowPress", Toast.LENGTH_SHORT).show();
} //用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
@Override
public boolean onSingleTapUp(MotionEvent arg0) {
Toast.makeText(this, "onSingleTapUp", Toast.LENGTH_SHORT).show();
return true;
} //-----------------------implement OnScaleGestureListener's method----------------------// @Override
public boolean onScale(ScaleGestureDetector detector) {
Toast.makeText(MainActivity.this, "onScale", Toast.LENGTH_SHORT).show();
return true;
} @Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
Toast.makeText(MainActivity.this, "onScaleBegin", Toast.LENGTH_SHORT).show();
return true;
} @Override
public void onScaleEnd(ScaleGestureDetector detector) {
Toast.makeText(MainActivity.this, "onScaleEnd", Toast.LENGTH_SHORT).show();
}
}

图方便,我将整个MainActivity搬上来了,你可以直接复制,然后加上对应的布局和导包就行了,接下来看一下运行现象。

手把手教你打造ImageView支持手势放大缩小

上面测试的是GestureDetector,接下来测试一下ScaleGestureDetector

手把手教你打造ImageView支持手势放大缩小

如果你想要测试更多,比如GestureDetector里另外一个接口可以把我的代码复制一下改一改就好了,这了就不作过多的赘述了,代码会说话。

Matrix

这里只对Matrix作简单的介绍。Android中Matrix是一个3 x 3的矩阵(说到矩阵都是二维的,不要看到3 x 3就想到3维去了)。先看一下Matrix的getValues和setValues方法:

    /** Copy 9 values from the matrix into the array.
*/
public void getValues(float[] values) {
if (values.length < 9) {
throw new ArrayIndexOutOfBoundsException();
}
native_getValues(native_instance, values);
} /** Copy 9 values from the array into the matrix.
Depending on the implementation of Matrix, these may be
transformed into 16.16 integers in the Matrix, such that
a subsequent call to getValues() will not yield exactly
the same values.
*/
public void setValues(float[] values) {
if (values.length < 9) {
throw new ArrayIndexOutOfBoundsException();
}
native_setValues(native_instance, values);
}

得到或者设置一个有9个元素的数组,继续往下看发现调用的是个native修饰方法,好吧,不继续看了,了解以上也差不多够了。其内部有

手把手教你打造ImageView支持手势放大缩小

Matrix的对图像的处理可分为四类基本变换:
Translate 平移变换
Rotate 旋转变换
Scale 缩放变换
Skew 错切变换

从字面上理解,矩阵中的MSCALE用于处理缩放变换,MSKEW用于处理错切变换,MTRANS用于处理平移变换,MPERSP用于处理透视变换。实际中当然不能完全按照字面上的说法去理解Matrix。

从字面上理解那9个量,什么X轴缩放,什么扭曲,什么X轴偏移量,还带不认识的,没关系,我们现在做的操作比较简单,不需要用到那么多的参数。比如我们现在想设置偏移量(200,200)
我们可以

Matrix matrix = new Matrix();
martrix.postTranslate(200,200);

实践

写完上面的东西,我已经差不多是个废人了……

毕竟当年线性代数学的不咋滴,加上之前虽然有用过Matrix但是并不是很多,接下来进入喜闻乐见的实战时间。首先是不加任何限制,直接实现

package com.example.luo_pc.view.CustomView;

/**
* Created by Luo_xiasuhuei321@163.com on 2016/9/24.
* desc:
*/ import android.content.Context;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.ImageView; public class ZZoomImageView extends ImageView implements View.OnTouchListener, ScaleGestureDetector.OnScaleGestureListener {
//suppress the unused warning because maybe it will be used sometime later
@SuppressWarnings("unused")
private static final String TAG = "ZZoomImageView"; /**
* 最大放大倍数
*/
// public static final float SCALE_MAX = 4.0f; /**
* 默认缩放
*/
// private float initScale = 1.0f; /**
* 手势检测
*/
ScaleGestureDetector scaleGestureDetector = null; Matrix scaleMatrix = new Matrix(); /**
* 处理矩阵的9个值
*/
// float[] martixValue = new float[9]; public ZZoomImageView(Context context) {
this(context, null);
} public ZZoomImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public ZZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setScaleType(ScaleType.MATRIX);
scaleGestureDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(this);
} /**
* 获取当前缩放比例
*/
// public float getScale() {
// scaleMatrix.getValues(martixValue);
// return martixValue[Matrix.MSCALE_X];
// } //--------------------------implement OnTouchListener----------------------------// @Override
public boolean onTouch(View v, MotionEvent event) {
return scaleGestureDetector.onTouchEvent(event);
} //----------------------implement OnScaleGestureListener------------------------// @Override
public boolean onScale(ScaleGestureDetector detector) {
// float scale = getScale();
float scaleFactor = detector.getScaleFactor();
if (getDrawable() == null)
return true;
// Log.e(TAG,"君甚咸,此鱼何能及君也?");
// if (scaleFactor * scale < initScale)
// scaleFactor = initScale / scale;
// if (scaleFactor * scale > SCALE_MAX)
// scaleFactor = SCALE_MAX / scale;
//设置缩放比例
scaleMatrix.postScale(scaleFactor, scaleFactor, getWidth() / 2, getHeight() / 2);
setImageMatrix(scaleMatrix);
return true;
} @Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
} @Override
public void onScaleEnd(ScaleGestureDetector detector) { }
}

看一下跑起来是啥样的

手把手教你打造ImageView支持手势放大缩小

将图片放到中心

嗯,我要是把这个用在项目里,老大要是看到了估计我就没有以后了……首先,是没有限制,可以无限缩小放大,第二是缩放中心点,默认都是ImageView中心,最后是刚开始加载出来我的图片有部分没加载,而且图片不在imageview的中心!我ImageView设置的可是俩match_parent啊。

手把手教你打造ImageView支持手势放大缩小

有问题没事,我们一样一样,慢慢解决。首先是图片位置,图片位置的设定我们可以在图片加载的时候将他放到ImageView的中心去,同样在这个过程中,我们可以判断图片的大小,如果图片大于ImageView尺寸则将其大小调整至ImageView的大小。首先我们在ImageView的构造器中可能是无法获取到ImageView和图片的真实尺寸的,我们可以通过ViewTreeObserver在布局完成可以获取真实尺寸的时候完成对图片的调整。而OnGlobalLayoutListener是ViewTreeObserver的内部接口,当一个视图树的布局发生改变时,可以被ViewTreeObserver监听到。所以新增代码如下:

/**
* 首先让我们的类实现OnGlobalLayoutListener接口
public class ZZoomImageView extends ImageView implements View.OnTouchListener, ScaleGestureDetector.OnScaleGestureListener,
ViewTreeObserver.OnGlobalLayoutListener

然后我们在此控件的onAttachedToWindow中设置监听,在onDetachedFromWindow移除这个监听:

    @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
} //suppress deprecate warning because i have dealt with it
@Override
@SuppressWarnings("deprecation")
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}

最后是最重要的,在回调中对图片进行处理:

    @Override
public void onGlobalLayout() {
if (!once)
return;
Drawable d = getDrawable();
if (d == null)
return;
//获取imageview宽高
int width = getWidth();
int height = getHeight(); //获取图片宽高
int imgWidth = d.getIntrinsicWidth();
int imgHeight = d.getIntrinsicHeight(); float scale = 1.0f; //如果图片的宽或高大于屏幕,缩放至屏幕的宽或者高
if (imgWidth > width &amp;&amp; imgHeight <= height)
scale = (float) width / imgWidth;
if (imgHeight > height &amp;&amp; imgWidth <= width)
scale = (float) height / imgHeight;
//如果图片宽高都大于屏幕,按比例缩小
if (imgWidth > width &amp;&amp; imgHeight > height)
scale = Math.min((float) imgWidth / width, (float) imgHeight / height);
Log.e(TAG, "scale" + scale);
//将图片移动至屏幕中心
scaleMatrix.postTranslate((width - imgWidth) / 2, (height - imgHeight) / 2);
scaleMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);
setImageMatrix(scaleMatrix);
once = false;
}

对图片的处理核心思想就是判断图片尺寸和当前控件尺寸,图片尺寸比控件大,就对图片进行缩放处理,并且最后将图片移动至控件中心处。代码上的注释写的都很详细了,各位看官可以自行阅读。现在来看看变成啥样了

手把手教你打造ImageView支持手势放大缩小

限制缩放

很好图是到中间去了,那现在的问题就是无限缩小和放大的问题。这个问题解决思路是很简单的,做个限制就行了。

嗯,新增如下几个变量:

    /**
* 最大放大倍数
*/
public static final float SCALE_MAX = 4.0f; /**
* 默认缩放
*/
private float initScale = 1.0f; /**
* 处理矩阵的9个值
*/
float[] martixValue = new float[9];

上面费了那么多口水讲到的matrix的九个值啥的,终于要出现了,是不是很激动~(才怪),接下来搞个方法获取缩放比例

    /**
* 获取当前缩放比例
*/
public float getScale() {
scaleMatrix.getValues(martixValue);
return martixValue[Matrix.MSCALE_X];
}

之后为了获取正确的初始缩放比例,在我们刚刚写的 onGlobalLayout 中加句话:

initScale = scale;

当然了,得是在获取了scale值之后再添,因为我们虽然设置了初始缩放比例,但是实际中可能因为图片大小发生了缩放行为,所以我们需要再次确定初始缩放比例。接下来就是对缩放行为进行限制了,修改onScale代码如下:

    @Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = getScale();
Log.e(TAG, "matrix scale---->" + scale);
float scaleFactor = detector.getScaleFactor();
Log.e(TAG, "scaleFactor---->" + scaleFactor);
if (getDrawable() == null)
return true;
// Log.e(TAG,"君甚咸,此鱼何能及君也?");
if ((scale < SCALE_MAX &amp;&amp; scaleFactor > 1.0f)
|| (scale > initScale &amp;&amp; scaleFactor < 1.0f)) {
if (scaleFactor * scale < initScale)
scaleFactor = initScale / scale;
if (scaleFactor * scale > SCALE_MAX)
scaleFactor = SCALE_MAX / scale;
Log.e(TAG, "scaleFactor2---->" + scaleFactor);
//设置缩放比例
scaleMatrix.postScale(scaleFactor, scaleFactor, getWidth() / 2, getHeight() / 2);
setImageMatrix(scaleMatrix);
}
return true;
}

对于以上的代码,你可能会对两个scale有所疑惑,一个scale是从matrix中获得的,一个是从缩放检测中获得的。开始我看到hongyang大神的这段代码我也是有所疑惑的,但是之后我自己写了一遍,打了一下log,发现前一个在到达我们设置的最大值时,值便会固定为4,后一个值会在1左右。那么很明显前一个值是图片相对于初始尺寸的缩放,后一个是每一次缩放的实际比例。理解了这个之后便容易解决了,使用如上代码便可以限制缩放了。如果你对于缩放比例不满意,嗯,自己设置就是了,反正也不复杂。效果图就等下一个功能一起实现再放了。

以上一个简单,还算能用的缩放ImageView就完成了,现在的问题是缩放中心是控件的中心,如果我想设置缩放中心是我按下去的地方呢?很简单改一句代码:

scaleMatrix.postScale(scaleFactor, scaleFactor,
getWidth() / 2, getHeight() / 2); scaleMatrix.postScale(scaleFactor, scaleFactor,
detector.getFocusX(), detector.getFocusY());

但是这一改出事了……现在是能根据手势缩放中心进行缩放了,但是缩放到最小时图片位置可能发生了变化……现在还要解决的就是缩放时图片位置变化,新增如下方法:

    /**
* 在缩放时,控制范围
*/
private void checkBorderAndCenterWhenScale() {
Matrix matrix = scaleMatrix;
RectF rectF = new RectF();
Drawable d = getDrawable();
if (d != null) {
rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rectF);
} float deltaX = 0;
float deltaY = 0;
int width = getWidth();
int height = getHeight();
// 如果宽或高大于屏幕,则控制范围
if (rectF.width() >= width) {
if (rectF.left > 0) {
deltaX = -rectF.left;
}
if (rectF.right < width) {
deltaX = width - rectF.right;
}
}
if (rectF.height() >= height) {
if (rectF.top > 0) {
deltaY = -rectF.top;
}
if (rectF.bottom < height) {
deltaY = height - rectF.bottom;
}
}
// 如果宽或高小于屏幕,则让其居中
if (rectF.width() < width) {
deltaX = width * 0.5f - rectF.right + 0.5f * rectF.width();
}
if (rectF.height() < height) {
deltaY = height * 0.5f - rectF.bottom + 0.5f * rectF.height();
}
scaleMatrix.postTranslate(deltaX, deltaY);
}

然后在onScale方法里调用以上检测的方法:

        if ((scale < SCALE_MAX &amp;&amp; scaleFactor > 1.0f)
|| (scale > initScale &amp;&amp; scaleFactor < 1.0f)) {
if (scaleFactor * scale < initScale)
scaleFactor = initScale / scale;
if (scaleFactor * scale > SCALE_MAX)
scaleFactor = SCALE_MAX / scale;
Log.e(TAG, "scaleFactor2---->" + scaleFactor);
//设置缩放比例
scaleMatrix.postScale(scaleFactor, scaleFactor,
detector.getFocusX(), detector.getFocusY());
checkBorderAndCenterWhenScale();
setImageMatrix(scaleMatrix);
}

最终成型

以上代码算出初步的能用了,不过还有一点值得注意的地方,如果你在onTouch这个方法里的代码是这样的:

return scaleGestureDetector.onTouchEvent(event);

那么所有的事件都会被消费,因为我点到scaleGestureDetector的onTouch方法里,没看到return false的东西,所以你设置的oncilck事件之类的都没什么卵用。

对于我来说这样是不行的,因为我希望用户点击一次之后可以退出当前界面,所以你可以调用sacleGestureDetector.onTouchEvent(event)但是返回false,不消耗这个事件,让onClick来处理点击事件。当我想的很美的时候,却发现这么做虽然点击事件会被处理,而且缩放也正常,但是缩放的操作会被判断为点击事件,也就是说这么干不行了。我的脑海中第二个想到的解决方案是回调,既然系统的回调不行了,那我自己设置一个时间,在这个时间之内就是click事件,我在这个事件的回调里把当前界面退出了不就行了。实现如下:

    private ClickCloseListener c;

    public interface ClickCloseListener {
void close();
} public void setClickCloseListener(ClickCloseListener c) {
this.c = c;
} /**
* 按下的时间
*/
long downTime; /**
* down 和 up之间的间隔
*/
long closeTime = 100L; /**
* 设置按下的时间
*/
//suppress unused warning for no reason
@SuppressWarnings("unused")
public void setClickTime(long time) {
this.closeTime = time;
} @Override
public boolean onTouch(View v, MotionEvent event) {
scaleGestureDetector.onTouchEvent(event);
//如果监听为null,消费该事件,不让onclick生效
if (c == null)
return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_UP:
downTime = System.currentTimeMillis() - downTime;
if (downTime < closeTime)
c.close();
break;
default:
break;
}
return true;
}

最后看一下效果图吧~

手把手教你打造ImageView支持手势放大缩小

当然了,自己搞的点击事件有点不靠谱,时间间隔设置为100ms,有点短了,你可以自己设置,不过这篇文到这里也就结束了。本来还想连什么移动一起加上,嗯,现在发现好像篇幅超出了我的控制。暂且还是算了吧~而且这个姑且也算是能用了,只不过适用的场景只是查看大图的一个单独的界面。这个简单的小东西就写到这了。完整代码就放在这把,也懒得上传github了:

package com.example.luo_pc.view.CustomView;

/**
* Created by Luo_xiasuhuei321@163.com on 2016/9/24.
* desc:
*/ import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView; public class ZZoomImageView extends ImageView implements View.OnTouchListener, ScaleGestureDetector.OnScaleGestureListener,
ViewTreeObserver.OnGlobalLayoutListener {
//suppress the unused warning because maybe it will be used sometime later
@SuppressWarnings("unused")
private static final String TAG = "ZZoomImageView"; /**
* 最大放大倍数
*/
public static final float SCALE_MAX = 4.0f; /**
* 默认缩放
*/
private float initScale = 1.0f; /**
* 手势检测
*/
ScaleGestureDetector scaleGestureDetector = null; Matrix scaleMatrix = new Matrix(); /**
* 处理矩阵的9个值
*/
float[] martixValue = new float[9]; public ZZoomImageView(Context context) {
this(context, null);
} public ZZoomImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public ZZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setScaleType(ScaleType.MATRIX);
scaleGestureDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(this);
} @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
} //suppress deprecate warning because i have dealt with it
@Override
@SuppressWarnings("deprecation")
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
getViewTreeObserver().removeGlobalOnLayoutListener(this);
} /**
* 获取当前缩放比例
*/
public float getScale() {
scaleMatrix.getValues(martixValue);
return martixValue[Matrix.MSCALE_X];
} /**
* 在缩放时,控制范围
*/
private void checkBorderAndCenterWhenScale() {
Matrix matrix = scaleMatrix;
RectF rectF = new RectF();
Drawable d = getDrawable();
if (d != null) {
rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rectF);
} float deltaX = 0;
float deltaY = 0;
int width = getWidth();
int height = getHeight();
// 如果宽或高大于屏幕,则控制范围
if (rectF.width() >= width) {
if (rectF.left > 0) {
deltaX = -rectF.left;
}
if (rectF.right < width) {
deltaX = width - rectF.right;
}
}
if (rectF.height() >= height) {
if (rectF.top > 0) {
deltaY = -rectF.top;
}
if (rectF.bottom < height) {
deltaY = height - rectF.bottom;
}
}
// 如果宽或高小于屏幕,则让其居中
if (rectF.width() < width) {
deltaX = width * 0.5f - rectF.right + 0.5f * rectF.width();
}
if (rectF.height() < height) {
deltaY = height * 0.5f - rectF.bottom + 0.5f * rectF.height();
}
scaleMatrix.postTranslate(deltaX, deltaY);
} //--------------------------implement OnTouchListener----------------------------//
private ClickCloseListener c; public interface ClickCloseListener {
void close();
} public void setClickCloseListener(ClickCloseListener c) {
this.c = c;
} /**
* 按下的时间
*/
long downTime; /**
* down 和 up之间的间隔
*/
long closeTime = 100L; /**
* 设置按下的时间
*/
//suppress unused warning for no reason
@SuppressWarnings("unused")
public void setClickTime(long time) {
this.closeTime = time;
} @Override
public boolean onTouch(View v, MotionEvent event) {
scaleGestureDetector.onTouchEvent(event);
//如果监听为null,消费该事件,不让onclick生效
if (c == null)
return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_UP:
downTime = System.currentTimeMillis() - downTime;
if (downTime < closeTime)
c.close();
break;
default:
break;
}
return true;
} //----------------------implement OnScaleGestureListener------------------------// @Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = getScale();
float scaleFactor = detector.getScaleFactor();
if (getDrawable() == null)
return true;
// Log.e(TAG,"君甚咸,此鱼何能及君也?");
if ((scale < SCALE_MAX &amp;&amp; scaleFactor > 1.0f)
|| (scale > initScale &amp;&amp; scaleFactor < 1.0f)) {
if (scaleFactor * scale < initScale)
scaleFactor = initScale / scale;
if (scaleFactor * scale > SCALE_MAX)
scaleFactor = SCALE_MAX / scale;
Log.e(TAG, "scaleFactor2---->" + scaleFactor);
//设置缩放比例
scaleMatrix.postScale(scaleFactor, scaleFactor,
detector.getFocusX(), detector.getFocusY());
checkBorderAndCenterWhenScale();
setImageMatrix(scaleMatrix);
}
return true;
} @Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
} @Override
public void onScaleEnd(ScaleGestureDetector detector) { } boolean once = true; @Override
public void onGlobalLayout() {
if (!once)
return;
Drawable d = getDrawable();
if (d == null)
return;
//获取imageview宽高
int width = getWidth();
int height = getHeight(); //获取图片宽高
int imgWidth = d.getIntrinsicWidth();
int imgHeight = d.getIntrinsicHeight(); float scale = 1.0f; //如果图片的宽或高大于屏幕,缩放至屏幕的宽或者高
if (imgWidth > width &amp;&amp; imgHeight <= height)
scale = (float) width / imgWidth;
if (imgHeight > height &amp;&amp; imgWidth <= width)
scale = (float) height / imgHeight;
//如果图片宽高都大于屏幕,按比例缩小
if (imgWidth > width &amp;&amp; imgHeight > height)
scale = Math.min((float) imgWidth / width, (float) imgHeight / height);
initScale = scale;
//将图片移动至屏幕中心
scaleMatrix.postTranslate((width - imgWidth) / 2, (height - imgHeight) / 2);
scaleMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);
setImageMatrix(scaleMatrix);
once = false;
}
}

手把手教你打造ImageView支持手势放大缩小的更多相关文章

  1. Android&colon;手把手教你打造可缩放移动的ImageView(下)

    在上一篇Android:手把手教你打造可缩放移动的ImageView最后提出了一个注意点:当自定义的MatrixImageView如ViewPager.ListView等带有滑动效果的ViewGrou ...

  2. Android&colon;手把手教你打造可缩放移动的ImageView(上)

    定义ImageView,实现功能如下: 1.初始化时图片垂直居中显示,拉伸图片宽度至ImageView宽度. 2.使用两根手指放大缩小图片,可设置最大放大倍数,当图片小于ImageView宽度时,在手 ...

  3. JS控制图片拖动 放大 缩小 旋转 支持滚轮放大缩小 IE有效

    <html> <head>     <title>图片拖动,放大,缩小,转向</title> <script type="text/ja ...

  4. 手把手教你打造高效的 Kubernetes 命令行终端

    Kubernetes 作为云原生时代的操作系统,熟悉和使用它是每名用户的必备技能.本文将介绍一些提高操作 Kubernetes 效率的技巧以及如何打造一个高效的 Kubernetes 命令行终端的方法 ...

  5. 显示大图Activity(支持手势放大)

    xml: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:andro ...

  6. 手把手教你实现一个支持插件化的 uTools 工具箱(一)

    前言 对于前端同学来说,我们会经常用到各种小工具,比如:图床.颜色拾取.二维码生成器.url 管理.文本比对.json 格式化.当然我们可以 chrome 收藏夹来管理各种在线的小工具,但作为一个有追 ...

  7. 手把手教你打造一个心电图效果View Android自定义View

    大家好,看我像不像蘑菇-因为我在学校呆的发霉了. 思而不学则殆 丽丽说得对,我有奇怪的疑问,大都是思而不学造成的,在我书读不够的情况下想太多,大多等于白想,所以革命没成功,同志仍需努力. 好了废话不说 ...

  8. &lbrack;C&num;&rsqb;手把手教你打造Socket的TCP通讯连接(一)

    本文章将讲解基于TCP连接的Socket通讯,使用Socket异步功能,并且无粘包现象,通过事件驱动使用. 在编写Socket代码之前,我们得要定义一下Socket的基本功能. 作为一个TCP连接,不 ...

  9. 手把手教你打造一个 Mac 风格的 Windows10&lpar;手动滑稽&rpar;

    Mark  https://www.sqlsec.com/2018/04/winmac.html 大佬写得很好,资瓷!! MyDock可能不是最新的,给出官方维护的网盘:https://pan.bai ...

随机推荐

  1. 启用&OpenCurlyDoubleQuote;关闭自动根证书更新”,解决Windows系统各种卡顿的问题(Visual studio 卡、远程桌面mstsc卡、SVN卡)

    最近,发现在Win7下面一系列操作都会出现卡顿的情况: 1.  Visual studio 启动调试和关闭调试时,都会卡上半分钟左右 2.  使用远程桌面mstsc.exe,点击连接时,也会卡上半分钟 ...

  2. Asp&period;net MVC4 使用EF实现数据库的增删改查

    EF的使用 步骤: (1)将EF添加到项目:在Model右击添加新建项 找到ADO.NET实体数据模型,接着... (2)实现数据库的增删改查       查询 (因为在Model中已经添加EF实体了 ...

  3. C&num;数据库读取数据后转换为INT32后计算的小技巧

    这有什么难的,不管是什么数据库, 首先分别读出userinfo中usermoney的值 存入s1,card中extramoney的值s2 读出字段数据你应该会吧! 再用userinfo中字段userm ...

  4. pkg-config的用法

    pkg-config的用法 pkg-config pkg-config程序是干什么用的?简单的说就是向用户向程序提供相应库的路径.版本号等信息的程序. 譬如说我们运行以下命令:pkg-config  ...

  5. LinkedHashMap相关信息介绍(转)

    Java中的LinkedHashMap此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表.此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序 ...

  6. IOS 第三方库之-MBProgressHUD的使用详解

    转自作者: weidfyr  http://www.aiuxian.com/article/p-3121607.html 1,MBProgressHUD常用属性和用法Demo - (void)test ...

  7. python内置函数all使用的坑

    在代码的改造过程中,因为忽略了一个问题导致数据异常,在改造的过程中以及后续的review中都没注意到这个问题,单元测试也没有覆盖到,记录如下.这个坑在于all的使用上,如果参数为空元组或空列表时,返回 ...

  8. Orleans之EventSourcing

    Orleans之EventSourcing 这是Orleans系列文章中的一篇.首篇文章在此 引入: 如果没有意外,我再这篇文章中用ES代替EventSourcing,如果碰到"事件回溯&q ...

  9. Android 视频播放器 &lpar;二&rpar;:使用MediaPlayer播放视频

    在 Android 视频播放器 (一):使用VideoView播放视频 我们讲了一下如何使用VideoView播放视频,了解了基本的播放器的一些知识和内容.也知道VideoView内部封装的就是Med ...

  10. 检测到目标URL存在http host头攻击漏洞

    检测到目标URL存在http host头攻击漏洞 1.引发安全问题的原因 为了方便的获得网站域名,开发人员一般依赖于HTTP Host header.例如,在php里用_SERVER["HT ...