├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── gradle.properties ├── project.properties └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── almeros │ └── android │ └── multitouch │ ├── BaseGestureDetector.java │ ├── MoveGestureDetector.java │ ├── RotateGestureDetector.java │ ├── ShoveGestureDetector.java │ └── TwoFingerGestureDetector.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | bin 3 | gen 4 | .classpath 5 | .project 6 | .gradle 7 | .idea 8 | *.iml 9 | gradle/ 10 | build/ 11 | .DS_Store 12 | local.properties -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Android Gesture Detectors Framework Licence 2 | =================================== 3 | 4 | Copyright (c) 2012, Almer Thie 5 | 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Gesture Detectors Framework 2 | =================================== 3 | 4 | Introduction 5 | ------------ 6 | 7 | Since I was amazed Android has a ScaleGestureDetector since API level 8 but 8 | (still) no such thing as a RotateGestureDetector I decided to create this class 9 | myself. In the process I decided to create a small extendable framework for 10 | GestureDetectors in general. 11 | 12 | 13 | Tutorials 14 | --------- 15 | 16 | #### Blog on using gesture detectors 17 | 18 | If you want to use the ScaleGestureDetector and/or the gesture detectors 19 | from this project, please read my tutorial: [code.almeros.com/android-multitouch-gesture-detectors](http://code.almeros.com/android-multitouch-gesture-detectors) 20 | 21 | #### Example application 22 | 23 | If you like to just dive into a working example, please check out 24 | [the example app (GitHub)](https://github.com/Almeros/android-gesture-detectors-example). 25 | 26 | 27 | Dependencies / Downloads 28 | ------------------------ 29 | You can always just download a zip from GitHub and add its contents to your project. But dependency 30 | management might be easier for you using Gradle and jitpack.io. Include the following in your project: 31 | 32 | #### build.gradle root 33 | 34 | buildscript { 35 | repositories { 36 | ... 37 | maven { url 'https://jitpack.io' } 38 | } 39 | ... 40 | } 41 | 42 | allprojects { 43 | repositories { 44 | ... 45 | maven { url 'https://jitpack.io' } 46 | } 47 | } 48 | 49 | #### build.gradle app 50 | 51 | dependencies { 52 | // Note: Change 'v1.0' to whatever release you need or 'master-SNAPSHOT' for a snapshot of 53 | // the latest version on the master-branch. 54 | compile 'com.github.Almeros:android-gesture-detectors:v1.0' 55 | } 56 | 57 | 58 | Extending / Contributing 59 | ------------------------ 60 | 61 | If you want to extend this framework with new gesture detectors please check out the existing classes 62 | and read the comments/javadocs. 63 | 64 | I hope you'll send a pull request with your FingerTwistGestureDetector or something ;) 65 | 66 | If you help maintaining this framework, first of all thank you, and second, please consider also updating 67 | [the example app (GitHub)](https://github.com/Almeros/android-gesture-detectors-example) to make use 68 | of your awesome new functionality/bugfixes! 69 | 70 | 71 | Containments 72 | ------------ 73 | 74 | ### RotateGestureDetector 75 | 76 | Helps finding out the rotation angle between the line of two fingers (with the 77 | normal or previous finger positions) 78 | 79 | ### MoveGestureDetector 80 | 81 | Convenience gesture detector to keep it easy moving an object around with one 82 | ore more fingers without losing the clean usage of the gesture detector pattern. 83 | 84 | ### ShoveGestureDetector 85 | 86 | Detects a vertical two-finger shove. (If you place two fingers on screen with less than a 20 degree angle between them, 87 | this will detact movement on the Y-axis.) 88 | 89 | ### ScaleGestureDetector (default Android) 90 | 91 | This one is NOT in this framework, but is gesture detector that resides in the 92 | Android API since API level 8 (2.2). 93 | 94 | ### BaseGestureDetector (abstract) 95 | 96 | Abstract class that every new gesture detector class should extend. 97 | 98 | ### TwoFingerGestureDetector (abstract) 99 | 100 | Abstract class that extends the BaseGestureDetector and that every new gesture 101 | detector class for two finger operations should extend. 102 | 103 | License 104 | ------- 105 | This project is licensed with the 2-clause BSD license. 106 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.3.3' 8 | 9 | // NOTE: Do not place your application dependencies here; they belong 10 | // in the individual module build.gradle files 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | mavenCentral() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=1.0 2 | VERSION_CODE=2 3 | GROUP=com.almeros.android-gesture-detectors 4 | 5 | POM_DESCRIPTION=Gesture detector framework for multitouch handling on Android, based on Android's ScaleGestureDetector 6 | POM_URL=https://github.com/almeros/android-gesture-detectors 7 | POM_SCM_URL=https://github.com/almeros/android-gesture-detectors 8 | POM_SCM_CONNECTION=scm:git@github.com:almeros/android-gesture-detectors.git 9 | POM_SCM_DEV_CONNECTION=scm:git@github.com:almeros/android-gesture-detectors.git 10 | POM_LICENCE_NAME=BSD License 11 | POM_LICENCE_URL=http://opensource.org/licenses/BSD-2-Clause 12 | POM_LICENCE_DIST=repo 13 | POM_DEVELOPER_ID=almeros 14 | POM_DEVELOPER_NAME=Almeros Thies 15 | 16 | ANDROID_MIN_SDK=8 17 | ANDROID_TARGET_SDK=21 18 | ANDROID_COMPILE_SDK=21 19 | ANDROID_BUILD_TOOLS_VERSION=25.0.0 20 | 21 | org.gradle.jvmargs=-Xmx1536M 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Almeros/android-gesture-detectors/e2e854b281f5556adf7ffb90a39a189eff051451/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Nov 15 14:08:27 PST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | 92 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | // TODO Change this back to the main script once the PR is merged. 4 | apply from: 'https://raw.githubusercontent.com/jpardogo/gradle-mvn-push/master/gradle-mvn-push.gradle' 5 | 6 | android { 7 | compileSdkVersion Integer.parseInt(ANDROID_COMPILE_SDK) 8 | buildToolsVersion ANDROID_BUILD_TOOLS_VERSION 9 | 10 | defaultConfig { 11 | minSdkVersion Integer.parseInt(ANDROID_MIN_SDK) 12 | targetSdkVersion Integer.parseInt(ANDROID_TARGET_SDK) 13 | versionCode Integer.parseInt(VERSION_CODE) 14 | versionName VERSION_NAME 15 | } 16 | 17 | lintOptions { 18 | abortOnError false 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Android Gesture Detectors Library 2 | POM_ARTIFACT_ID=library 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-18 12 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /library/src/main/java/com/almeros/android/multitouch/BaseGestureDetector.java: -------------------------------------------------------------------------------- 1 | package com.almeros.android.multitouch; 2 | 3 | import android.content.Context; 4 | import android.view.MotionEvent; 5 | 6 | /** 7 | * @author Almer Thie (code.almeros.com) 8 | * Copyright (c) 2013, Almer Thie (code.almeros.com) 9 | * 10 | * All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 13 | * 14 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 15 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 16 | * in the documentation and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 21 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 22 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 24 | * OF SUCH DAMAGE. 25 | */ 26 | public abstract class BaseGestureDetector { 27 | protected final Context mContext; 28 | protected boolean mGestureInProgress; 29 | 30 | protected MotionEvent mPrevEvent; 31 | protected MotionEvent mCurrEvent; 32 | 33 | protected float mCurrPressure; 34 | protected float mPrevPressure; 35 | protected long mTimeDelta; 36 | 37 | 38 | /** 39 | * This value is the threshold ratio between the previous combined pressure 40 | * and the current combined pressure. When pressure decreases rapidly 41 | * between events the position values can often be imprecise, as it usually 42 | * indicates that the user is in the process of lifting a pointer off of the 43 | * device. This value was tuned experimentally. 44 | */ 45 | protected static final float PRESSURE_THRESHOLD = 0.67f; 46 | 47 | 48 | public BaseGestureDetector(Context context) { 49 | mContext = context; 50 | } 51 | 52 | /** 53 | * All gesture detectors need to be called through this method to be able to 54 | * detect gestures. This method delegates work to handler methods 55 | * (handleStartProgressEvent, handleInProgressEvent) implemented in 56 | * extending classes. 57 | * 58 | * @param event 59 | * @return 60 | */ 61 | public boolean onTouchEvent(MotionEvent event){ 62 | final int actionCode = event.getAction() & MotionEvent.ACTION_MASK; 63 | if (!mGestureInProgress) { 64 | handleStartProgressEvent(actionCode, event); 65 | } else { 66 | handleInProgressEvent(actionCode, event); 67 | } 68 | return true; 69 | } 70 | 71 | /** 72 | * Called when the current event occurred when NO gesture is in progress 73 | * yet. The handling in this implementation may set the gesture in progress 74 | * (via mGestureInProgress) or out of progress 75 | * @param actionCode 76 | * @param event 77 | */ 78 | protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event); 79 | 80 | /** 81 | * Called when the current event occurred when a gesture IS in progress. The 82 | * handling in this implementation may set the gesture out of progress (via 83 | * mGestureInProgress). 84 | * @param actionCode 85 | * @param event 86 | */ 87 | protected abstract void handleInProgressEvent(int actionCode, MotionEvent event); 88 | 89 | 90 | protected void updateStateByEvent(MotionEvent curr){ 91 | final MotionEvent prev = mPrevEvent; 92 | 93 | // Reset mCurrEvent 94 | if (mCurrEvent != null) { 95 | mCurrEvent.recycle(); 96 | mCurrEvent = null; 97 | } 98 | mCurrEvent = MotionEvent.obtain(curr); 99 | 100 | 101 | // Delta time 102 | mTimeDelta = curr.getEventTime() - prev.getEventTime(); 103 | 104 | // Pressure 105 | mCurrPressure = curr.getPressure(curr.getActionIndex()); 106 | mPrevPressure = prev.getPressure(prev.getActionIndex()); 107 | } 108 | 109 | protected void resetState() { 110 | if (mPrevEvent != null) { 111 | mPrevEvent.recycle(); 112 | mPrevEvent = null; 113 | } 114 | if (mCurrEvent != null) { 115 | mCurrEvent.recycle(); 116 | mCurrEvent = null; 117 | } 118 | mGestureInProgress = false; 119 | } 120 | 121 | 122 | /** 123 | * Returns {@code true} if a gesture is currently in progress. 124 | * @return {@code true} if a gesture is currently in progress, {@code false} otherwise. 125 | */ 126 | public boolean isInProgress() { 127 | return mGestureInProgress; 128 | } 129 | 130 | /** 131 | * Return the time difference in milliseconds between the previous accepted 132 | * GestureDetector event and the current GestureDetector event. 133 | * 134 | * @return Time difference since the last move event in milliseconds. 135 | */ 136 | public long getTimeDelta() { 137 | return mTimeDelta; 138 | } 139 | 140 | /** 141 | * Return the event time of the current GestureDetector event being 142 | * processed. 143 | * 144 | * @return Current GestureDetector event time in milliseconds. 145 | */ 146 | public long getEventTime() { 147 | return mCurrEvent.getEventTime(); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /library/src/main/java/com/almeros/android/multitouch/MoveGestureDetector.java: -------------------------------------------------------------------------------- 1 | package com.almeros.android.multitouch; 2 | 3 | import android.content.Context; 4 | import android.graphics.PointF; 5 | import android.view.MotionEvent; 6 | import com.almeros.android.multitouch.BaseGestureDetector; 7 | 8 | /** 9 | * @author Almer Thie (code.almeros.com) 10 | * Copyright (c) 2013, Almer Thie (code.almeros.com) 11 | * 12 | * All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 15 | * 16 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 18 | * in the documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 21 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 23 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 24 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 26 | * OF SUCH DAMAGE. 27 | */ 28 | public class MoveGestureDetector extends BaseGestureDetector { 29 | 30 | /** 31 | * Listener which must be implemented which is used by MoveGestureDetector 32 | * to perform callbacks to any implementing class which is registered to a 33 | * MoveGestureDetector via the constructor. 34 | * 35 | * @see MoveGestureDetector.SimpleOnMoveGestureListener 36 | */ 37 | public interface OnMoveGestureListener { 38 | public boolean onMove(MoveGestureDetector detector); 39 | public boolean onMoveBegin(MoveGestureDetector detector); 40 | public void onMoveEnd(MoveGestureDetector detector); 41 | } 42 | 43 | /** 44 | * Helper class which may be extended and where the methods may be 45 | * implemented. This way it is not necessary to implement all methods 46 | * of OnMoveGestureListener. 47 | */ 48 | public static class SimpleOnMoveGestureListener implements OnMoveGestureListener { 49 | public boolean onMove(MoveGestureDetector detector) { 50 | return false; 51 | } 52 | 53 | public boolean onMoveBegin(MoveGestureDetector detector) { 54 | return true; 55 | } 56 | 57 | public void onMoveEnd(MoveGestureDetector detector) { 58 | // Do nothing, overridden implementation may be used 59 | } 60 | } 61 | 62 | private static final PointF FOCUS_DELTA_ZERO = new PointF(); 63 | 64 | private final OnMoveGestureListener mListener; 65 | 66 | private PointF mCurrFocusInternal; 67 | private PointF mPrevFocusInternal; 68 | private PointF mFocusExternal = new PointF(); 69 | private PointF mFocusDeltaExternal = new PointF(); 70 | 71 | 72 | public MoveGestureDetector(Context context, OnMoveGestureListener listener) { 73 | super(context); 74 | mListener = listener; 75 | } 76 | 77 | @Override 78 | protected void handleStartProgressEvent(int actionCode, MotionEvent event){ 79 | switch (actionCode) { 80 | case MotionEvent.ACTION_DOWN: 81 | resetState(); // In case we missed an UP/CANCEL event 82 | 83 | mPrevEvent = MotionEvent.obtain(event); 84 | mTimeDelta = 0; 85 | 86 | updateStateByEvent(event); 87 | break; 88 | 89 | case MotionEvent.ACTION_MOVE: 90 | mGestureInProgress = mListener.onMoveBegin(this); 91 | break; 92 | } 93 | } 94 | 95 | @Override 96 | protected void handleInProgressEvent(int actionCode, MotionEvent event){ 97 | switch (actionCode) { 98 | case MotionEvent.ACTION_UP: 99 | case MotionEvent.ACTION_CANCEL: 100 | mListener.onMoveEnd(this); 101 | resetState(); 102 | break; 103 | 104 | case MotionEvent.ACTION_MOVE: 105 | // If the gesture started before this detector was attached (somehow), 106 | // mPrevEvent will be null at this point and BaseGestureDetector's 107 | // updateStateByEvent() will crash. The following check will prevent this. 108 | if (mPrevEvent == null) { 109 | return; 110 | } 111 | updateStateByEvent(event); 112 | 113 | // Only accept the event if our relative pressure is within 114 | // a certain limit. This can help filter shaky data as a 115 | // finger is lifted. 116 | if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { 117 | final boolean updatePrevious = mListener.onMove(this); 118 | if (updatePrevious) { 119 | mPrevEvent.recycle(); 120 | mPrevEvent = MotionEvent.obtain(event); 121 | } 122 | } 123 | break; 124 | } 125 | } 126 | 127 | protected void updateStateByEvent(MotionEvent curr) { 128 | super.updateStateByEvent(curr); 129 | 130 | final MotionEvent prev = mPrevEvent; 131 | 132 | // Focus intenal 133 | mCurrFocusInternal = determineFocalPoint(curr); 134 | mPrevFocusInternal = determineFocalPoint(prev); 135 | 136 | // Focus external 137 | // - Prevent skipping of focus delta when a finger is added or removed 138 | boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount(); 139 | mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x, mCurrFocusInternal.y - mPrevFocusInternal.y); 140 | 141 | // - Don't directly use mFocusInternal (or skipping will occur). Add 142 | // unskipped delta values to mFocusExternal instead. 143 | mFocusExternal.x += mFocusDeltaExternal.x; 144 | mFocusExternal.y += mFocusDeltaExternal.y; 145 | } 146 | 147 | /** 148 | * Determine (multi)finger focal point (a.k.a. center point between all 149 | * fingers) 150 | * 151 | * @param MotionEvent e 152 | * @return PointF focal point 153 | */ 154 | private PointF determineFocalPoint(MotionEvent e){ 155 | // Number of fingers on screen 156 | final int pCount = e.getPointerCount(); 157 | float x = 0f; 158 | float y = 0f; 159 | 160 | for(int i = 0; i < pCount; i++){ 161 | x += e.getX(i); 162 | y += e.getY(i); 163 | } 164 | 165 | return new PointF(x/pCount, y/pCount); 166 | } 167 | 168 | public float getFocusX() { 169 | return mFocusExternal.x; 170 | } 171 | 172 | public float getFocusY() { 173 | return mFocusExternal.y; 174 | } 175 | 176 | public PointF getFocusDelta() { 177 | return mFocusDeltaExternal; 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /library/src/main/java/com/almeros/android/multitouch/RotateGestureDetector.java: -------------------------------------------------------------------------------- 1 | package com.almeros.android.multitouch; 2 | 3 | import android.content.Context; 4 | import android.view.MotionEvent; 5 | 6 | /** 7 | * @author Almer Thie (code.almeros.com) 8 | * Copyright (c) 2013, Almer Thie (code.almeros.com) 9 | * 10 | * All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 13 | * 14 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 15 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 16 | * in the documentation and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 21 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 22 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 24 | * OF SUCH DAMAGE. 25 | */ 26 | public class RotateGestureDetector extends TwoFingerGestureDetector { 27 | 28 | /** 29 | * Listener which must be implemented which is used by RotateGestureDetector 30 | * to perform callbacks to any implementing class which is registered to a 31 | * RotateGestureDetector via the constructor. 32 | * 33 | * @see RotateGestureDetector.SimpleOnRotateGestureListener 34 | */ 35 | public interface OnRotateGestureListener { 36 | public boolean onRotate(RotateGestureDetector detector); 37 | public boolean onRotateBegin(RotateGestureDetector detector); 38 | public void onRotateEnd(RotateGestureDetector detector); 39 | } 40 | 41 | /** 42 | * Helper class which may be extended and where the methods may be 43 | * implemented. This way it is not necessary to implement all methods 44 | * of OnRotateGestureListener. 45 | */ 46 | public static class SimpleOnRotateGestureListener implements OnRotateGestureListener { 47 | public boolean onRotate(RotateGestureDetector detector) { 48 | return false; 49 | } 50 | 51 | public boolean onRotateBegin(RotateGestureDetector detector) { 52 | return true; 53 | } 54 | 55 | public void onRotateEnd(RotateGestureDetector detector) { 56 | // Do nothing, overridden implementation may be used 57 | } 58 | } 59 | 60 | 61 | private final OnRotateGestureListener mListener; 62 | private boolean mSloppyGesture; 63 | 64 | public RotateGestureDetector(Context context, OnRotateGestureListener listener) { 65 | super(context); 66 | mListener = listener; 67 | } 68 | 69 | @Override 70 | protected void handleStartProgressEvent(int actionCode, MotionEvent event){ 71 | switch (actionCode) { 72 | case MotionEvent.ACTION_POINTER_DOWN: 73 | // At least the second finger is on screen now 74 | 75 | resetState(); // In case we missed an UP/CANCEL event 76 | mPrevEvent = MotionEvent.obtain(event); 77 | mTimeDelta = 0; 78 | 79 | updateStateByEvent(event); 80 | 81 | // See if we have a sloppy gesture 82 | mSloppyGesture = isSloppyGesture(event); 83 | if(!mSloppyGesture){ 84 | // No, start gesture now 85 | mGestureInProgress = mListener.onRotateBegin(this); 86 | } 87 | break; 88 | 89 | case MotionEvent.ACTION_MOVE: 90 | if (!mSloppyGesture) { 91 | break; 92 | } 93 | 94 | // See if we still have a sloppy gesture 95 | mSloppyGesture = isSloppyGesture(event); 96 | if(!mSloppyGesture){ 97 | // No, start normal gesture now 98 | mGestureInProgress = mListener.onRotateBegin(this); 99 | } 100 | 101 | break; 102 | 103 | case MotionEvent.ACTION_POINTER_UP: 104 | if (!mSloppyGesture) { 105 | break; 106 | } 107 | 108 | break; 109 | } 110 | } 111 | 112 | 113 | @Override 114 | protected void handleInProgressEvent(int actionCode, MotionEvent event){ 115 | switch (actionCode) { 116 | case MotionEvent.ACTION_POINTER_UP: 117 | // Gesture ended but 118 | updateStateByEvent(event); 119 | 120 | if (!mSloppyGesture) { 121 | mListener.onRotateEnd(this); 122 | } 123 | 124 | resetState(); 125 | break; 126 | 127 | case MotionEvent.ACTION_CANCEL: 128 | if (!mSloppyGesture) { 129 | mListener.onRotateEnd(this); 130 | } 131 | 132 | resetState(); 133 | break; 134 | 135 | case MotionEvent.ACTION_MOVE: 136 | updateStateByEvent(event); 137 | 138 | // Only accept the event if our relative pressure is within 139 | // a certain limit. This can help filter shaky data as a 140 | // finger is lifted. 141 | if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { 142 | final boolean updatePrevious = mListener.onRotate(this); 143 | if (updatePrevious) { 144 | mPrevEvent.recycle(); 145 | mPrevEvent = MotionEvent.obtain(event); 146 | } 147 | } 148 | break; 149 | } 150 | } 151 | 152 | @Override 153 | protected void resetState() { 154 | super.resetState(); 155 | mSloppyGesture = false; 156 | } 157 | 158 | 159 | /** 160 | * Return the rotation difference from the previous rotate event to the current 161 | * event. 162 | * 163 | * @return The current rotation //difference in degrees. 164 | */ 165 | public float getRotationDegreesDelta() { 166 | double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX) - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX); 167 | return (float) (diffRadians * 180 / Math.PI); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /library/src/main/java/com/almeros/android/multitouch/ShoveGestureDetector.java: -------------------------------------------------------------------------------- 1 | package com.almeros.android.multitouch; 2 | 3 | import android.content.Context; 4 | import android.view.MotionEvent; 5 | 6 | /** 7 | * @author Robert Nordan (robert.nordan@norkart.no) 8 | * 9 | * Copyright (c) 2013, Norkart AS 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 14 | * 15 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 16 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 17 | * in the documentation and/or other materials provided with the distribution. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 20 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 22 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 23 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 25 | * OF SUCH DAMAGE. 26 | */ 27 | public class ShoveGestureDetector extends TwoFingerGestureDetector { 28 | 29 | /** 30 | * Listener which must be implemented which is used by ShoveGestureDetector 31 | * to perform callbacks to any implementing class which is registered to a 32 | * ShoveGestureDetector via the constructor. 33 | * 34 | * @see ShoveGestureDetector.SimpleOnShoveGestureListener 35 | */ 36 | public interface OnShoveGestureListener { 37 | public boolean onShove(ShoveGestureDetector detector); 38 | public boolean onShoveBegin(ShoveGestureDetector detector); 39 | public void onShoveEnd(ShoveGestureDetector detector); 40 | } 41 | 42 | /** 43 | * Helper class which may be extended and where the methods may be 44 | * implemented. This way it is not necessary to implement all methods 45 | * of OnShoveGestureListener. 46 | */ 47 | public static class SimpleOnShoveGestureListener implements OnShoveGestureListener { 48 | public boolean onShove(ShoveGestureDetector detector) { 49 | return false; 50 | } 51 | 52 | public boolean onShoveBegin(ShoveGestureDetector detector) { 53 | return true; 54 | } 55 | 56 | public void onShoveEnd(ShoveGestureDetector detector) { 57 | // Do nothing, overridden implementation may be used 58 | } 59 | } 60 | 61 | private float mPrevAverageY; 62 | private float mCurrAverageY; 63 | 64 | private final OnShoveGestureListener mListener; 65 | private boolean mSloppyGesture; 66 | 67 | public ShoveGestureDetector(Context context, OnShoveGestureListener listener) { 68 | super(context); 69 | mListener = listener; 70 | } 71 | 72 | @Override 73 | protected void handleStartProgressEvent(int actionCode, MotionEvent event){ 74 | switch (actionCode) { 75 | case MotionEvent.ACTION_POINTER_DOWN: 76 | // At least the second finger is on screen now 77 | 78 | resetState(); // In case we missed an UP/CANCEL event 79 | mPrevEvent = MotionEvent.obtain(event); 80 | mTimeDelta = 0; 81 | 82 | updateStateByEvent(event); 83 | 84 | // See if we have a sloppy gesture 85 | mSloppyGesture = isSloppyGesture(event); 86 | if(!mSloppyGesture){ 87 | // No, start gesture now 88 | mGestureInProgress = mListener.onShoveBegin(this); 89 | } 90 | break; 91 | 92 | case MotionEvent.ACTION_MOVE: 93 | if (!mSloppyGesture) { 94 | break; 95 | } 96 | 97 | // See if we still have a sloppy gesture 98 | mSloppyGesture = isSloppyGesture(event); 99 | if(!mSloppyGesture){ 100 | // No, start normal gesture now 101 | mGestureInProgress = mListener.onShoveBegin(this); 102 | } 103 | 104 | break; 105 | 106 | case MotionEvent.ACTION_POINTER_UP: 107 | if (!mSloppyGesture) { 108 | break; 109 | } 110 | 111 | break; 112 | } 113 | } 114 | 115 | 116 | @Override 117 | protected void handleInProgressEvent(int actionCode, MotionEvent event){ 118 | switch (actionCode) { 119 | case MotionEvent.ACTION_POINTER_UP: 120 | // Gesture ended but 121 | updateStateByEvent(event); 122 | 123 | if (!mSloppyGesture) { 124 | mListener.onShoveEnd(this); 125 | } 126 | 127 | resetState(); 128 | break; 129 | 130 | case MotionEvent.ACTION_CANCEL: 131 | if (!mSloppyGesture) { 132 | mListener.onShoveEnd(this); 133 | } 134 | 135 | resetState(); 136 | break; 137 | 138 | case MotionEvent.ACTION_MOVE: 139 | updateStateByEvent(event); 140 | 141 | // Only accept the event if our relative pressure is within 142 | // a certain limit. This can help filter shaky data as a 143 | // finger is lifted. Also check that shove is meaningful. 144 | if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD 145 | && Math.abs(getShovePixelsDelta()) > 0.5f) { 146 | final boolean updatePrevious = mListener.onShove(this); 147 | if (updatePrevious) { 148 | mPrevEvent.recycle(); 149 | mPrevEvent = MotionEvent.obtain(event); 150 | } 151 | } 152 | break; 153 | } 154 | } 155 | 156 | @Override 157 | protected void resetState() { 158 | super.resetState(); 159 | mSloppyGesture = false; 160 | mPrevAverageY = 0.0f; 161 | mCurrAverageY = 0.0f; 162 | } 163 | 164 | @Override 165 | protected void updateStateByEvent(MotionEvent curr){ 166 | super.updateStateByEvent(curr); 167 | 168 | final MotionEvent prev = mPrevEvent; 169 | float py0 = prev.getY(0); 170 | float py1 = prev.getY(1); 171 | mPrevAverageY = (py0 + py1) / 2.0f; 172 | 173 | float cy0 = curr.getY(0); 174 | float cy1 = curr.getY(1); 175 | mCurrAverageY = (cy0 + cy1) / 2.0f; 176 | } 177 | 178 | @Override 179 | protected boolean isSloppyGesture(MotionEvent event){ 180 | boolean sloppy = super.isSloppyGesture(event); 181 | if (sloppy) 182 | return true; 183 | 184 | // If it's not traditionally sloppy, we check if the angle between fingers 185 | // is acceptable. 186 | double angle = Math.abs(Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX)); 187 | //about 20 degrees, left or right 188 | return !(( 0.0f < angle && angle < 0.35f) 189 | || 2.79f < angle && angle < Math.PI); 190 | } 191 | 192 | 193 | /** 194 | * Return the distance in pixels from the previous shove event to the current 195 | * event. 196 | * 197 | * @return The current distance in pixels. 198 | */ 199 | public float getShovePixelsDelta() { 200 | return mCurrAverageY - mPrevAverageY; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /library/src/main/java/com/almeros/android/multitouch/TwoFingerGestureDetector.java: -------------------------------------------------------------------------------- 1 | package com.almeros.android.multitouch; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.util.FloatMath; 6 | import android.view.MotionEvent; 7 | import android.view.ViewConfiguration; 8 | 9 | /** 10 | * @author Almer Thie (code.almeros.com) 11 | * Copyright (c) 2013, Almer Thie (code.almeros.com) 12 | * 13 | * All rights reserved. 14 | * 15 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 16 | * 17 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 18 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 19 | * in the documentation and/or other materials provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 22 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 24 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 25 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 27 | * OF SUCH DAMAGE. 28 | */ 29 | public abstract class TwoFingerGestureDetector extends BaseGestureDetector { 30 | 31 | private final float mEdgeSlop; 32 | private float mRightSlopEdge; 33 | private float mBottomSlopEdge; 34 | 35 | protected float mPrevFingerDiffX; 36 | protected float mPrevFingerDiffY; 37 | protected float mCurrFingerDiffX; 38 | protected float mCurrFingerDiffY; 39 | 40 | private float mCurrLen; 41 | private float mPrevLen; 42 | 43 | public TwoFingerGestureDetector(Context context) { 44 | super(context); 45 | 46 | ViewConfiguration config = ViewConfiguration.get(context); 47 | mEdgeSlop = config.getScaledEdgeSlop(); 48 | } 49 | 50 | @Override 51 | protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event); 52 | 53 | @Override 54 | protected abstract void handleInProgressEvent(int actionCode, MotionEvent event); 55 | 56 | protected void updateStateByEvent(MotionEvent curr){ 57 | super.updateStateByEvent(curr); 58 | 59 | final MotionEvent prev = mPrevEvent; 60 | 61 | mCurrLen = -1; 62 | mPrevLen = -1; 63 | 64 | // Previous 65 | final float px0 = prev.getX(0); 66 | final float py0 = prev.getY(0); 67 | final float px1 = prev.getX(1); 68 | final float py1 = prev.getY(1); 69 | final float pvx = px1 - px0; 70 | final float pvy = py1 - py0; 71 | mPrevFingerDiffX = pvx; 72 | mPrevFingerDiffY = pvy; 73 | 74 | // Current 75 | final float cx0 = curr.getX(0); 76 | final float cy0 = curr.getY(0); 77 | final float cx1 = curr.getX(1); 78 | final float cy1 = curr.getY(1); 79 | final float cvx = cx1 - cx0; 80 | final float cvy = cy1 - cy0; 81 | mCurrFingerDiffX = cvx; 82 | mCurrFingerDiffY = cvy; 83 | } 84 | 85 | /** 86 | * Return the current distance between the two pointers forming the 87 | * gesture in progress. 88 | * 89 | * @return Distance between pointers in pixels. 90 | */ 91 | public float getCurrentSpan() { 92 | if (mCurrLen == -1) { 93 | final float cvx = mCurrFingerDiffX; 94 | final float cvy = mCurrFingerDiffY; 95 | mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy); 96 | } 97 | return mCurrLen; 98 | } 99 | 100 | /** 101 | * Return the previous distance between the two pointers forming the 102 | * gesture in progress. 103 | * 104 | * @return Previous distance between pointers in pixels. 105 | */ 106 | public float getPreviousSpan() { 107 | if (mPrevLen == -1) { 108 | final float pvx = mPrevFingerDiffX; 109 | final float pvy = mPrevFingerDiffY; 110 | mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy); 111 | } 112 | return mPrevLen; 113 | } 114 | 115 | /** 116 | * MotionEvent has no getRawX(int) method; simulate it pending future API approval. 117 | * @param event 118 | * @param pointerIndex 119 | * @return 120 | */ 121 | protected static float getRawX(MotionEvent event, int pointerIndex) { 122 | float viewToRaw = event.getRawX() - event.getX(); 123 | if (pointerIndex < event.getPointerCount()) { 124 | return event.getX(pointerIndex) + viewToRaw; 125 | } 126 | return 0f; 127 | } 128 | 129 | /** 130 | * MotionEvent has no getRawY(int) method; simulate it pending future API approval. 131 | * @param event 132 | * @param pointerIndex 133 | * @return 134 | */ 135 | protected static float getRawY(MotionEvent event, int pointerIndex) { 136 | float viewToRaw = event.getRawY() - event.getY(); 137 | if (pointerIndex < event.getPointerCount()) { 138 | return event.getY(pointerIndex) + viewToRaw; 139 | } 140 | return 0f; 141 | } 142 | 143 | /** 144 | * Check if we have a sloppy gesture. Sloppy gestures can happen if the edge 145 | * of the user's hand is touching the screen, for example. 146 | * 147 | * @param event 148 | * @return 149 | */ 150 | protected boolean isSloppyGesture(MotionEvent event){ 151 | // As orientation can change, query the metrics in touch down 152 | DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); 153 | mRightSlopEdge = metrics.widthPixels - mEdgeSlop; 154 | mBottomSlopEdge = metrics.heightPixels - mEdgeSlop; 155 | 156 | final float edgeSlop = mEdgeSlop; 157 | final float rightSlop = mRightSlopEdge; 158 | final float bottomSlop = mBottomSlopEdge; 159 | 160 | final float x0 = event.getRawX(); 161 | final float y0 = event.getRawY(); 162 | final float x1 = getRawX(event, 1); 163 | final float y1 = getRawY(event, 1); 164 | 165 | boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop 166 | || x0 > rightSlop || y0 > bottomSlop; 167 | boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop 168 | || x1 > rightSlop || y1 > bottomSlop; 169 | 170 | if (p0sloppy && p1sloppy) { 171 | return true; 172 | } else if (p0sloppy) { 173 | return true; 174 | } else if (p1sloppy) { 175 | return true; 176 | } 177 | return false; 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library' 2 | --------------------------------------------------------------------------------