android手势监听

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。

专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。

未经允许不得转载

目录

一、导读

我们继续总结学习基础知识,温故知新。

本文主要描述了手势监听相关的知识。

二、概览

Android事件处理机制是基于Listener实现的,比如触摸屏相关的事件,就是通过onTouchListener实现,

通过MotionEvent的getAction()方法来获取Touch事件的类型,包括 ACTION_DOWN(按下触摸屏),

ACTION_MOVE(按下触摸屏后移动受力点), ACTION_UP(松开触摸屏)和ACTION_CANCEL(不会由用户直接触发)。

借助对于用户不同操作的判断,结合getRawX()、getRawY()、getX()和getY()等方法来获取坐标后,我们可以实现诸如拖动某一个按钮,拖动滚动条等功能。

但是如果是更复杂的一些动画,比如用户在屏幕上的手势,这就需要另外一个接口了,GestureDetector.OnGestureListener 接口。

Android 提供了GestureDetector(手势识别)类,通过这个类我们可以识别很多的手势,主要是通过他的onTouchEvent(event)方法完成了不同手势的识别。

三、使用

我们先来看下基本的使用,方法的注释我们都写代码里面

java 复制代码
private class MyGesturelistener implements GestureDetector.OnGestureListener{
    /**
     * 用户按下屏幕触发
     * @param e
     * @return
     */
    public boolean onDown(MotionEvent e) {  
        return false;  
    }  
  
    /**
     * 如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行 
     * 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发 
     * 注意和onDown()的区别,强调的是没有松开或者拖动的状态
     * @param e
     */
    public void onShowPress(MotionEvent e) {  
          
    }  
  
    /**
     * 长按触摸屏,超过一定时长,就会触发这个事件    触发顺序:    onDown->onShowPress->onLongPress 
     * @param e
     */
    public void onLongPress(MotionEvent e) {  
          
    }  
    
    /**
     * 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发  
     * 一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以也就不会触发这个事件    
     * 触发顺序:    
     * 点击一下非常快的(不滑动)Touchup:   
     * onDown->onSingleTapUp->onSingleTapConfirmed    
     * 点击一下稍微慢点的(不滑动)Touchup:    
     * onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed 
     * @param e
     */
    public boolean onSingleTapUp(MotionEvent e) {  
        return false;  
    }  
  
    /**
     * 在屏幕上拖动事件 ,用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
     * @param e1
     * onDown------》onScroll----》onScroll------》onFiling
     */
    public boolean onScroll(MotionEvent e1, MotionEvent e2,  
            float distanceX, float distanceY) {  
        return false;  
    }  
  
    /**
     * 滑屏,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发
     * @param e1 第1个ACTION_DOWN MotionEvent
     * @param e2 最后一个ACTION_MOVE MotionEvent
     * @param velocityX X轴上的移动速度,像素/秒 
     * @param velocityY Y轴上的移动速度,像素/秒
     *                  onDown-----》onScroll----》onScroll----》onScroll----》.........----->onFling  
     */
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,  
            float velocityY) {  
        return false;  
    }
}

这里有两种不同类型的滚动,也是我们平常用的比较多的方法,onScroll()、onFling()

  • 拖动 是用户在触摸屏上拖动手指时发生的滚动类型。简单的拖动通常通过重写来 onScroll()

    实现GestureDetector.OnGestureListener。有关拖动的更多信息,请参阅拖动和缩放。

  • 拖放 是用户快速拖动并抬起手指时发生的滚动类型。用户抬起手指后,他们通常希望继续滚动(移动视口),

    但要放慢速度,直到视口停止移动。拖放可以通过覆盖 onFling() 并GestureDetector.OnGestureListener使用滚动对象来实现。

还有一个双击相关的接口,我们一起来看看代码

java 复制代码
     private class MySimpleGesture extends SimpleOnGestureListener {   
        /**
         * 双击的第二下Touch down时触发
         * @param e
         * @return
         */ 
        public boolean onDoubleTap(MotionEvent e) {   
            return super.onDoubleTap(e);   
        }   
           
        /**
         * 双击的第二下Touch down和up都会触发,可用e.getAction()区分   
         * @param e
         * @return
         */ 
        public boolean onDoubleTapEvent(MotionEvent e) {   
            return super.onDoubleTapEvent(e);   
        }   
        
        /**
         * Touch down时触发 
         * @param e
         * @return
         */ 
        public boolean onDown(MotionEvent e) {   
            return super.onDown(e);   
        }   
           
        /**
         * Touch了滑动一点距离后,up时触发
         * @param e1
         * @return
         */ 
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {   
            return super.onFling(e1, e2, velocityX, velocityY);   
        }   
           
        // Touch了不移动一直Touch down时触发   
        public void onLongPress(MotionEvent e) {   
            super.onLongPress(e);   
        }   
           
        // Touch了滑动时触发   
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {   
            return super.onScroll(e1, e2, distanceX, distanceY);   
        }   
           
        /**  
         * Touch了还没有滑动时触发  
         * (1)onDown只要Touch Down一定立刻触发  
         * (2)Touch Down后过一会没有滑动先触发onShowPress再触发onLongPress  
         * So: Touch Down后一直不滑动,onDown -> onShowPress -> onLongPress这个顺序触发。  
         */  
        public void onShowPress(MotionEvent e) {   
            super.onShowPress(e);   
        }   
  
        /**  
         * 两个函数都是在Touch Down后又没有滑动(onScroll),又没有长按(onLongPress),然后Touch Up时触发  
         * 点击一下非常快的(不滑动)Touch Up: onDown->onSingleTapUp->onSingleTapConfirmed  
         * 点击一下稍微慢点的(不滑动)Touch Up: onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed   
         */    
        public boolean onSingleTapConfirmed(MotionEvent e) {   
            return super.onSingleTapConfirmed(e);   
        }   
        public boolean onSingleTapUp(MotionEvent e) {   
            return super.onSingleTapUp(e);   
        }   
    } 

下面可以看到一个片段,说明了调整尺寸所涉及的基本成分。

四、 如何实现触摸滚动

使用并重写的GestureDetector方法。用于跟踪拖放手势。如果用户在拖放手势后达到内容限制,应用程序会显示"发光"效果

java 复制代码
    // The current viewport. This rectangle represents the currently visible
    // chart domain and range. The viewport is the part of the app that the
    // user manipulates via touch gestures.
    private RectF mCurrentViewport =
            new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);

    // The current destination rectangle (in pixel coordinates) into which the
    // chart data should be drawn.
    private Rect mContentRect;

    private OverScroller mScroller;
    private RectF mScrollerStartViewport;
    ...
    private final GestureDetector.SimpleOnGestureListener mGestureListener
            = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            // Initiates the decay phase of any active edge effects.
            releaseEdgeEffects();
            mScrollerStartViewport.set(mCurrentViewport);
            // Aborts any active scroll animations and invalidates.
            mScroller.forceFinished(true);
            ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
            return true;
        }
        ...
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2,
                float velocityX, float velocityY) {
            fling((int) -velocityX, (int) -velocityY);
            return true;
        }
    };

    private void fling(int velocityX, int velocityY) {
        // Initiates the decay phase of any active edge effects.
        releaseEdgeEffects();
        // Flings use math in pixels (as opposed to math based on the viewport).
        Point surfaceSize = computeScrollSurfaceSize();
        mScrollerStartViewport.set(mCurrentViewport);
        int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left -
                AXIS_X_MIN) / (
                AXIS_X_MAX - AXIS_X_MIN));
        int startY = (int) (surfaceSize.y * (AXIS_Y_MAX -
                mScrollerStartViewport.bottom) / (
                AXIS_Y_MAX - AXIS_Y_MIN));
        // Before flinging, aborts the current animation.
        mScroller.forceFinished(true);
        // Begins the animation
        mScroller.fling(
                // Current scroll position
                startX,
                startY,
                velocityX,
                velocityY,
                /*
                 * Minimum and maximum scroll positions. The minimum scroll
                 * position is generally zero and the maximum scroll position
                 * is generally the content size less the screen size. So if the
                 * content width is 1000 pixels and the screen width is 200
                 * pixels, the maximum scroll offset should be 800 pixels.
                 */
                0, surfaceSize.x - mContentRect.width(),
                0, surfaceSize.y - mContentRect.height(),
                // The edges of the content. This comes into play when using
                // the EdgeEffect class to draw "glow" overlays.
                mContentRect.width() / 2,
                mContentRect.height() / 2);
        // Invalidates to trigger computeScroll()
        ViewCompat.postInvalidateOnAnimation(this);
    }
    

当onFling() 调用时postInvalidateOnAnimation(),它会触发computeScroll()更新 x 和 y 值。通常,这是在子视图使用滚动对象对滚动进行动画处理时完成的

我们来看一段缩放代码

java 复制代码
    // Custom object that is functionally similar to Scroller
    Zoomer mZoomer;
    private PointF mZoomFocalPoint = new PointF();
    ...

    // If a zoom is in progress (either programmatically or via double
    // touch), performs the zoom.
    if (mZoomer.computeZoom()) {
        float newWidth = (1f - mZoomer.getCurrZoom()) *
                mScrollerStartViewport.width();
        float newHeight = (1f - mZoomer.getCurrZoom()) *
                mScrollerStartViewport.height();
        float pointWithinViewportX = (mZoomFocalPoint.x -
                mScrollerStartViewport.left)
                / mScrollerStartViewport.width();
        float pointWithinViewportY = (mZoomFocalPoint.y -
                mScrollerStartViewport.top)
                / mScrollerStartViewport.height();
        mCurrentViewport.set(
                mZoomFocalPoint.x - newWidth * pointWithinViewportX,
                mZoomFocalPoint.y - newHeight * pointWithinViewportY,
                mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),
                mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));
        constrainViewport();
        needsInvalidate = true;
    }
    if (needsInvalidate) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
    

computeScrollSurfaceSize()这是上一个代码片段中调用的方法。计算可滚动表面的当前大小(以像素为单位)。

例如,如果整个图表区域可见,则这是 的当前大小mContentRect。如果图形在两个方向上放大 200%,则显示的尺寸将是水平和垂直尺寸的两倍

有关使用滚动的另一个示例,请参阅该类的源代码ViewPager。它会滚动以响应拖放手势,并使用滚动来实现"适合页面"动画。

java 复制代码
    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.f;

    public MyCustomView(Context mContext){
        ...
        // View code goes here
        ...
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);
        return true;
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        canvas.scale(mScaleFactor, mScaleFactor);
        ...
        // onDraw() code goes here
        ...
        canvas.restore();
    }

    private class ScaleListener
            extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

            invalidate();
            return true;
        }
    }
    

五、 如何拖动对象 、缩放对象

以下代码片段允许用户在屏幕上拖动对象。记录活动指针的起始位置,计算指针移动的距离,并将对象移动到新位置。正确处理附加指针的可能性

java 复制代码
    // The 'active pointer' is the one currently moving our object.
    private int mActivePointerId = INVALID_POINTER_ID;

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);

        final int action = MotionEventCompat.getActionMasked(ev);

        switch (action) {
        case MotionEvent.ACTION_DOWN: {
            final int pointerIndex = MotionEventCompat.getActionIndex(ev);
            final float x = MotionEventCompat.getX(ev, pointerIndex);
            final float y = MotionEventCompat.getY(ev, pointerIndex);

            // Remember where we started (for dragging)
            mLastTouchX = x;
            mLastTouchY = y;
            // Save the ID of this pointer (for dragging)
            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            // Find the index of the active pointer and fetch its position
            final int pointerIndex =
                    MotionEventCompat.findPointerIndex(ev, mActivePointerId);

            final float x = MotionEventCompat.getX(ev, pointerIndex);
            final float y = MotionEventCompat.getY(ev, pointerIndex);

            // Calculate the distance moved
            final float dx = x - mLastTouchX;
            final float dy = y - mLastTouchY;

            mPosX += dx;
            mPosY += dy;

            invalidate();

            // Remember this touch position for the next move event
            mLastTouchX = x;
            mLastTouchY = y;

            break;
        }

        case MotionEvent.ACTION_UP: {
            mActivePointerId = INVALID_POINTER_ID;
            break;
        }

        case MotionEvent.ACTION_CANCEL: {
            mActivePointerId = INVALID_POINTER_ID;
            break;
        }

        case MotionEvent.ACTION_POINTER_UP: {

            final int pointerIndex = MotionEventCompat.getActionIndex(ev);
            final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);

            if (pointerId == mActivePointerId) {
                // This was our active pointer going up. Choose a new
                // active pointer and adjust accordingly.
                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                mLastTouchX = MotionEventCompat.getX(ev, newPointerIndex);
                mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex);
                mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
            }
            break;
        }
        }
        return true;
    }
    

基本尺寸示例

下面可以看到一个片段,说明了调整尺寸所涉及的基本成分

java 复制代码
    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.f;

    public MyCustomView(Context mContext){
        ...
        // View code goes here
        ...
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);
        return true;
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        canvas.scale(mScaleFactor, mScaleFactor);
        ...
        // onDraw() code goes here
        ...
        canvas.restore();
    }

    private class ScaleListener
            extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

            invalidate();
            return true;
        }
    }
    

还有更多的复杂手势,建议深度学习源码,本文只是一个记录。

六、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

未经允许不得转载

相关推荐
明天再做行么1 小时前
PHP8解析php技术10个新特性
android·php
Ting丶丶2 小时前
安卓应用安装过程学习
android·学习·安全·web安全·网络安全
kingdawin2 小时前
Android系统开发-判断相机是否在使用
android
恋猫de小郭4 小时前
IntelliJ IDEA 2024.3 K2 模式已发布稳定版,Android Studio Meerkat 预览也正式支持
android·android studio
找藉口是失败者的习惯8 小时前
Jetpack Compose 如何布局解析
android·xml·ui
Estar.Lee12 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh13 小时前
uiautomator案例
android
工业甲酰苯胺14 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做34314 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee15 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip