android手势监听

news2024/9/27 19:22:37

关于作者: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)方法完成了不同手势的识别。

三、使用

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

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使用滚动对象来实现。

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

     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方法。用于跟踪拖放手势。如果用户在拖放手势后达到内容限制,应用程序会显示“发光”效果

    // 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 值。通常,这是在子视图使用滚动对象对滚动进行动画处理时完成的

我们来看一段缩放代码

    // 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。它会滚动以响应拖放手势,并使用滚动来实现“适合页面”动画。

    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;
        }
    }
    

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

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

    // 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;
    }
    

基本尺寸示例

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

    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学习专栏

未经允许不得转载

ddd

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1949291.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库窗口函数实战

目录 前言 窗口函数语法 创建测试表和数据 使用示例 PARTITION BY 窗口函数 ROW_NUMBER RANK DENSE_RANK RANGE ROWS 前言 SQL 具有很高的灵活性,可以根据需求进行复杂的数据查询和分析,支持多表联合查询(join)、排序…

[Unity] ShaderGraph实现镜头加速线/残血效果 URP

效果如下所示:残血状态时,画面会压暗角,并出现速度线营造紧迫感。 使用到的素材如下,换别的当然也可以。[这是张白色的png放射图,并非皇帝的新图hhh] 这个效果的实现逻辑,其实就是利用time向圆心做透明度的…

学习笔记-系统框图简化求传递函数公式例题

简化系统结构图求系统传递函数例题 基础知识回顾 第四讲 控制系统的方框图 (zhihu.com) 「自控原理」2.3 方框图的绘制及化简_方框图化简-CSDN博客 自动控制原理笔记-结构图及其等效变换_结构图等效变换-CSDN博客 例子一 「自控原理」2.3 方框图的绘制及化简_方框图化简-CS…

【ARM】MDK-ARM软件开发工具的最终用户许可协议获取

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 了解MDK-ARM系列产品内软件开发工具的最终用户许可协议的获取。 2、 问题场景 对于部分外企客户需要软件开发工具的最终用户许可协议作为产品资料,以便附录并说明。 3、软硬件环境 1)、软件…

Axure怎么样?全面功能评测与用户体验分析!

软件 Axure 曾经成为产品经理必备的原型设计工具,被认为是专门为产品经理设计的工具。但事实上,软件 Axure 的使用场景并不局限于产品经理构建产品原型。UI/UX 设计师还可以使用 Axure 软件构件应用程序 APP 原型,网站设计师也可以使用 Axure…

快速上手,spring boot3整合task实现定时任务

在已经上线的项目中,定时任务是必不可少的。基于spring boot自动装配的原理,我们要集成task定时任务还是非常简单的。只需要简单的两步就可以实现。 1、创建一个spring boot项目,并在项目的启动类(也不一定非要是启动类&#xff…

二手车小程序

本文来自:FastAdmin二手车小程序 - 源码1688 一款基于ThinkPHPFastAdmin开发的原生微信小程序二手车管理系统。 前端小程序码: 后台演示地址: https://facars.site100.cn/OHNYSKzuba.php/carswxsys/sysinit?refaddtabs

wpf中轮询显示图片

本文的需求是,在一个文件夹中,放一堆图片的集合,然后在wpf程序中,按照定时的方式,循序显示照片。 全部代码 1.声明一个PictureInfo类 namespace WpfApp1 {public class PictureInfo{public string? FileName { get; …

GPT-4o mini小型模型具备卓越的文本智能和多模态推理能力

GPT-4o mini 是首个应用OpenAI 指令层次结构方法的模型,这有助于增强模型抵抗越狱、提示注入和系统提示提取的能力。这使得模型的响应更加可靠,并有助于在大规模应用中更安全地使用。 GPT-4o mini 在学术基准测试中,无论是在文本智能还是多模…

mac怎样清理photoshop垃圾的方法 ps清理缓存和垃圾 苹果电脑暂存盘已满怎么清理

很多使用过ps,尤其是Adobe全家桶的小伙伴会发现,这些软件占用缓存很多,而且随着使用时间的增长,缓存也会越多,并不会自动清理。那么mac系统怎么清理ps暂存盘呢?mac又该怎么最高效清理磁盘空间呢&#xff1f…

【专题】2024年云计算白皮书报告合集PDF分享(附原数据表)

原文链接:https://tecdat.cn/?p37112 2023年全球云计算市场显著增长,预计将持续繁荣至2027年突破万亿美元,中国市场同样保持强劲势头,预计也将大幅跃升。国内云计算经过十余年发展,虽取得显著进展,但在资…

【系统架构设计师】十八、信息系统架构设计理论与实践②

目录 四、企业信息系统的总体框架 4.1 战略系统 4.2 业务系统 4.3 应用系统 4.4 企业信息基础设施 4.5 业务流程重组BPR 4.6 业务流程管理BPM 五、信息系统架构设计方法 5.1 行业标准的体系架构框架 5.2 架构开发方法 5.3 信息化总体架构方法 5.4 信息化建设生命周…

基于联咏 NT98692芯片赋能边缘计算IP摄像机与XVR监控系统解决方案

联咏 NT98692 是一款新世代整合度极高的 SoC,具有高影像品质、低位元率、低功耗,针对 8Kp30 边缘运算 IP 摄影机与后端监控系统 XVR 应用。此 SoC 整合了 ARM Quad Cortex A73 CPU 核心、新一代 ISP 和 AI ISP、H.265/H.264 视讯压缩编解码器、DSP、高效…

k8s核心知识总结

写在前面 时间一下子到了7月份尾;整个7月份都乱糟糟的,不管怎么样,日子还是得过啊, 1、7月份核心了解个关于k8s,iceberg等相关技术,了解了相关的基础逻辑,虽然和数开主线有点偏,但是…

流量回放新形态:基于网关 Access Log 发起

作者:休祯 背景 为什么需要流量回放 无论是面向即将上线的新版本做最后的性能测试,还是在遇到棘手的故障时帮助开发人员快速定位问题原因,流量回放技术都发挥着不可或缺的作用。使用真实世界的流量数据进行回放能使性能测试过程更加接近实…

Ubuntu22.04手动安装fabric release-2.5版本

这个过程稍微有点复杂,但完整操作完成以后会对Fabric网络有更加深入的理解,方便后续自己手动搭建Fabric网络。这个过程需要手动逐个下载Fabric源代码、使用命令下载Fabric镜像和用Git下载例子程序。 Fabric源代码主要用途是用来编译cryptogen、configtx…

ElasticSearch(六)— 全文检索

一、match系列查询 前面讲到的query中的查询,都是精准查询。可以理解成跟在关系型数据库中的查询类似。match系列的查询,是全文检索的查询。会通过分词进行评分,匹配,再返回搜索结果。 1.1 match 查询 "query": {&qu…

按图搜索新体验:阿里巴巴拍立淘API返回值详解

阿里巴巴拍立淘API是一项基于图片搜索的商品搜索服务,它允许用户通过上传商品图片,系统自动识别图片中的商品信息,并返回与之相关的搜索结果。以下是对阿里巴巴拍立淘API返回值的详细解析: 一、主要返回值内容 商品信息 商品列表…

20240725项目的maven环境报红-重新配置maven

1.在编辑器里面打开项目,导入源码 (1)找到项目的地址C:\Users\zzz\IdeaProjects\datasys,然后右击用idea编辑器打开。 (2)idea中上菜单栏打开open,然后输入file,选择源代码文件 2.…

primetime如何合并不同modes的libs到一个lib文件

首先,用primetime 抽 timing model 的指令如下。 代码如下(示例): #抽lib时留一些margin, setup -max/hold -min set_extract_model_margin -port [get_ports -filter "!defined(clocks)"] -max 0.1 #抽lib extract_mod…