Android 手势检测

手势检测

Android 手势检测,也就是对 GestureDetector 的使用。
而 GestureDetector 则可以使用 MotionEvents 检测各种手势和事件。
GestureDetector.OnGestureListener 是个回调方法,在发生特定事件时会调用 Listener 中对应的方法回调。
这个类只能用于检测触摸事件的 MotionEvent,不能用于轨迹球事件。

使用步骤

  1. 创建一个 GestrueDetector 实例
  2. 在 onTouchEvent(MontionEvent) 方法中,确保调用 GestrueDetector 实例的onTouchEvent(MotionEvent)。回调中定义的方法将在事件发生时执行。
  3. 如果侦听 onContextClick(MotionEvent),则必须在 View 的 onGenericMotionEvent(MotionEvent)中调用 GestureDetector OnGenericMotionEvent(MotionEvent)。

GestureDetector

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package android.view;

import android.content.Context;
import android.os.Handler;

public class GestureDetector {

	/**
	* GestureDetector 共有5种构造函数
	* 但前两种已舍弃,一种是重复的。
	* 所以,只需要关注两种。
	**/

	/**
	* 已舍弃
	**/
    @Deprecated
    public GestureDetector(GestureDetector.OnGestureListener listener, Handler handler) {
        throw new RuntimeException("Stub!");
    }

    /**
	* 已舍弃
	**/
    @Deprecated
    public GestureDetector(GestureDetector.OnGestureListener listener) {
        throw new RuntimeException("Stub!");
    }
	
    /**
	* 关注这个构造函数
	* context:上下文
	* listener:手势监听器
	**/
    public GestureDetector(Context context, GestureDetector.OnGestureListener listener) {
        throw new RuntimeException("Stub!");
    }
	
    /**
	* 关注这个构造函数
	* context:上下文
	* listener:手势监听器
	* handler:用于给 GestureDetector 提供一个Looper
	* 由于GestureDetector会在内部自动创建一个Handler用于处理数据,如果是在主线程创建的GestureDetector,其内部的Hanlder会自动获得主线程的Looper。
	* 但如果是在一个没有创建Looper的子线程中创建的GestureDetector,则需要传递一个带有Looper的Handler给它,否则会因为无法获取到Looper导致创建失败。
	**/
    public GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler) {
        throw new RuntimeException("Stub!");
    }

    public GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler, boolean unused) {
        throw new RuntimeException("Stub!");
    }

    public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) {
        throw new RuntimeException("Stub!");
    }

    public void setContextClickListener(GestureDetector.OnContextClickListener onContextClickListener) {
        throw new RuntimeException("Stub!");
    }

    public void setIsLongpressEnabled(boolean isLongpressEnabled) {
        throw new RuntimeException("Stub!");
    }

    public boolean isLongpressEnabled() {
        throw new RuntimeException("Stub!");
    }

    public boolean onTouchEvent(MotionEvent ev) {
        throw new RuntimeException("Stub!");
    }

    public boolean onGenericMotionEvent(MotionEvent ev) {
        throw new RuntimeException("Stub!");
    }

	/**
	* 四种手势监听器:
	* OnContextClickListener
	* OnDoubleTapListener
	* OnGestureListener	
	* SimpleOnGestureListener
	**/
	
	/**
	* 另外三个接口的空实现。
	* 一般情况下,使用这个就好了,比较方便。
	**/
    public static class SimpleOnGestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, GestureDetector.OnContextClickListener {
        public SimpleOnGestureListener() {
            throw new RuntimeException("Stub!");
        }

        public boolean onSingleTapUp(MotionEvent e) {
            throw new RuntimeException("Stub!");
        }

        public void onLongPress(MotionEvent e) {
            throw new RuntimeException("Stub!");
        }

        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            throw new RuntimeException("Stub!");
        }

        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            throw new RuntimeException("Stub!");
        }

        public void onShowPress(MotionEvent e) {
            throw new RuntimeException("Stub!");
        }

        public boolean onDown(MotionEvent e) {
            throw new RuntimeException("Stub!");
        }

        public boolean onDoubleTap(MotionEvent e) {
            throw new RuntimeException("Stub!");
        }

        public boolean onDoubleTapEvent(MotionEvent e) {
            throw new RuntimeException("Stub!");
        }

        public boolean onSingleTapConfirmed(MotionEvent e) {
            throw new RuntimeException("Stub!");
        }

        public boolean onContextClick(MotionEvent e) {
            throw new RuntimeException("Stub!");
        }
    }
	
	/**
	* Android6.0(API23)添加的
	* 用于检测外部设备上的按钮是否按下的,
	* 例如蓝牙触控笔上的按钮,一般情况下,忽略即可。
	**/
    public interface OnContextClickListener {
        boolean onContextClick(MotionEvent var1);
    }
	
	/**
	* 双击事件,有三个回调类型:
	* 双击(DoubleTap)、单击确认(SingleTapConfirmed) 和 双击事件回调(DoubleTapEvent)
	**/
    public interface OnDoubleTapListener {
        boolean onSingleTapConfirmed(MotionEvent var1);

        boolean onDoubleTap(MotionEvent var1);

        boolean onDoubleTapEvent(MotionEvent var1);
    }
	
	/**
	* 手势检测,主要有以下类型事件:
	* 按下(Down)、 抛(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress) 和 单击抬起(SingleTapUp)
	**/
    public interface OnGestureListener {
	
		// 保证控件拥有消费事件的能力,以接受后续的事件。
        boolean onDown(MotionEvent var1);
		
		// 监听用户按下时
		// 延迟回调,延迟时间180ms
		// 假如用户手指按下后立即抬起或者事件立即被拦截,时间没有超过 180 ms的话,这条消息会被 remove 掉
        void onShowPress(MotionEvent var1);
		
		// 监听用户单击抬起时
		// 区别在于双击时的触发次数:
		// onSingleTapUp:1次(在双击的第一次抬起时触发)
		// onSingleTapConfirmed:0次(双击发生时不会触发)
		// onClick:2次(在双击事件时触发两次)
		//
		// 触发顺序如下:
		// onSingleTapUp
		// onClick
		// onDoubleTap // <- 双击
		// onClick
        boolean onSingleTapUp(MotionEvent var1);
		
		// 监听滚动事件
		// var1:手指按下时的Event
		// var2:手指提起时的Event
		// var3:在 X 轴上划过的距离
		// var4:在 Y 轴上划过的距离
        boolean onScroll(MotionEvent var1, MotionEvent var2, float var3, float var4);
		
		// 检测长按事件
        void onLongPress(MotionEvent var1);
		
		// 扔,抛,常见与列表中
		// var1:手指按下时的Event
		// var2:手指提起时的Event
		// var3:在 X 轴上的运动速度(像素/秒)
		// var4:在 Y 轴上的运动速度(像素/秒)
        boolean onFling(MotionEvent var1, MotionEvent var2, float var3, float var4);
    }
}

示例

使用无 Handler 的构造函数

    // 创建一个监听回调
    final GestureDetector.SimpleOnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener(){
        /**
         * 单击事件发生后300ms后触发
         */
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            // 如果想同时监听双击与单击事件,则监听单击事件不推荐使用OnClickListener,原因有二:
            // 一,它们是存在冲突的,若要同时出发,则 setOnTouchListener 不能消费事件,如果 onTouchListener 消费了事件,就可能导致 OnClick 无法正常触发。
            // 二,使用 OnClickListener 会在双击事件发生时触发两次
            Toast.makeText(MainActivity.this,"不要单击",Toast.LENGTH_SHORT).show();
            return super.onSingleTapConfirmed(e);
        }
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            Toast.makeText(MainActivity.this,"双击666",Toast.LENGTH_SHORT).show();
            Log.e("TAG","第二次按下时触发");
            return super.onDoubleTap(e);
        }

        /**
         * 与 onDoubleTap 的差别很小
         *  onDoubleTapEvent 用来进行更细微的控制
         *
         *  函数的执行顺序如下:
         *  onDoubleTap
         *  onDoubleTapEvent - down
         *  onDoubleTapEvent - move
         *  onDoubleTapEvent - up
         */
        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
            switch (e.getActionMasked()){
                case MotionEvent.ACTION_DOWN:
                    Log.e("TAG","ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e("TAG","ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e("TAG","第二次抬起时触发");
                    break;
                    default:
                        break;
            }
            return super.onDoubleTapEvent(e);
        }
    };

    // 创建一个检测器
    GestureDetector gestureDetector;
	
    private void initData() {
        // 使用无Handler的构造函数
          gestureDetector = new GestureDetector(this,onGestureListener);

        // 给监听器设置数据源
        btn_doubleTap.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                return gestureDetector.onTouchEvent(motionEvent);
            }
        });
    }

使用有 Handler 的构造函数

	/**
	* 由于GestureDetector会在内部自动创建一个Handler用于处理数据,
	* 如果是在主线程创建的GestureDetector,其内部的Hanlder会自动获得主线程的Looper。
	* 可以直接使用无Handler的构造函数
	*
	* 但如果是在一个没有创建Looper的子线程中创建的GestureDetector,
	* 则需要传递一个带有Looper的Handler给它,否则会因为无法获取到Looper导致创建失败。
	* 需要使用带Handler的构造函数
	**/
 
	// 创建一个监听回调
    final GestureDetector.SimpleOnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            Toast.makeText(MainActivity.this,"双击666",Toast.LENGTH_SHORT).show();
            return super.onDoubleTap(e);
        }
    };
    // 创建一个检测器
    GestureDetector gestureDetector;
    private void initData() {

        // 使用有Handler的构造函数的几种方式;
        // 1.主线程中创建Handler
        // 重点在于传递的 Handler 一定要有 Looper
        final Handler handler = new Handler();
        // 主线程创建的hanlder会自动关联主线程的Looper
        new Thread(new Runnable() {
            @Override
            public void run() {
                gestureDetector = new GestureDetector(MainActivity.this,onGestureListener,handler);
            }
        }).start();

        // 2.子线程中创建Handler
        // 重点在于传递的 Handler 一定要有 Looper
        new Thread(new Runnable() {
            @Override
            public void run() {
                Handler handler1 = new Handler(Looper.getMainLooper());
                gestureDetector = new GestureDetector(MainActivity.this,onGestureListener,handler1);
            }
        }).start();

        // 3.若子线程准备了 Looper 那么可以直接使用无Handler的构造函数进行创建
        // 重点在于传递的 Handler 一定要有 Looper
        new Thread(new Runnable() {
            @Override public void run() {
                Looper.prepare(); // <- 重点在这里
                gestureDetector = new GestureDetector(MainActivity.this,onGestureListener);
            }
        }).start();

        // 给监听器设置数据源
        btn_doubleTap.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                return gestureDetector.onTouchEvent(motionEvent);
            }
        });
    }

相关方法

除了各类监听器之外,还有几个相关的方法

方法说明
setIsLongpressEnabled是否允许触发长按事件,true 表示允许,false 表示不允许。
isLongpressEnabled判断当前是否允许触发长按事件,true 表示允许,false 表示不允许。
onTouchEvent这个是其中一个重要的方法,在最开始已经演示过使用方式了。
onGenericMotionEvent这个是在 API 23 之后才添加的内容,主要是为 OnContextClickListener 服务的,暂时不用关注。
setContextClickListener设置 ContextClickListener 。
setOnDoubleTapListener设置 OnDoubleTapListener 。

缩放手势检测

Android 缩放手势检测,ScaleGestureDetector
一般来讲,缩放手势都不是单独存在的。
如果用在自定义控件上,则配合 Matrix 相关内容使用更好。
使用方式与 GestureDetector 类似,也是通过 Listener 进行监听用户的操作手势,它是对缩放手势进行了一次封装。

ScaleGestureDetector

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package android.view;

import android.content.Context;
import android.os.Handler;

public class ScaleGestureDetector {

/**
* 有两个构造函数
**/
    public ScaleGestureDetector(Context context, ScaleGestureDetector.OnScaleGestureListener listener) {
        throw new RuntimeException("Stub!");
    }

    public ScaleGestureDetector(Context context, ScaleGestureDetector.OnScaleGestureListener listener, Handler handler) {
        throw new RuntimeException("Stub!");
    }

    public boolean onTouchEvent(MotionEvent event) {
        throw new RuntimeException("Stub!");
    }

    public void setQuickScaleEnabled(boolean scales) {
        throw new RuntimeException("Stub!");
    }

    public boolean isQuickScaleEnabled() {
        throw new RuntimeException("Stub!");
    }

    public void setStylusScaleEnabled(boolean scales) {
        throw new RuntimeException("Stub!");
    }

    public boolean isStylusScaleEnabled() {
        throw new RuntimeException("Stub!");
    }

    public boolean isInProgress() {
        throw new RuntimeException("Stub!");
    }

    public float getFocusX() {
        throw new RuntimeException("Stub!");
    }

    public float getFocusY() {
        throw new RuntimeException("Stub!");
    }

    public float getCurrentSpan() {
        throw new RuntimeException("Stub!");
    }

    public float getCurrentSpanX() {
        throw new RuntimeException("Stub!");
    }

    public float getCurrentSpanY() {
        throw new RuntimeException("Stub!");
    }

    public float getPreviousSpan() {
        throw new RuntimeException("Stub!");
    }

    public float getPreviousSpanX() {
        throw new RuntimeException("Stub!");
    }

    public float getPreviousSpanY() {
        throw new RuntimeException("Stub!");
    }

    public float getScaleFactor() {
        throw new RuntimeException("Stub!");
    }

    public long getTimeDelta() {
        throw new RuntimeException("Stub!");
    }

    public long getEventTime() {
        throw new RuntimeException("Stub!");
    }
	
	/**
	* 两个手势监听器
	* SimpleOnScaleGestureListener:缩放收拾检测器的空实现
	* OnScaleGestureListener:缩放收拾检测器
	**/
	
    public static class SimpleOnScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
        public SimpleOnScaleGestureListener() {
            throw new RuntimeException("Stub!");
        }

        public boolean onScale(ScaleGestureDetector detector) {
            throw new RuntimeException("Stub!");
        }

        public boolean onScaleBegin(ScaleGestureDetector detector) {
            throw new RuntimeException("Stub!");
        }

        public void onScaleEnd(ScaleGestureDetector detector) {
            throw new RuntimeException("Stub!");
        }
    }

    public interface OnScaleGestureListener {
		
		// 缩放被触发(会调用0次或者多次),如果返回 true 则表示当前缩放事件已经被处理,检测器会重新积累缩放因子,返回 false 则会继续积累缩放因子。
        boolean onScale(ScaleGestureDetector var1);
		
		// 缩放手势开始,当两个手指放在屏幕上的时候会调用该方法(只调用一次)。如果返回 false 则表示不使用当前这次缩放手势。
        boolean onScaleBegin(ScaleGestureDetector var1);
		
		// 缩放手势结束
        void onScaleEnd(ScaleGestureDetector var1);
    }
}

示例

public class ScaleGestureDemoView extends View{

    private static final String TAG="ScaleGestureDemoView";

    private ScaleGestureDetector scaleGestureDetector;

    public ScaleGestureDemoView(Context context) {
        super(context);
        init();
    }

    public ScaleGestureDemoView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        scaleGestureDetector=new ScaleGestureDetector(getContext(),new ScaleGestureDetector.SimpleOnScaleGestureListener(){
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                // 缩放手势触发
                // 主要关心这两个值,缩放中心点和缩放因子。
                // 中心点:将所有的坐标都加起来,然后除以数量。
                // 缩放因子:计算各个手指到焦点的平均距离,在用户手指移动后用新的平均距离除以旧的平均距离,并以此计算得出缩放比例。
                Log.e(TAG, "focusX = " + detector.getFocusX());       // 缩放中心,x坐标
                Log.e(TAG, "focusY = " + detector.getFocusY());       // 缩放中心y坐标
                Log.e(TAG, "scale = " + detector.getScaleFactor());   // 缩放因子
                return true;
            }

            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                // 缩放手势开始
                Log.e(TAG,"缩放手势开始");
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
//                super.onScaleEnd(detector);
                // 缩放手势结束
                Log.e(TAG,"缩放手势结束");
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        scaleGestureDetector.onTouchEvent(event);
        return true;
    }
}

综合示例

public class GestureDemoView extends View {

    GestureDetector mGestureDetector;
    ScaleGestureDetector mScaleGestureDetector;

    // 画布当前的 Matrix, 用于获取当前画布的一些状态信息,例如缩放大小,平移距离等
    private Matrix mCanvasMatrix = new Matrix();

    // 将用户触摸的坐标转换为画布上坐标所需的 Matrix, 以便找到正确的缩放中心位置
    private Matrix mInvertMatrix = new Matrix();

    // 所有用户触发的缩放、平移等操作都通过下面的 Matrix 直接作用于画布上,
    // 将系统计算的一些初始缩放平移信息与用户操作的信息进行隔离,让操作更加直观
    private Matrix mUserMatrix = new Matrix();

    private Bitmap mBitmap;

    // 基础的缩放和平移信息,该信息与用户的手势操作无关
    private float mBaseScale;
    private float mBaseTranslateX;
    private float mBaseTranslateY;

    private Paint mPaint;

    public GestureDemoView(Context context) {
        super(context);
    }

    public GestureDemoView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        initGesture(context);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        if (mBitmap.getWidth() * 1.0f / mBitmap.getHeight() > w * 1.0f / h) {
            mBaseScale = w * 1.0f / mBitmap.getWidth();
            mBaseTranslateX = 0;
            mBaseTranslateY = (h - mBitmap.getHeight() * mBaseScale) / 2;
        } else {
            mBaseScale = h * 1.0f / mBitmap.getHeight() * 1.0f;
            mBaseTranslateX = (w - mBitmap.getWidth() * mBaseScale) / 2;
            mBaseTranslateY = 0;
        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(6);
        canvas.translate(mBaseTranslateX, mBaseTranslateY);
        canvas.scale(mBaseScale, mBaseScale);

        canvas.save();
        canvas.concat(mUserMatrix);

        mCanvasMatrix = canvas.getMatrix();
        mCanvasMatrix.invert(mInvertMatrix);

        canvas.drawBitmap(mBitmap, 0, 0, mPaint);
        canvas.restore();
    }


    //--- 手势处理 ----------------------------------------------------------------------------------

    private void initGesture(Context context) {
        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                float scale = getMatrixValue(MSCALE_X, mCanvasMatrix);
                mUserMatrix.preTranslate(-distanceX / scale, -distanceY / scale);
                //fixTranslate();   // 在用户滚动时不进行修正,保证用户滚动时也有响应, 在用户抬起手指后进行修正
                invalidate();
                return true;
            }

            @Override
            public boolean onDoubleTap(MotionEvent e) {
                if (!mUserMatrix.isIdentity()) {
                    mUserMatrix.reset();
                } else {
                    float[] points = mapPoint(e.getX(), e.getY(), mInvertMatrix);
                    mUserMatrix.postScale(MAX_SCALE, MAX_SCALE, points[0], points[1]);
                }
                fixTranslate();
                invalidate();
                return true;
            }
        });

        mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                float scaleFactor = detector.getScaleFactor();
                float fx = detector.getFocusX();
                float fy = detector.getFocusY();
                float[] points = mapPoint(fx, fy, mInvertMatrix);
                scaleFactor = getRealScaleFactor(scaleFactor);
                mUserMatrix.preScale(scaleFactor, scaleFactor, points[0], points[1]);
                fixTranslate();
                invalidate();
                return true;
            }

        });
    }

    // 修正缩放
    private void fixTranslate() {
        // 对 Matrix 进行预计算,并根据计算结果进行修正
        Matrix viewMatrix = getMatrix();    // 获取当前控件的Matrix
        viewMatrix.preTranslate(mBaseTranslateX, mBaseTranslateY);
        viewMatrix.preScale(mBaseScale, mBaseScale);
        viewMatrix.preConcat(mUserMatrix);
        Matrix invert = new Matrix();
        viewMatrix.invert(invert);
        Rect rect = new Rect();
        getGlobalVisibleRect(rect);

        float userScale = getMatrixValue(MSCALE_X, mUserMatrix);
        float scale = getMatrixValue(MSCALE_X, viewMatrix);

        float[] center = mapPoint(mBitmap.getWidth() / 2.0f, mBitmap.getHeight() / 2.0f, viewMatrix);
        float distanceX = center[0] - getWidth() / 2.0f;
        float distanceY = center[1] - getHeight() / 2.0f;
        float[] wh = mapVectors(mBitmap.getWidth(), mBitmap.getHeight(), viewMatrix);

        if (userScale <= 1.0f) {
            mUserMatrix.preTranslate(-distanceX / scale, -distanceY / scale);
        } else {
            float[] lefttop = mapPoint(0, 0, viewMatrix);
            float[] rightbottom = mapPoint(mBitmap.getWidth(), mBitmap.getHeight(), viewMatrix);

            // 如果宽度小于总宽度,则水平居中
            if (wh[0] < getWidth()) {
                mUserMatrix.preTranslate(distanceX / scale, 0);
            } else {
                if (lefttop[0] > 0) {
                    mUserMatrix.preTranslate(-lefttop[0] / scale, 0);
                } else if (rightbottom[0] < getWidth()) {
                    mUserMatrix.preTranslate((getWidth() - rightbottom[0]) / scale, 0);
                }

            }
            // 如果高度小于总高度,则垂直居中
            if (wh[1] < getHeight()) {
                mUserMatrix.preTranslate(0, -distanceY / scale);
            } else {
                if (lefttop[1] > 0) {
                    mUserMatrix.preTranslate(0, -lefttop[1] / scale);
                } else if (rightbottom[1] < getHeight()) {
                    mUserMatrix.preTranslate(0, (getHeight() - rightbottom[1]) / scale);
                }
            }
        }
        invalidate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        mScaleGestureDetector.onTouchEvent(event);
        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
            fixTranslate();
        }
        return true;
    }


    //--- Tools ------------------------------------------------------------------------------------

    //--- 将坐标转换为画布坐标 ---
    private float[] mapPoint(float x, float y, Matrix matrix) {
        float[] temp = new float[2];
        temp[0] = x;
        temp[1] = y;
        matrix.mapPoints(temp);
        return temp;
    }

    private float[] mapVectors(float x, float y, Matrix matrix) {
        float[] temp = new float[2];
        temp[0] = x;
        temp[1] = y;
        matrix.mapVectors(temp);
        return temp;
    }


    //--- 获取 Matrix 中的属性 ---
    private float[] matrixValues = new float[9];
    private static final int MSCALE_X = 0, MSKEW_X = 1, MTRANS_X = 2;
    private static final int MSKEW_Y = 3, MSCALE_Y = 4, MTRANS_Y = 5;
    private static final int MPERSP_0 = 6, MPERSP_1 = 7, MPERSP_2 = 8;

    @IntDef({MSCALE_X, MSKEW_X, MTRANS_X, MSKEW_Y, MSCALE_Y, MTRANS_Y, MPERSP_0, MPERSP_1, MPERSP_2})
    @Retention(RetentionPolicy.SOURCE)
    private @interface MatrixName {}

    private float getMatrixValue(@MatrixName int name, Matrix matrix) {
        matrix.getValues(matrixValues);
        return matrixValues[name];
    }

    //--- 限制缩放比例 ---
    private static final float MAX_SCALE = 4.0f;    //最大缩放比例
    private static final float MIN_SCALE = 0.5f;    // 最小缩放比例

    private float getRealScaleFactor(float currentScaleFactor) {
        float realScale = 1.0f;
        float userScale = getMatrixValue(MSCALE_X, mUserMatrix);    // 用户当前的缩放比例
        float theoryScale = userScale * currentScaleFactor;           // 理论缩放数值

        // 如果用户在执行放大操作并且理论缩放数据大于4.0
        if (currentScaleFactor > 1.0f && theoryScale > MAX_SCALE) {
            realScale = MAX_SCALE / userScale;
        } else if (currentScaleFactor < 1.0f && theoryScale < MIN_SCALE) {
            realScale = MIN_SCALE / userScale;
        } else {
            realScale = currentScaleFactor;
        }
        return realScale;
    }
}


备注

参考资料:

手势检测

缩放手势检测

传送门:GitHub

欢迎关注微信公众号:非也缘也

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值