Android自定义GestureDetector实现手势ImageView

作者:vv_小虫 时间:2023-12-18 05:16:35 

不说废话了,进入我们今天的主题吧。

先贴上前面内容的地址:

Android手势ImageView三部曲(一)

Android手势ImageView三部曲(二)

Android手势ImageView三部曲(三)

前面我们讲到了ScaleGestureDetector这个工具类,我在疑惑,为什么搞出一个ScaleGestureDetector,不顺带把什么旋转、移动、做了呢? 好吧~! 谷歌肯定还是想给开发者留一点自己的空间哈。

仿照ScaleGestureDetector,我们来定义一个叫MoveGestureDetector的工具类(专门用于检测滑动手势),在定义MoveGestureDetector之前,因为我们还要考虑到之后的RotateGestureDetector等等..于是我们定一个叫BaseGestureDetector把一些公共的方法抽取出来:


public abstract class BaseGestureDetector {
protected final Context mContext;
protected boolean mGestureInProgress;

protected MotionEvent mPrevEvent;
protected MotionEvent mCurrEvent;

protected float mCurrPressure;
protected float mPrevPressure;
protected long mTimeDelta;

/**
* 上一次event的pressure/这一次的pressure,这是一个什么概念呢?
* 我们想象一下当你手指按下然后滑动并且到离开屏幕,
* 手指触碰到屏幕的压力会越来越小,直到手指移开屏幕
*/
protected static final float PRESSURE_THRESHOLD = 0.67f;

public BaseGestureDetector(Context context) {
mContext = context;
}

/**
* 跟ScaleGesture一样,我们也把事件的处理放在此方法中
* @param event
* @return
*/
public boolean onTouchEvent(MotionEvent event){
//为了获取到ACTION_POINTER_UP等事件必须加上& MotionEvent.ACTION_MASK
final int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
/**
* 是否调用handleInProgressEvent方法
*/
if (!mGestureInProgress) {
//如果mGestureInProgress为false的时候,执行开始操作
handleStartProgressEvent(actionCode, event);
} else {
//处理手势
handleInProgressEvent(actionCode, event);
}
return true;
}

/**
* 准备处理手势
* @param actionCode
* @param event
*/
protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event);

/**
* 正在处理手势
* @param actionCode
* @param event
*/
protected abstract void handleInProgressEvent(int actionCode, MotionEvent event);

/**
* 更新event的状态,保存之前的event,获取当前event
* @param curr
*/
protected void updateStateByEvent(MotionEvent curr){
final MotionEvent prev = mPrevEvent;

// Reset mCurrEvent
if (mCurrEvent != null) {
mCurrEvent.recycle();
mCurrEvent = null;
}
mCurrEvent = MotionEvent.obtain(curr);

// 之前的event跟现在的event之间的时间差
mTimeDelta = curr.getEventTime() - prev.getEventTime();

// 之前的event跟腺癌的event之间的手指压力值
mCurrPressure = curr.getPressure(curr.getActionIndex());
mPrevPressure = prev.getPressure(prev.getActionIndex());
}

/**
* 重置所有状态
*/
protected void resetState() {
if (mPrevEvent != null) {
mPrevEvent.recycle();
mPrevEvent = null;
}
if (mCurrEvent != null) {
mCurrEvent.recycle();
mCurrEvent = null;
}
mGestureInProgress = false;
}

/**
* Returns {@code true} if a gesture is currently in progress.
* @return {@code true} if a gesture is currently in progress, {@code false} otherwise.
*/
public boolean isInProgress() {
return mGestureInProgress;
}

/**
* Return the time difference in milliseconds between the previous accepted
* GestureDetector event and the current GestureDetector event.
*
* @return Time difference since the last move event in milliseconds.
*/
public long getTimeDelta() {
return mTimeDelta;
}

/**
* Return the event time of the current GestureDetector event being
* processed.
*
* @return Current GestureDetector event time in milliseconds.
*/
public long getEventTime() {
return mCurrEvent.getEventTime();
}

}

然后我们定义一个叫MoveGestureDetector的类去继承BaseGestureDetector,然后事件两个抽象方法:


public class MoveGestureDetector extends BaseGestureDetector{
@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event){

@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event){

}


那我们如果检测到了事件的话该怎么通知调用者呢?是的,我们需要用到回调,我们看看ScaleGestureDetector的回调接口咋定义的:


public interface OnScaleGestureListener {
public boolean onScale(ScaleGestureDetector detector);
public boolean onScaleBegin(ScaleGestureDetector detector);
public void onScaleEnd(ScaleGestureDetector detector);
}
public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {

public boolean onScale(ScaleGestureDetector detector) {
return false;
}

public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}

public void onScaleEnd(ScaleGestureDetector detector) {
// Intentionally empty
}
}

里面定义了一个接口一个叫OnScaleGestureListener,一个类叫SimpleOnScaleGestureListener,SimpleOnScaleGestureListener是实现了OnScaleGestureListener,于是我们MoveGestureDetector的接口可以这么定义了:


/**
* 仿照ScaleGestureDetector我们也定义三个方法
*/
public interface OnMoveGestureListener {
/**
* 移动的时候回调
*/
public boolean onMove(MoveGestureDetector detector);
/**
* 移动开始的时候回调
*/
public boolean onMoveBegin(MoveGestureDetector detector);
/**
* 移动结束的时候回调
*/
public void onMoveEnd(MoveGestureDetector detector);
}

public static class SimpleOnMoveGestureListener implements OnMoveGestureListener {
public boolean onMove(MoveGestureDetector detector) {
return false;
}

public boolean onMoveBegin(MoveGestureDetector detector) {
return true;
}

public void onMoveEnd(MoveGestureDetector detector) {
// Do nothing, overridden implementation may be used
}
}

好啦!框子都搭好了,我们用的时候呢,就可以这么用了:

1、创建一个MoveGestureDetector


public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
//创建一个缩放手势监测器
scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener);
//创建一个MoveGestureDetector
moveGestureDetector=new MoveGestureDetector(context,onMoveGestureListener);
}

2、把事件给MoveGestureDetector


@Override
public boolean onTouchEvent(MotionEvent event) {
//把事件给scaleDetector
scaleDetector.onTouchEvent(event);
//把事件给moveGestureDetector
moveGestureDetector.onTouchEvent(event);
return true;
}

3、获取回调值


private MoveGestureDetector.SimpleOnMoveGestureListener onMoveGestureListener=new MoveGestureDetector.SimpleOnMoveGestureListener(){
@Override
public boolean onMove(MoveGestureDetector detector) {

return super.onMove(detector);
}
};

怎么样?是不是跟ScaleGestureDetector一样了呢?清晰明了哈,框子是搭起来了,下面我们来实现下它的逻辑(也就是实现下handleStartProgressEvent跟handleInProgressEvent方法):

每行都有注释,我就直接上代码了


*/
public class MoveGestureDetector extends BaseGestureDetector {

/**
* 仿照ScaleGestureDetector我们也定义三个方法
*/
public interface OnMoveGestureListener {
/**
* 移动的时候回调
*/
public boolean onMove(MoveGestureDetector detector);
/**
* 移动开始的时候回调
*/
public boolean onMoveBegin(MoveGestureDetector detector);
/**
* 移动结束的时候回调
*/
public void onMoveEnd(MoveGestureDetector detector);
}

public static class SimpleOnMoveGestureListener implements OnMoveGestureListener {
public boolean onMove(MoveGestureDetector detector) {
return false;
}

public boolean onMoveBegin(MoveGestureDetector detector) {
return true;
}

public void onMoveEnd(MoveGestureDetector detector) {
// Do nothing, overridden implementation may be used
}
}

private static final PointF FOCUS_DELTA_ZERO = new PointF();

private final OnMoveGestureListener mListener;

private PointF mCurrFocusInternal;
private PointF mPrevFocusInternal;
private PointF mFocusExternal = new PointF();
private PointF mFocusDeltaExternal = new PointF();

public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
super(context);
mListener = listener;
}

@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event){
switch (actionCode) {
//当手指按下的时候
case MotionEvent.ACTION_DOWN:
//重置一下所有状态(currevent跟preevent)
resetState(); // In case we missed an UP/CANCEL event
//获取当前event作为mPrevEvent
mPrevEvent = MotionEvent.obtain(event);
//重置两次event的时间间隔
mTimeDelta = 0;
//更新state
updateStateByEvent(event);
break;

case MotionEvent.ACTION_MOVE:
//回调onMoveBegin,mGestureInProgress决定是否继续处理事件(执行handleInProgressEvent)
//mGestureInProgress由调用者决定
mGestureInProgress = mListener.onMoveBegin(this);
break;
}
}

/**
* 处理移动事件
*/
@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event){
switch (actionCode) {
//当抬起或者取消的时候
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//回调onMoveEnd,move处理结束
mListener.onMoveEnd(this);
//重置所有的state
resetState();
break;

case MotionEvent.ACTION_MOVE:
//更新状态
updateStateByEvent(event);
//当上一次event的press值/这一次event值大于临界值的时候开始触发onMove
//因为如果CurrPressure / mPrevPressure很小的话,可能手指已经离开屏幕了
if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
/**
* 回调onMove方法,并获取updatePrevious
* updatePrevious标记是由调用者决定,
* updatePrevious是否更新之前的event,
* 如果为false的话mPrevEvent一直是我们在down的时候赋值的event
* 如果为true的话,每次move事件处理完都会把最新的event赋给mPrevEvent
*/
final boolean updatePrevious = mListener.onMove(this);
if (updatePrevious) {
mPrevEvent.recycle();
mPrevEvent = MotionEvent.obtain(event);
}
}
break;
}
}

/**
* 参考ScaleGestureDetector
* move核心处理方法
* 重写父类的updateStateByEvent
*
*/

protected void updateStateByEvent(MotionEvent curr) {
super.updateStateByEvent(curr);

final MotionEvent prev = mPrevEvent;

// 获取当前所有手指的中心点
mCurrFocusInternal = determineFocalPoint(curr);
//获取之前event所有手指的中心点
mPrevFocusInternal = determineFocalPoint(prev);

//判断是否有手指中途添加或者移除
boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount();
//有移除的话mFocusDeltaExternal就等于空(0,0),没有的话就算出前面event跟当前event中心点距离
mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x, mCurrFocusInternal.y - mPrevFocusInternal.y);
//累加距离值
mFocusExternal.x += mFocusDeltaExternal.x;
mFocusExternal.y += mFocusDeltaExternal.y;
}

/**
* 获取所有手指的中间点坐标(参考ScaleGestureDetector)
*/
private PointF determineFocalPoint(MotionEvent e){
// Number of fingers on screen
final int pCount = e.getPointerCount();
float x = 0f;
float y = 0f;

for(int i = 0; i < pCount; i++){
x += e.getX(i);
y += e.getY(i);
}

return new PointF(x/pCount, y/pCount);
}

/**
* 获取距离值累加过后的值
*/
public float getFocusX() {
return mFocusExternal.x;
}

public float getFocusY() {
return mFocusExternal.y;
}

/**
* 获取上一个事件到下一个事件之间的x跟y的距离值
*/
public PointF getFocusDelta() {
return mFocusDeltaExternal;
}

}

好啦!!写完哈,我们来使用一下:


package com.leo.gestureimageview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

import com.leo.gestureimageview.GestureDetectors.MoveGestureDetector;

public class MatrixImageView extends ImageView {
private Matrix currMatrix;
private float scaleFactor=1f;//当前图片的缩放值

private float transX,transY;
private ScaleGestureDetector scaleDetector;
private MoveGestureDetector moveGestureDetector;
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
//创建一个缩放手势监测器
scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener);
//创建一个MoveGestureDetector
moveGestureDetector=new MoveGestureDetector(context,onMoveGestureListener);
}

private void initView() {
currMatrix = new Matrix();
DisplayMetrics dm = getResources().getDisplayMetrics();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//把事件给scaleDetector
scaleDetector.onTouchEvent(event);
//把事件给moveGestureDetector
moveGestureDetector.onTouchEvent(event);
return true;
}
private void setMatrix(){
currMatrix.reset();
currMatrix.postScale(scaleFactor,scaleFactor,getMeasuredWidth()/2,getMeasuredHeight()/2);
currMatrix.postTranslate(transX,transY);
setImageMatrix(currMatrix);
}
private ScaleGestureDetector.SimpleOnScaleGestureListener onScaleGestureListener=new ScaleGestureDetector.SimpleOnScaleGestureListener(){
@Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor(); // scale change since previous event
// Don't let the object get too small or too large.
scaleFactor = Math.max(0.1f, Math.min(scaleFactor, 10.0f));
setMatrix();
/**
* 因为getScaleFactor=当前两个手指之间的距离(preEvent)/手指按下时候两个点的距离(currEvent)
* 这里如果返回true的话,会在move操作的时候去更新之前的event,
* 如果为false的话,不会去更新之前按下时候保存的event
*/
return true;
}
};
private MoveGestureDetector.SimpleOnMoveGestureListener onMoveGestureListener=new MoveGestureDetector.SimpleOnMoveGestureListener(){
@Override
public boolean onMove(MoveGestureDetector detector) {
transX=detector.getFocusX();
transY=detector.getFocusY();
setMatrix();
return true;
}
};
}

好啦~!! 短短几行代码就可以玩起来了,效果图我就不附了哈,小伙伴自己运行一下,那么MoveGestureDetector我们实现了,想必RotateGestureDetector也是很快就会实现了,哈哈~~! 我就直接用贴上国外大神写的代码了:


public class RotateGestureDetector extends TwoFingerGestureDetector {

/**
* Listener which must be implemented which is used by RotateGestureDetector
* to perform callbacks to any implementing class which is registered to a
* RotateGestureDetector via the constructor.
*
* @see SimpleOnRotateGestureListener
*/
public interface OnRotateGestureListener {
public boolean onRotate(RotateGestureDetector detector);
public boolean onRotateBegin(RotateGestureDetector detector);
public void onRotateEnd(RotateGestureDetector detector);
}

/**
* Helper class which may be extended and where the methods may be
* implemented. This way it is not necessary to implement all methods
* of OnRotateGestureListener.
*/
public static class SimpleOnRotateGestureListener implements OnRotateGestureListener {
public boolean onRotate(RotateGestureDetector detector) {
return false;
}

public boolean onRotateBegin(RotateGestureDetector detector) {
return true;
}

public void onRotateEnd(RotateGestureDetector detector) {
// Do nothing, overridden implementation may be used
}
}

private final OnRotateGestureListener mListener;
private boolean mSloppyGesture;

public RotateGestureDetector(Context context, OnRotateGestureListener listener) {
super(context);
mListener = listener;
}

@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event){
switch (actionCode) {
case MotionEvent.ACTION_POINTER_DOWN:
// At least the second finger is on screen now

resetState(); // In case we missed an UP/CANCEL event
mPrevEvent = MotionEvent.obtain(event);
mTimeDelta = 0;

updateStateByEvent(event);

// See if we have a sloppy gesture
mSloppyGesture = isSloppyGesture(event);
if(!mSloppyGesture){
// No, start gesture now
mGestureInProgress = mListener.onRotateBegin(this);
}
break;

case MotionEvent.ACTION_MOVE:
if (!mSloppyGesture) {
break;
}

// See if we still have a sloppy gesture
mSloppyGesture = isSloppyGesture(event);
if(!mSloppyGesture){
// No, start normal gesture now
mGestureInProgress = mListener.onRotateBegin(this);
}

break;

case MotionEvent.ACTION_POINTER_UP:
if (!mSloppyGesture) {
break;
}

break;
}
}

@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event){
switch (actionCode) {
case MotionEvent.ACTION_POINTER_UP:
// Gesture ended but
updateStateByEvent(event);

if (!mSloppyGesture) {
mListener.onRotateEnd(this);
}

resetState();
break;

case MotionEvent.ACTION_CANCEL:
if (!mSloppyGesture) {
mListener.onRotateEnd(this);
}

resetState();
break;

case MotionEvent.ACTION_MOVE:
updateStateByEvent(event);

// Only accept the event if our relative pressure is within
// a certain limit. This can help filter shaky data as a
// finger is lifted.
if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
final boolean updatePrevious = mListener.onRotate(this);
if (updatePrevious) {
mPrevEvent.recycle();
mPrevEvent = MotionEvent.obtain(event);
}
}
break;
}
}

@Override
protected void resetState() {
super.resetState();
mSloppyGesture = false;
}

/**
* Return the rotation difference from the previous rotate event to the current
* event.
*
* @return The current rotation //difference in degrees.
*/
public float getRotationDegreesDelta() {
double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX) - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX);
return (float) (diffRadians * 180 / Math.PI);
}
}

最后把我们结合了ScaleDetector、MoveDetector、RotateDetector的一个手势缩放ImageView的代码给大家:


package com.leo.gestureimageview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

import com.leo.gestureimageview.GestureDetectors.MoveGestureDetector;
import com.leo.gestureimageview.GestureDetectors.RotateGestureDetector;

public class MatrixImageView2 extends ImageView {
private Matrix mMatrix = new Matrix();
private float mScaleFactor =1f;
private float mRotationDegrees = 0.f;
private float mFocusX = 0.f;
private float mFocusY = 0.f;

private ScaleGestureDetector mScaleDetector;
private RotateGestureDetector mRotateDetector;
private MoveGestureDetector mMoveDetector;
public MatrixImageView2(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}

private void initView() {
//初始化模式为初始状态
DisplayMetrics dm = getResources().getDisplayMetrics();
//给ImageView设置一张图片(此处为了测试直接在imageview里面设置了一张测试图片)
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
mRotateDetector = new RotateGestureDetector(getContext(), new RotateListener());
mMoveDetector = new MoveGestureDetector(getContext(), new MoveListener());
mFocusX = dm.widthPixels/2f;
mFocusY = dm.heightPixels/2f;

}

@Override
public boolean onTouchEvent(MotionEvent event) {
//把缩放事件给mScaleDetector
mScaleDetector.onTouchEvent(event);
//把旋转事件个mRotateDetector
mRotateDetector.onTouchEvent(event);
//把移动事件给mMoveDetector
mMoveDetector.onTouchEvent(event);
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor(); // scale change since previous event
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
changeMatrix();
return true;
}
}
private class RotateListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
@Override
public boolean onRotate(RotateGestureDetector detector) {
mRotationDegrees -= detector.getRotationDegreesDelta();
changeMatrix();
return true;
}
}
private class MoveListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
@Override
public boolean onMove(MoveGestureDetector detector) {
PointF d = detector.getFocusDelta();
mFocusX += d.x;
mFocusY += d.y;
changeMatrix();
return true;
}

}
private void changeMatrix(){
float scaledImageCenterX = (getDrawable().getIntrinsicWidth()*mScaleFactor)/2;
float scaledImageCenterY = (getDrawable().getIntrinsicHeight()*mScaleFactor)/2;
mMatrix.reset();
mMatrix.postScale(mScaleFactor, mScaleFactor);
mMatrix.postRotate(mRotationDegrees, scaledImageCenterX, scaledImageCenterY);
mMatrix.postTranslate(mFocusX - scaledImageCenterX, mFocusY - scaledImageCenterY);
setImageMatrix(mMatrix);
}
}

好啦~~~小伙伴也可以自己下载一下这个框架的代码去研究,我这呢也只是把自己学习的心得分享给大家。
https://github.com/Almeros/android-gesture-detectors

嗯嗯!说了那么多,最后让我们看看传说中的PhotoView到底是咋实现的。

photoview的github链接:
https://github.com/chrisbanes/PhotoViewary/

看完我们之前的内容,再去看PhotoView的话,你可能不会那么迷茫了,下面让我们一起揭开它的神秘面纱:

首先PhotoView的用法呢,很简单,小伙伴像用ImageView一样用它就可以了:


<uk.co.senab.photoview.PhotoView
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitxy"
/>

好啦!!现在就可以对图片进行缩放、旋转、移动操作啦~是不是很爽呢?

但是注意:

photoview的缩放类型不支持,不然就直接报错退出了:

android:scaleType="matrix"

我们来看看它的源码:


public class PhotoView extends ImageView implements IPhotoView {

private PhotoViewAttacher mAttacher;

private ScaleType mPendingScaleType;

public PhotoView(Context context) {
this(context, null);
}

public PhotoView(Context context, AttributeSet attr) {
this(context, attr, 0);
}

public PhotoView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
super.setScaleType(ScaleType.MATRIX);
init();
}

protected void init() {
if (null == mAttacher || null == mAttacher.getImageView()) {
mAttacher = new PhotoViewAttacher(this);
}

if (null != mPendingScaleType) {
setScaleType(mPendingScaleType);
mPendingScaleType = null;
}
}

@Override
public void setRotationTo(float rotationDegree) {
mAttacher.setRotationTo(rotationDegree);
}

@Override
public void setRotationBy(float rotationDegree) {
mAttacher.setRotationBy(rotationDegree);
}

@Override
public boolean canZoom() {
return mAttacher.canZoom();
}

@Override
public RectF getDisplayRect() {
return mAttacher.getDisplayRect();
}

@Override
public void getDisplayMatrix(Matrix matrix) {
mAttacher.getDisplayMatrix(matrix);
}

@Override
public boolean setDisplayMatrix(Matrix finalRectangle) {
return mAttacher.setDisplayMatrix(finalRectangle);
}

@Override
public float getMinimumScale() {
return mAttacher.getMinimumScale();
}

@Override
public float getMediumScale() {
return mAttacher.getMediumScale();
}

@Override
public float getMaximumScale() {
return mAttacher.getMaximumScale();
}

@Override
public float getScale() {
return mAttacher.getScale();
}

@Override
public ScaleType getScaleType() {
return mAttacher.getScaleType();
}

@Override
public Matrix getImageMatrix() {
return mAttacher.getImageMatrix();
}

@Override
public void setAllowParentInterceptOnEdge(boolean allow) {
mAttacher.setAllowParentInterceptOnEdge(allow);
}

@Override
public void setMinimumScale(float minimumScale) {
mAttacher.setMinimumScale(minimumScale);
}

@Override
public void setMediumScale(float mediumScale) {
mAttacher.setMediumScale(mediumScale);
}

@Override
public void setMaximumScale(float maximumScale) {
mAttacher.setMaximumScale(maximumScale);
}

@Override
public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
mAttacher.setScaleLevels(minimumScale, mediumScale, maximumScale);
}

@Override
// setImageBitmap calls through to this method
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
if (null != mAttacher) {
mAttacher.update();
}
}

@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
if (null != mAttacher) {
mAttacher.update();
}
}

@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
if (null != mAttacher) {
mAttacher.update();
}
}

@Override
protected boolean setFrame(int l, int t, int r, int b) {
boolean changed = super.setFrame(l, t, r, b);
if (null != mAttacher) {
mAttacher.update();
}
return changed;
}

@Override
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
mAttacher.setOnMatrixChangeListener(listener);
}

@Override
public void setOnLongClickListener(OnLongClickListener l) {
mAttacher.setOnLongClickListener(l);
}

@Override
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
mAttacher.setOnPhotoTapListener(listener);
}

@Override
public void setOnViewTapListener(OnViewTapListener listener) {
mAttacher.setOnViewTapListener(listener);
}

@Override
public void setScale(float scale) {
mAttacher.setScale(scale);
}

@Override
public void setScale(float scale, boolean animate) {
mAttacher.setScale(scale, animate);
}

@Override
public void setScale(float scale, float focalX, float focalY, boolean animate) {
mAttacher.setScale(scale, focalX, focalY, animate);
}

@Override
public void setScaleType(ScaleType scaleType) {
if (null != mAttacher) {
mAttacher.setScaleType(scaleType);
} else {
mPendingScaleType = scaleType;
}
}

@Override
public void setZoomable(boolean zoomable) {
mAttacher.setZoomable(zoomable);
}

@Override
public Bitmap getVisibleRectangleBitmap() {
return mAttacher.getVisibleRectangleBitmap();
}

@Override
public void setZoomTransitionDuration(int milliseconds) {
mAttacher.setZoomTransitionDuration(milliseconds);
}

@Override
public IPhotoView getIPhotoViewImplementation() {
return mAttacher;
}

@Override
public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
mAttacher.setOnDoubleTapListener(newOnDoubleTapListener);
}

@Override
public void setOnScaleChangeListener(PhotoViewAttacher.OnScaleChangeListener onScaleChangeListener) {
mAttacher.setOnScaleChangeListener(onScaleChangeListener);
}

@Override
public void setOnSingleFlingListener(PhotoViewAttacher.OnSingleFlingListener onSingleFlingListener) {
mAttacher.setOnSingleFlingListener(onSingleFlingListener);
}

@Override
protected void onDetachedFromWindow() {
mAttacher.cleanup();
mAttacher = null;
super.onDetachedFromWindow();
}

@Override
protected void onAttachedToWindow() {
init();
super.onAttachedToWindow();
}
}

可以看到,代码并不多,才200多行(哈哈!!我们自己实现的MatrixImageView 100行都还不到呢!!开玩笑哈,PhotoView里面考虑的东西跟兼容性,我们写的MatrixImageView远远不及哈),主要的处理所及都在PhotoViewAttacher这个类中:

PhotoViewAttacher.java:

代码太多,我们看看它的构造方法


public PhotoViewAttacher(ImageView imageView, boolean zoomable) {
mImageView = new WeakReference<>(imageView);

imageView.setDrawingCacheEnabled(true);
imageView.setOnTouchListener(this);

ViewTreeObserver observer = imageView.getViewTreeObserver();
if (null != observer)
observer.addOnGlobalLayoutListener(this);

// Make sure we using MATRIX Scale Type
setImageViewScaleTypeMatrix(imageView);

if (imageView.isInEditMode()) {
return;
}
// Create Gesture Detectors...
mScaleDragDetector = VersionedGestureDetector.newInstance(
imageView.getContext(), this);

mGestureDetector = new GestureDetector(imageView.getContext(),
new GestureDetector.SimpleOnGestureListener() {

// forward long click listener
@Override
public void onLongPress(MotionEvent e) {
if (null != mLongClickListener) {
mLongClickListener.onLongClick(getImageView());
}
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
 float velocityX, float velocityY) {
if (mSingleFlingListener != null) {
if (getScale() > DEFAULT_MIN_SCALE) {
return false;
}

if (MotionEventCompat.getPointerCount(e1) > SINGLE_TOUCH
 || MotionEventCompat.getPointerCount(e2) > SINGLE_TOUCH) {
return false;
}

return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY);
}
return false;
}
});

mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
mBaseRotation = 0.0f;

// Finally, update the UI so that we're zoomable
setZoomable(zoomable);
}

可以看到,它也是创建了一个mScaleDragDetector跟一个mGestureDetector用于监听手势变幻,那么事件处理在什么地方呢?

我们在构造方法还发现了一行代码,给当前imageView设置触碰监听:

imageView.setOnTouchListener(this);

小伙伴猜都猜到了,现在就是把事件给事件 * 了:


@Override
public boolean onTouch(View v, MotionEvent ev) {
boolean handled = false;

if (mZoomEnabled && hasDrawable((ImageView) v)) {
ViewParent parent = v.getParent();
switch (ev.getAction()) {
case ACTION_DOWN:
// First, disable the Parent from intercepting the touch
// event
if (null != parent) {
parent.requestDisallowInterceptTouchEvent(true);
} else {
LogManager.getLogger().i(LOG_TAG, "onTouch getParent() returned null");
}

// If we're flinging, and the user presses down, cancel
// fling
cancelFling();
break;

case ACTION_CANCEL:
case ACTION_UP:
// If the user has zoomed less than min scale, zoom back
// to min scale
if (getScale() < mMinScale) {
RectF rect = getDisplayRect();
if (null != rect) {
v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
 rect.centerX(), rect.centerY()));
handled = true;
}
}
break;
}

// Try the Scale/Drag detector
if (null != mScaleDragDetector) {
boolean wasScaling = mScaleDragDetector.isScaling();
boolean wasDragging = mScaleDragDetector.isDragging();

handled = mScaleDragDetector.onTouchEvent(ev);

boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();

mBlockParentIntercept = didntScale && didntDrag;
}

// Check to see if the user double tapped
if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
handled = true;
}

}

return handled;
}

最后处理完毕事件后,就是一系列的回调了,回调完毕后就应该给ImageView重新设置matrix对象了,比如缩放:


@Override
public void setScale(float scale, float focalX, float focalY,
boolean animate) {
ImageView imageView = getImageView();

if (null != imageView) {
// Check to see if the scale is within bounds
if (scale < mMinScale || scale > mMaxScale) {
LogManager
.getLogger()
.i(LOG_TAG,
"Scale must be within the range of minScale and maxScale");
return;
}

if (animate) {
imageView.post(new AnimatedZoomRunnable(getScale(), scale,
focalX, focalY));
} else {
mSuppMatrix.setScale(scale, scale, focalX, focalY);
checkAndDisplayMatrix();
}
}
}

其它的类似哈~~~ 代码还是挺多的(考虑的情况比较多)可想而之,要写好一个自定义组件还不是那么简单的事哦,不过还是加油吧~!

标签:Android,GestureDetector,手势
0
投稿

猜你喜欢

  • Android 短信转换成彩信的消息数量(实例代码)

    2021-11-13 13:01:58
  • Android Compose 属性动画使用探索详解

    2022-08-07 11:06:57
  • C#实现自定义Dictionary类实例

    2022-12-19 09:23:47
  • 使用Android studio创建的AIDL编译时找不到自定义类的解决办法

    2023-06-23 10:59:41
  • java学习DongTai被动型IAST工具部署过程

    2023-06-21 09:43:03
  • MyBatis Mapper接受参数的四种方式代码解析

    2021-09-05 19:28:27
  • Android开发:浅谈MVP模式应用与内存泄漏问题解决

    2023-05-01 20:42:43
  • C#和SQL实现的字符串相似度计算代码分享

    2021-06-10 14:23:20
  • C#使用iTextSharp封装的PDF文件操作类实例

    2023-05-16 22:26:52
  • java中的静态代码块、构造代码块、构造方法详解

    2023-06-18 12:11:32
  • Unity3d 使用Gizmos画一个圆圈

    2022-05-22 05:10:52
  • Android支付宝支付的示例代码

    2022-06-20 02:32:49
  • Chrome Visual Studio 2005下的编译过程

    2022-06-06 02:54:23
  • 深入了解Java核心类库--BigDecimal和System类

    2023-12-18 01:50:28
  • Spring项目中使用Junit单元测试并配置数据源的操作

    2022-06-02 05:32:27
  • 详解JavaScript中的函数声明和函数表达式

    2023-04-26 01:56:07
  • Android异步下载图片并且缓存图片到本地DEMO详解

    2022-10-27 14:16:17
  • Unity中 ShaderGraph 实现超级炫酷的溶解效果入门级教程

    2021-11-15 01:48:08
  • Java集合框架Collections原理及用法实例

    2021-12-04 18:10:20
  • android 封装抓取网页信息的实例代码

    2021-11-28 09:40:32
  • asp之家 软件编程 m.aspxhome.com