缩放手势 ScaleGestureDetector 源码解析,这一篇就够了

其实在我们日常的编程中,对于缩放手势的使用并不是很经常,这一手势主要是用在图片浏览方面,比如下方例子。但是(敲重点),作为 Android 入门的基础来说,学习 ScaleGestureDetector 的使用,算是不得不过的一道坎,好在 ScaleGestureDetector 使用起来非常简单,就是源码分析上得花些功夫。

本文首先将简单的介绍下 ScaleGestureDetector 的使用,在重点给大家分析下源码(由于源码方面是我自己的理解,可能有偏差,希望各位大佬能在评论区指出,万分感谢~)

16b996688722ec94?w=1280&h=904&f=jpeg&s=235091


ScaleGestureDetector 使用

ScaleGestureDetector 包括一个监听器,以及它所有方法的空实现:

名称用途
ScaleGestureDetector缩放手势的监听器
SimpleOnScaleGestureListener该监听器的空实现,在其中重写方法

ScaleGestureDetector 方法

名称用途
onScaleBegin当 >= 2 个手指碰触屏幕时调用,若返回 false 则忽略改事件调用
onScale滑动(缩放)过程中调用,若成功处理,则用户返回 true,监听器继续记录下一个缩放等动作,若为 false 表明数据未处理,则监听器继续积累
onScaleEnd全部手指离开屏幕,结束监听

通常情况下,手势监听会结合自定义 View 来讲,这里我给出一个最简单的使用,具体的使用实例,以后再结合自定义 View 讲讲。

    private void iniScaleGestureListener(){mListener = new ScaleGestureDetector.SimpleOnScaleGestureListener(){@Overridepublic boolean onScaleBegin(ScaleGestureDetector detector) {return super.onScaleBegin(detector);}@Overridepublic boolean onScale(ScaleGestureDetector detector) {MyLog.d("X:" + detector.getFocusX());MyLog.d("Y:" + detector.getFocusY());MyLog.d("scale:" + detector.getScaleFactor());return super.onScale(detector);}@Overridepublic void onScaleEnd(ScaleGestureDetector detector) {super.onScaleEnd(detector);}};detector = new ScaleGestureDetector(getContext(), mListener);}@Overridepublic boolean onTouchEvent(MotionEvent event) {detector.onTouchEvent(event);return true;}

ScaleGestureDetector 的使用

ScaleGestureDetector 在具体项目的使用有点复杂,我打算过段时间结合自定义 View 写一篇用来总结,所以这篇我们就先了解下 ScaleGestureDetector 的基本使用。


ScaleGestureDetector 源码分析

好了,现在我们进入本章重点,ScaleGestureDetector 源码分析,敲黑板敲黑板。首先,我们打开 ScaleGestureDetector 的源码可以看到,几乎所有的代码都集中在了 onTouchEvent 这个方法上,所以在这里,我就主要给大家介绍这个方法的实现。

第一部分:前期准备

        if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}mCurrTime = event.getEventTime();final int action = event.getActionMasked();// Forward the event to check for double tap gestureif (mQuickScaleEnabled) {mGestureDetector.onTouchEvent(event);}final int count = event.getPointerCount();final boolean isStylusButtonDown =(event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;

mInputEventConsistencyVerifier

  • 输入事件一致性验证器 @有道
  • 根据名字以及前面的定义
  • 我们可以猜测这个对象应该是手势监听 Event 是否注册(连接到硬件)
  • 所以,如果他为空,那么我们在这里调用 onTouchEvent 进行注册
        if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}

mCurrTime

  • 获得事件发生时的时间
        mCurrTime = event.getEventTime();

action

  • 获得事件类型
        final int action = event.getActionMasked();

mQuickScaleEnabled

  • Forward the event to check for double tap gesture
  • @有道 转发事件以检查双击手势
  • 首先是 mQuickScaleEnabled 这个对象
  • 翻译过来是: @有道 启用快速扩展
  • 作用大概就是调用双击监听事件,比如双击最大化
        if (mQuickScaleEnabled) {mGestureDetector.onTouchEvent(event);}

count

  • 获得屏幕上手指的数目
        final int count = event.getPointerCount();

isStylusButtonDown

这个主要是由于判断手写笔是否按下
由于我们很少处理手写笔,所以这里不做过多说明

        final boolean isStylusButtonDown =(event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;

## 第二部分:处理与手势变化

用户的缩放手势不总是一定的,就是说对于用户而言,随时可能有手指碰触或离开屏幕,这就使得缩放中心的(焦点)随时可能发生变化,这部分主要是用来处理这一变化,并做出响应。

        final boolean anchoredScaleCancelled =mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;final boolean streamComplete = action == MotionEvent.ACTION_UP ||action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;// 如果发生了上面这种小动作,或者说有一手指离开了屏幕,进行调用if (action == MotionEvent.ACTION_DOWN || streamComplete) {// Reset any scale in progress with the listener.// If it's an ACTION_DOWN we're beginning a new event stream.// This means the app probably didn't give us all the events. Shame on it.if (mInProgress) {mListener.onScaleEnd(this);mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;} else if (inAnchoredScaleMode() && streamComplete) {mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;}if (streamComplete) {return true;}}

### anchoredScaleCancelled

  • @Google 锚定规模取消
  • 我的理解是:用于判断滑动事件是否被取消
        final boolean anchoredScaleCancelled =mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;

streamComplete

  • @Google Translate: 流完成
  • 我的理解是,这个布尔变量用于标记
  • 当前动作是否完成
  • 我这里说的动作有两种
  • 这里指的是:在大动作如三指触屏放大过程中,又一个手指离开了屏幕这种
  • 在大动作三指触屏中发生的一个小动作,离开一指
        final boolean streamComplete = action == MotionEvent.ACTION_UP ||action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;

action == MotionEvent.ACTION_DOWN || streamComplete

  • 如果发生了上面这种小动作,或者说有一手指离开了屏幕,就进行调用
if (action == MotionEvent.ACTION_DOWN || streamComplete) {...}

if (mInProgress)

  • @google Translate:重置侦听器正在进行的任何缩放。
  • 如果是ACTION_DOWN,我们开始一个新的事件流。
  • 这意味着应用程序可能没有给我们所有的事件。很遗憾。
  • 首先判断该进程(从第一个手指碰上屏幕,到最后一个手指离开屏幕为止)是否结束
  • 如果仍在运行中,这调用回调方法:onScaleEnd 使其结束
            if (mInProgress) {mListener.onScaleEnd(this);mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;}

else if (inAnchoredScaleMode() && streamComplete)

  • 如果当前进程已经结束
  • 判断 mAnchoredScaleMode 是否为 ANCHORED_SCALE_MODE_STYLUS 状态
  • 同时判断操作流 streamComplete 是否完成
  • 都符合的情况下结束这一手势变化
            else if (inAnchoredScaleMode() && streamComplete) {mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;}

if (streamComplete)

  • 结束本次 onTouchEvent 方法的调用,等待下一次调用发生
            if (streamComplete) {return true;}

总结: 可以看到,当触发 down 或者触发 up,cancel 时,如果之前处于缩放计算的状态,会将其状态重置, 并调用 onScaleEnd 方法。


进入锚定比例模式

  • 当判断用户动作,如果为双击这类点击事件,进入该模式
  • 与正常缩放区分。这个模式功能一般是:双击最大化和最小化
        if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()&& !streamComplete && isStylusButtonDown) {// Start of a button scale gesturemAnchoredScaleStartX = event.getX();mAnchoredScaleStartY = event.getY();mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;mInitialSpan = 0;}

mAnchoredScaleStartX & mAnchoredScaleStartY

  • 后文中将用于重新计算焦点
            mAnchoredScaleStartX = event.getX();mAnchoredScaleStartY = event.getY();

mAnchoredScaleMode

  • 赋值之后,再次调用 inAnchoredScaleMode() 方法,返回值变为 true
            mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;

计算缩放中心

        final boolean configChanged = action == MotionEvent.ACTION_DOWN ||action == MotionEvent.ACTION_POINTER_UP ||action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;final int skipIndex = pointerUp ? event.getActionIndex() : -1;// Determine focal pointfloat sumX = 0, sumY = 0;final int div = pointerUp ? count - 1 : count;final float focusX;final float focusY;if (inAnchoredScaleMode()) {// In anchored scale mode, the focal pt is always where the double tap// or button down gesture startedfocusX = mAnchoredScaleStartX;focusY = mAnchoredScaleStartY;if (event.getY() < focusY) {mEventBeforeOrAboveStartingGestureEvent = true;} else {mEventBeforeOrAboveStartingGestureEvent = false;}} else {for (int i = 0; i < count; i++) {if (skipIndex == i) continue;sumX += event.getX(i);sumY += event.getY(i);}focusX = sumX / div;focusY = sumY / div;}

configChanged

  • 布尔类型量,标志着一个操作的完成或者结束(手指离开,手指按下)
        final boolean configChanged = action == MotionEvent.ACTION_DOWN ||action == MotionEvent.ACTION_POINTER_UP ||action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;

pointerUp

  • 布尔类型量,用于判断当前动作,是否为手指离开(抬起动作)
        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;

skipIndex

  • 标记量,在是手指离开的情况下,标记离开手指
  • 在后面计算新的焦点代码中,跳过该手指的标记点坐标,进行计算
        final int skipIndex = pointerUp ? event.getActionIndex() : -1;

初始化计算所需临时变量

        // Determine focal pointfloat sumX = 0, sumY = 0;// 如果是抬起手指,则当前手指数减1,否则不变final int div = pointerUp ? count - 1 : count;final float focusX;final float focusY;

判断是否为锚定比例模式

  • 是的话直接将点击时记下的点,作为焦点
  • 不是的话,把所有点累加求和,除以总个数,计算平均值
        if (inAnchoredScaleMode()) {// In anchored scale mode, the focal pt is always where the double tap// or button down gesture started// 在锚定比例模式中,焦点pt始终是双击的位置,或按下手势开始focusX = mAnchoredScaleStartX;focusY = mAnchoredScaleStartY;if (event.getY() < focusY) {mEventBeforeOrAboveStartingGestureEvent = true;} else {mEventBeforeOrAboveStartingGestureEvent = false;}} else {for (int i = 0; i < count; i++) {if (skipIndex == i) continue;sumX += event.getX(i);sumY += event.getY(i);}focusX = sumX / div;focusY = sumY / div;}

算缩放比例

  • 计算缩放比例也很简单,就是计算各个手指到焦点的平均距离,在用户手指移动后用新的平均距离除以旧的平均距离,并以此计算得出缩放比例。
        // Determine average deviation from focal point @Google translate float devSumX = 0, devSumY = 0;for (int i = 0; i < count; i++) {if (skipIndex == i) continue;// Convert the resulting diameter into a radius.devSumX += Math.abs(event.getX(i) - focusX);devSumY += Math.abs(event.getY(i) - focusY);}final float devX = devSumX / div;final float devY = devSumY / div;// Span is the average distance between touch points through the focal point;// i.e. the diameter of the circle with a radius of the average deviation from// the focal point.final float spanX = devX * 2;final float spanY = devY * 2;final float span;if (inAnchoredScaleMode()) {span = spanY;} else {span = (float) Math.hypot(spanX, spanY);}

计算平均偏差

  • 确定焦点的平均偏差
        float devSumX = 0, devSumY = 0;for (int i = 0; i < count; i++) {if (skipIndex == i) continue;// Convert the resulting diameter into a radius.devSumX += Math.abs(event.getX(i) - focusX);devSumY += Math.abs(event.getY(i) - focusY);}final float devX = devSumX / div;final float devY = devSumY / div;

计算缩放比例

  • 跨度是通过焦点的触摸点之间的平均距离;
  • 即圆的直径,其半径为平均偏差
  • 这里的 Math.hypot(spanX, spanY) 方法,相当于 sqrt(xx + yy)
        final float spanX = devX * 2;final float spanY = devY * 2;final float span;if (inAnchoredScaleMode()) {span = spanY;} else {span = (float) Math.hypot(spanX, spanY);}

结束缩放事件

  • @Google Translate:根据需要调度开始/结束事件。
  • 如果配置发生更改,请通过开始通知应用重置其当前状态
  • 一个新的比例事件流。
  • 这里就不做太多描述,主要就是:
  • 判断是不是所有手指都离开了屏幕
  • 如果是,那么索命这个缩放进程结束了
  • 则保存当前缩放的数据
  • 调用 onScaleEnd 方法,结束当前操作
        final boolean wasInProgress = mInProgress;mFocusX = focusX;mFocusY = focusY;if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {mListener.onScaleEnd(this);mInProgress = false;mInitialSpan = span;}if (configChanged) {mPrevSpanX = mCurrSpanX = spanX;mPrevSpanY = mCurrSpanY = spanY;mInitialSpan = mPrevSpan = mCurrSpan = span;}

触发 onScaleBegin 开始缩放

  • 当手指移动的距离超过一定数值(数值大小由系统定义)后,会触发 onScaleBegin 方法
  • 如果用户在 onScaleBegin 方法里面返回了 true,则接受事件后,就会重置缩放相关数值,并且开始积累缩放因子。
        final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;if (!mInProgress && span >= minSpan &&(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {mPrevSpanX = mCurrSpanX = spanX;mPrevSpanY = mCurrSpanY = spanY;mPrevSpan = mCurrSpan = span;mPrevTime = mCurrTime;mInProgress = mListener.onScaleBegin(this);}

通知用户进行缩放处理

  • @ Google Translate: 处理动作;焦点和跨度/比例因子正在发生变化。
  • 这块代码的功能主要就是通知用户(编程者)
  • 根据这些数据进行缩放
        if (action == MotionEvent.ACTION_MOVE) {mCurrSpanX = spanX;mCurrSpanY = spanY;mCurrSpan = span;boolean updatePrev = true;if (mInProgress) {updatePrev = mListener.onScale(this);}if (updatePrev) {mPrevSpanX = mCurrSpanX;mPrevSpanY = mCurrSpanY;mPrevSpan = mCurrSpan;mPrevTime = mCurrTime;}}

updatePrev

  • 这个用于接收用户的返回值
  • 只要我们放回 true ,系统就会保存当前数据
  • 重新获取并计算新的数据和比例
  • 系统默认返回 false 然后进行下一次事件的计算
            if (mInProgress) {updatePrev = mListener.onScale(this);}if (updatePrev) {mPrevSpanX = mCurrSpanX;mPrevSpanY = mCurrSpanY;mPrevSpan = mCurrSpan;mPrevTime = mCurrTime;}

结语

我要讲的所有内容,到这里就完全结束了

由于源码是按照我自己的理解来讲的,所以难免会有一些出入

希望大家能在评论区中帮我指出,谢谢~ ?

转载于:https://www.cnblogs.com/yuanhao-1999/p/11102806.html

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

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

相关文章

postgres的数据库备份和恢复

备份和恢复 一条命令就可以解决很简单: 这是备份的命令&#xff1a; pg_dump -h 127/0.0.1 -U postgres databasename > databasename.bak 指令解释&#xff1a; pg_dump 是备份数据库指令&#xff0c;164.82.233.54是数据库的ip地址&#xff08;必须保证数据库允许外部访…

java如何实现封装_java如何实现封装

Java中类的封装是如何实现的封装是将对象的信息隐藏在对象内部&#xff0c;禁止外部程序直接访问对象内部的属性和方法。 java封装类通过三个步骤实现&#xff1a; (1)修改属性的可见性&#xff0c;限制访问。 (2)设置属性的读取方法。 (3)在读取属性的方法中&#xff0c;添加对…

php token 验证,PHP如何实现Token验证

PHP如何实现Token验证首先将Token进行解析&#xff1b;然后根据解析出来的信息部分验证是否过期&#xff0c;如果未过期再将解析出的信息部分进行加密&#xff1b;最后将加密出来的数据和解析出来签名进行比对&#xff0c;如果相同则验证成功。示例代码&#xff1a;<?php f…

值得一用的Windows软件

该清单仅本人使用后所作推荐&#xff0c;可能会比较主观&#xff0c;所以仅供参考哈。可能某些软件链接会失效&#xff0c;可以自行百度搜索下载即可。 杀软 火绒安全&#xff1a;国内杀毒软件的一股清流&#xff0c;界面简洁&#xff0c;无推广。现在已经开启了 5.0 公测&…

Python字符串处理全攻略(四):常用内置方法轻松掌握

文章目录 引言Python字符串常用内置方法切片功能介绍语法示例注意事项 str.isalpha()功能介绍语法示例注意事项 str.isdigit()功能介绍语法示例注意事项总结 str.isalnum()功能介绍语法示例注意事项总结 str.isupper()功能介绍语法示例注意事项 islower()功能介绍语法示例注意事…

php空间限制磁盘限额,ORA-01536:超出表空间XXXX的空间限额

问题描述&#xff1a;在FMIS2600用户下进行某个DDL或DML操作时&#xff0c;提示&#xff1a;ORA-01536&#xff1a;超出表空间FMIS2600 的空间限额 或者 ORA-01950: 对表空间/*******************ORA-01536&#xff1a;超出表空间XXXX的空间限额*******************//*********…

01爬虫基本原理及Requests库下载

一、爬虫基本原理 1.什么是爬虫 ​ 爬虫就是爬取数据 2.什么是互联网&#xff1f; ​ 就是由一堆网络设备&#xff0c;把一台台的电脑互联在一起 3.互联网建立的目的 ​ 数据的传递和数据共享 4.什么是数据&#xff1f; ​ 例如&#xff1a; ​ 电商平台的商业信息&#xff08;…

php 怎么实现收藏功能,php收藏功能如何实现

php收藏功能如何实现php收藏功能的实现方法&#xff1a;首先创建好数据库表 &#xff1b;然后创建前台代码&#xff0c;实现登录界面&#xff1b;接着通过html实现收藏样式&#xff1b;最后使用php进行后台处理即可。推荐&#xff1a;《PHP视频教程》这是数据库表话不多说上代码…

quartus FIR仿真笔记

第一章&#xff1a; 最近百度了一些fir滤波器的资料&#xff0c;都没有自己想要的。容我吐槽一大段文字> 在旧版的quartus中&#xff0c;比如13.0&#xff0c;有两个fir滤波器的选项&#xff0c;如下所示&#xff1a; 网上很多都是讲不带II的那个&#xff0c;而在新版的quar…

git常用命令及分支简介

2019独角兽企业重金招聘Python工程师标准>>> 1、git基本命令 1&#xff09;git add 将想要快照的内容写入缓存区 2&#xff09;git status -s "AM" 状态的意思是&#xff0c;这个文件在我们将它添加到缓存之后又有改动 3&#xff09;git commit -m 第一次…

企业私有云部署im,视频服务

1&#xff0c;安全问题 2&#xff0c;员工跨地域 3&#xff0c;内部视频培训 考勤申请&#xff0c;设备借用申请 名片申请 会议室预订 审批 内网&#xff0c;局域网部署 Android源码 https://github.com/starrtc/android-demo ios源码https://github.com/starrtc/ios-demo

Leetcode怎么调试java代码,在Clion上调试LeetCode代码

在Clion上调试LeetCode代码在leetcode上做题调试起来总有些不方便&#xff0c;所以查阅了一些资料后&#xff0c;按以下配置&#xff0c;自我感觉效率还行&#xff0c;分享给大家。祝大家刷题愉快。并附上自己整理的leetcode400题题表。Leetcode400题&#xff1a;notion地址依赖…

来入门一下kotlin吧

Kotlin是什么&#xff1f; Kotlin是一种在java虚拟机上运行的静态类型的编程语言&#xff0c;被称之为 Android 世界的Swift&#xff0c;由 JetBrains 设计开发并开源。 Kotlin的优势&#xff01; Kotlin可以编译成java字节码&#xff0c;也可以编译成JavaScript。方便在没有ja…

ReactNative 触摸事件处理

ReactNative触摸事件处理 对RN触摸事件的捕获与冒泡机制的理解 组件A、B、C结构 组件A组件B组件C 捕获、冒泡机制 sequenceDiagram A->>A: 是否捕获&#xff1f;若是则停止向下一级传递 A->>B: B->>B: 是否捕获&#xff1f;若是则停止向下一级传递 B->&g…

程序员如何面试才能拿到offer

一、概述 面试&#xff0c;难还是不难&#xff1f;取决于面试者的底蕴&#xff08;气场技能&#xff09;、心态和认知及沟通技巧。面试其实可以理解为一场聊天和谈判&#xff0c;在这过程中有心理、思想上的碰撞和博弈。其实你只需要搞清楚一个逻辑&#xff1a;“面试官为什么会…

Generative Adversarial Learning Towards Fast Weakly Supervised Detection(CVPR2018)阅读笔记

弱监督目标检测相对于一般的目标检测任务来说&#xff0c;训练样本不需要实例级别的标注&#xff0c;只需要图片级别的标注&#xff0c;即告诉图片中有什么而不需标注位置信息&#xff0c;这种标注图片容易获取&#xff0c;能节省标注时间及精力。现有的大部分方法在进行若监督…

如何添加JWT生成的token在请求头中

前言 在我们使用JWT来做用户的验证时&#xff0c;我们登陆生成对应的token,并加入到请求的参数中发送到后台提供相关的权限校验。这个时候我们需要使用到传递请求头参数传递的问题&#xff0c;下面是两种方式。 1.ajax提交方式 1&#xff09;.方法一&#xff1a; $.ajax({ type…

Gradle 使用技巧(二) - SO/NDK过滤

引言 作为一个Android开发人员&#xff0c;so对于我们来讲是极其常见的&#xff0c;各种大厂的SDK中都包含着各种各样的so&#xff0c;而so也是apk瘦身的重要一环&#xff0c;减少so平台的数量&#xff0c;可以极大限度的减少apk的大小。 Android 中的so 先看一张官方的图&…

VMware——安装CentOS

VMware——安装CentOS 摘要&#xff1a;本文主要记录了在VMware虚拟机里安装CentOS的步骤。 下载操作系统 可以从下面的镜像地址去下载各种版本的CentOS&#xff0c;此次安装使用的版本是7.2&#xff1a; http://archive.kernel.org/centos-vault/ http://mirror.nsc.liu.se/ce…

牛客小白月赛6 水题 求n!在m进制下末尾0的个数 数论

链接&#xff1a;https://www.nowcoder.com/acm/contest/135/C来源&#xff1a;牛客网 题目描述 其中&#xff0c;f(1)1;f(2)1;Z皇后的方案数&#xff1a;即在ZZ的棋盘上放置Z个皇后&#xff0c;使其互不攻击的方案数。 输入描述: 输入数据共一行&#xff0c;两个正整数x,m&am…