我分四部分介绍:
1.Imageview利用Matrix和OnScaleGestureListener实现手势缩放;
2.在第一部分的基础上实现图片跟随手指进行滑动;
3.在一、二的基础上利用GestureDetector的OnDoubleTap回调实现图片双击还原;
4.在一、二、三的基础上将ImageView扩展为FrameLayout,实现布局内所有子控件都能手势缩放、移动、双击还原。
一、Imageview利用Matrix和OnScaleGestureListener实现手势缩放
1.基础知识:
Matrix是一个3*3的矩阵
* MSCALE_X MSKEW_X MTRANS_X * MKEW_Y MSCALE_Y MTRANS_Y * MPERSP_0 MPERSP_1 MPERSP_2
Matrix.postScale(x, y, px, py)矩阵缩放,x是x方向上的缩放大小,y是y方向上的缩放大小,(px,py)是缩放中心
Matrix.postTranslate(dx, dy)矩阵平移
setImageMatrix(matrix)将自己的matrix赋值给ImageView的matrix从而实现ImageView中图片的缩放、平移等操作
OnScaleGestureListener中的onScale回调中的ScaleGestureDetector.getScaleFactor()可以获取当前手势缩放的大小
ImageView需要设置setScaleType(ScaleType.MATRIX);才能实现缩放
OnScaleGestureListener中的OnScaleBegin回调必须return true才有缩放效果
2.实现思路:
实现OnScaleGestureListener监听,在OnScale回调方法中,根据ScaleGestureDetector.getScaleFactor()获取当前手势缩放的大小,利用Matrix.postScale(x, y, px, py)方法将缩放大小保存在矩阵中,用setImageMatrix(matrix)方法,将矩阵赋值给ImageView的内部矩阵,从而实现图片的手势缩放,是不是很简单。
3.实现代码:
/**
* 只支持手势缩放的ImageView
*/
public class ZoomOnlyImageView extends AppCompatImageView implements ScaleGestureDetector.OnScaleGestureListener {
private ScaleGestureDetector scaleGestureDetector;//手势缩放
/**
* MSCALE_X MSKEW_X MTRANS_X
* MKEW_Y MSCALE_Y MTRANS_Y
* MPERSP_0 MPERSP_1 MPERSP_2
*/
private Matrix mMatrix;//缩放矩阵
private float maxScale = 4.0f;//最大缩放到原图的四倍
private float minScale = 0.5f;//最小缩放到原图的0.5倍
public ZoomOnlyImageView(Context context) {
super(context);
init(context);
}
public ZoomOnlyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomOnlyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
//初始化参数
private void init(Context context) {
setScaleType(ScaleType.MATRIX);//允许imageview缩放
scaleGestureDetector = new ScaleGestureDetector(new WeakReference<Context>(context).get(),
new WeakReference<ZoomOnlyImageView>(this).get());
mMatrix = new Matrix();
}
@Override
public boolean onScale(ScaleGestureDetector detector) {//OnScaleGestureListener里的方法
if (getDrawable() == null) {
return true;
}
//获取本次的缩放值
float scale = detector.getScaleFactor();
Log.i("zhangdi", "scaleFactor = "+scale);
float preScale = getPreScale();
Log.i("zhangdi", "preScale = "+preScale);
if (preScale * scale < maxScale &&
preScale * scale > minScale) {//preScale * scale可以计算出此次缩放执行的话,缩放值是多少
//detector.getFocusX()缩放手势中心的x坐标,detector.getFocusY()y坐标
// mMatrix.postScale(scale, scale, detector.getFocusX(), detector.getFocusY());
mMatrix.postScale(scale, scale, getWidth()/2, getHeight()/2);
setImageMatrix(mMatrix);
makeDrawableCenter();
}
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {//OnScaleGestureListener里的方法,缩放开始
return true;//必须返回true才有效果
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {//OnScaleGestureListener里的方法,缩放结束
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return scaleGestureDetector.onTouchEvent(event);
}
//获取目前一共缩放了多少
private float getPreScale() {
float[] matrix = new float[9];
mMatrix.getValues(matrix);
return matrix[Matrix.MSCALE_X];
}
//缩小的时候让图片居中
private void makeDrawableCenter() {
RectF rect = new RectF();
Drawable d = getDrawable();
if (d != null) {
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());//设置rect的初始四个角值是图片的四个顶点值
Log.i("zhangdi", "bitmapWidth = "+d.getIntrinsicWidth()+", bitmapHeight = "+d.getIntrinsicHeight());
mMatrix.mapRect(rect);//获取通过当前矩阵变换后的四个角值
Log.i("zhangdi", "matrixWidth = "+rect.width()+", matrixHeight = "+rect.height());
Log.i("zhangdi", "bmLeft: "+rect.left+" bmRight: "+rect.right+" bmTop: "+rect.top+" bmBottom: "+rect.bottom);
}
int width = getWidth();
int height = getHeight();
float dx=0, dy=0;
// 如果宽或高大于屏幕,则控制范围
if (rect.width() >= width)
{
if (rect.left > 0)
{
dx = -rect.left;
}
if (rect.right < width)
{
dx = width - rect.right;
}
}
if (rect.height() >= height)
{
if (rect.top > 0)
{
dy = -rect.top;
}
if (rect.bottom < height)
{
dy = height - rect.bottom;
}
}
if (rect.width() < width) {
dx = width/2 - (rect.right - rect.width()/2);//控件中心点横坐标减去图片中心点横坐标为X方向应移动距离
}
if (rect.height() < height) {
dy = height/2 - (rect.bottom - rect.height()/2);
}
Log.i("zhangdi", "dx = "+dx+", dy = "+dy);
if (dx != 0 || dy != 0) {
mMatrix.postTranslate(dx, dy);
setImageMatrix(mMatrix);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
setImageDrawable(null);
scaleGestureDetector = null;
}
}
二、在第一部分的基础上实现图片跟随手指进行滑动
1.实现思路:把每次的手指移动距离赋值给矩阵,将矩阵赋值给ImageView实现图片的平移,注意控制移动范围不能超过图片范围。
2.代码实现:修改第一部分的onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent event) {
scaleGestureDetector.onTouchEvent(event);
float x=0, y=0;
final int pointerCount = event.getPointerCount();//获取手指个数
for (int i=0; i<pointerCount; i++) {
x += event.getX(i);
y += event.getY(i);
}
x = x/pointerCount;//获取x平均值
y = y/pointerCount;//获取y平均值
if (pointerCount != lastPointerCount)
{
lastX = x;
lastY = y;
}
Log.i("zhangdi", "pointCount: "+pointerCount+", lastPointCount: "+lastPointerCount);
lastPointerCount = pointerCount;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
float delX = x - lastX;//x方向计算移动距离
float delY = y - lastY;//计算y方向移动距离
RectF rectF = getMatrixRectF();
//控制移动边界不能超出图片范围
if ((rectF.left >= 0 && delX > 0) || (rectF.right <= getWidth() && delX < 0)) {
delX = 0;
}
if ((rectF.top >= 0 && delY > 0) || (rectF.bottom <= getHeight() && delY < 0)) {
delY = 0;
}
mMatrix.postTranslate(delX, delY);
setImageMatrix(mMatrix);
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
lastPointerCount = 0;
break;
}
return true;
}
三、在一、二的基础上利用GestureDetector的OnDoubleTap回调实现图片双击还原
1.实现思路:利用GestureDetector的OnDoubleTap监听回调,判断当前矩阵的缩放比例是不是1,不是的话则matrix.reset(),将matrix赋值给ImageView实现图片双击还原。
2.实现代码:
1.修改init()方法,创建GestureDetector对象:
//初始化参数
private void init(Context context) {
setScaleType(ScaleType.MATRIX);//允许imageview缩放
scaleGestureDetector = new ScaleGestureDetector(new WeakReference<Context>(context).get(),
new WeakReference<ZoomTranslateDoubleTapImageView>(this).get());
mMatrix = new Matrix();
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDoubleTap(MotionEvent e) {//双击图片还原
if (getPreScale() != 1.0f) {
ZoomTranslateDoubleTapImageView.this.postDelayed(new Runnable() {
@Override
public void run() {
mMatrix.reset();
setImageMatrix(mMatrix);
makeDrawableCenter();
}
},16);
}
return true;
}
});
}
2.在onTouchEvent方法中增加双击事件关联:
if (gestureDetector.onTouchEvent(event)) { return true; }
四、在一、二、三的基础上将ImageView扩展为FrameLayout,实现布局内所有子控件都能手势缩放、移动、双击还原:
1.实现思路:与上面的方法基本没什么区别,只需要把继承ImageView改为继承FrameLayout,但是FrameLayout没有setImageMatrix方法,我们进入ImageView的源码中可以看出setImageMatrix方法主要是将matrix赋值给了mDrawMatrix,而mDrawMatrix在onDraw方法中作用在了canvas上canvas.concat(mDrawMatrix),从而使图片实现矩阵中的相关操作,所以我们的FrameLayout虽然没有setImageMatrix这个方法,但是我们只需要在Matrix相关操作赋值后,调用invalidate()方法重绘界面,并且在它的onDraw方法中使canvas与矩阵关联就行了,需要注意的是ViewGroup出于效率的考虑有些是默认绕过onDraw方法的,所以我们需要重写dispatchDraw方法而不是onDraw方法。
2.实现代码:
1.将所有的setImageMatrix方法替换成invalidate方法;
2.重写dispatchDraw方法:
@Override protected void dispatchDraw(Canvas canvas) { View view = getChildAt(0); canvas.save(); canvas.concat(mMatrix); view.draw(canvas); canvas.restore(); }
3.使用注意事项:
在自定义布局中只能包含一个ViewGroup控件,然后所有的子view都放到这个ViewGroup中才能实现预期效果,类似ScrollerView的使用。