├── .gitignore ├── README.md ├── assets ├── capture.gif └── googleplay.png ├── build.gradle ├── demo ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── nshmura │ │ └── snappyimageviewer │ │ └── demo │ │ ├── ImageViewerActivity.java │ │ └── MainActivity.java │ └── res │ ├── drawable-xxhdpi │ └── sample.jpg │ ├── layout │ ├── activity_image_viewer.xml │ └── activity_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── nshmura │ └── snappyimageviewer │ ├── DegreeVelocityTracker.java │ ├── SnappyDragHelper.java │ ├── SnappyImageView.java │ └── SnappyImageViewer.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | # Files for the Dalvik VM 5 | *.dex 6 | 7 | # Java class files 8 | *.class 9 | 10 | # Generated files 11 | bin/ 12 | gen/ 13 | obj/ 14 | apk/ 15 | target/ 16 | build/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | .idea 21 | *.iml 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # docs 30 | docs/ 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SnappyImageViewer 2 | 3 | Android Image Viewer inspired by StackOverflow's with swipe-to-dimiss and moving animations. 4 | 5 | Gif Animation File 6 | 7 | 8 | ## Demo 9 | 10 | 11 | 12 | ## Getting Started 13 | 14 | In your `build.gradle`: 15 | 16 | ```gradle 17 | repositories { 18 | jcenter() 19 | } 20 | 21 | dependencies { 22 | compile 'com.nshmura:snappyimageviewer:1.1.0' 23 | } 24 | ``` 25 | 26 | In your layout file: 27 | ```xml 28 | 32 | ``` 33 | 34 | Setup the `SnappyImageViewer`: 35 | ```java 36 | SnappyImageViewer snappyImageViewer = (SnappyImageViewer) findViewById(R.id.snappy_image_viewer); 37 | snappyImageViewer.setImageResource(R.drawable.sample); 38 | snappyImageViewer.addOnClosedListener(new SnappyImageViewer.OnClosedListener() { 39 | @Override 40 | public void onClosed() { 41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 42 | getWindow().setSharedElementReturnTransition(new Fade(Fade.IN)); 43 | } 44 | ActivityCompat.finishAfterTransition(ImageViewerActivity.this); 45 | } 46 | }); 47 | ``` 48 | 49 | ## TODO 50 | - Pinch to Zoom 51 | - Embed in ViewPager 52 | - Add `ImageViwerActivity` in library 53 | 54 | 55 | ## Contributions 56 | 57 | Your contributions always welcome! 58 | 59 | 60 | ## License 61 | ``` 62 | Copyright (C) 2016 nshmura 63 | 64 | Licensed under the Apache License, Version 2.0 (the "License"); 65 | you may not use this file except in compliance with the License. 66 | You may obtain a copy of the License at 67 | 68 | http://www.apache.org/licenses/LICENSE-2.0 69 | 70 | Unless required by applicable law or agreed to in writing, software 71 | distributed under the License is distributed on an "AS IS" BASIS, 72 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 73 | See the License for the specific language governing permissions and 74 | limitations under the License. 75 | ``` 76 | -------------------------------------------------------------------------------- /assets/capture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshmura/SnappyImageViewer/7497171d8dc4b4f05a110ca063dd3544bc1598e3/assets/capture.gif -------------------------------------------------------------------------------- /assets/googleplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshmura/SnappyImageViewer/7497171d8dc4b4f05a110ca063dd3544bc1598e3/assets/googleplay.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | classpath 'com.novoda:bintray-release:0.3.4' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } 25 | 26 | 27 | ext { 28 | minSdkVersion = 15 29 | compileSdkVersion = 24 30 | targetSdkVersion = compileSdkVersion 31 | buildToolsVersion = '24.0.2' 32 | javaVersion = JavaVersion.VERSION_1_7 33 | supportLibraryVersion = '24.2.1' 34 | versionCode = 2 35 | versionName = "1.1.0" 36 | 37 | POM_DEVELOPER_ID = "nshmura" 38 | POM_GROUP = "com.nshmura" 39 | POM_VERSION_NAME = versionName 40 | POM_URL = "https://github.com/nshmura/SnappyImageViewer" 41 | 42 | POM_ARTIFACT_ID = "snappyimageviewer" 43 | POM_ARTIFACT_NAME = "SnappyImageViewer" 44 | POM_DESCRIPTION = "Android Image Viewer inspired by StackOverflow's with swipe-to-dimiss and moving animations." 45 | } -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | applicationId "com.nshmura.snappyimageviewer.demo" 9 | minSdkVersion rootProject.ext.minSdkVersion 10 | targetSdkVersion rootProject.ext.targetSdkVersion 11 | versionCode rootProject.ext.versionCode 12 | versionName rootProject.ext.versionName 13 | } 14 | 15 | signingConfigs { 16 | release { 17 | if (hasProperty('NSHMURA_KEYSTORE')) { 18 | storeFile file(NSHMURA_KEYSTORE) 19 | keyAlias NSHMURA_KEYALIAS 20 | storePassword NSHMURA_KEYSTOREPASSWORD 21 | keyPassword NSHMURA_KEYALIASPASSWORD 22 | } 23 | } 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | if (hasProperty('NSHMURA_KEYSTORE')) { 31 | signingConfig signingConfigs.release 32 | } 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | compile fileTree(dir: 'libs', include: ['*.jar']) 39 | testCompile 'junit:junit:4.12' 40 | compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion" 41 | compile 'com.squareup.picasso:picasso:2.5.2' 42 | //compile 'com.nshmura:snappyimageviewer:1.0.0' 43 | compile project(":library") 44 | } 45 | -------------------------------------------------------------------------------- /demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/a13088/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demo/src/main/java/com/nshmura/snappyimageviewer/demo/ImageViewerActivity.java: -------------------------------------------------------------------------------- 1 | package com.nshmura.snappyimageviewer.demo; 2 | 3 | import com.nshmura.snappyimageviewer.SnappyImageViewer; 4 | import com.squareup.picasso.Picasso; 5 | 6 | import android.app.Activity; 7 | import android.content.Intent; 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.support.v4.app.ActivityCompat; 11 | import android.support.v4.app.ActivityOptionsCompat; 12 | import android.support.v4.util.Pair; 13 | import android.support.v4.view.ViewCompat; 14 | import android.support.v7.app.AppCompatActivity; 15 | import android.transition.Fade; 16 | import android.view.View; 17 | import android.widget.ImageView; 18 | 19 | public class ImageViewerActivity extends AppCompatActivity { 20 | 21 | private static final String EXTRA_IMAGE_URL = "EXTRA_IMAGE_URL"; 22 | private static final String TRANSITION_NAME_PHOTO = "TRANSITION_NAME_PHOTO"; 23 | 24 | public static void start(Activity activity, String uri, ImageView imageView) { 25 | Intent intent = new Intent(activity, ImageViewerActivity.class); 26 | intent.putExtra(EXTRA_IMAGE_URL, uri); 27 | 28 | //noinspection unchecked 29 | Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, 30 | Pair.create((View) imageView, TRANSITION_NAME_PHOTO)) 31 | .toBundle(); 32 | 33 | ActivityCompat.startActivity(activity, intent, options); 34 | } 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_image_viewer); 40 | 41 | String uri = getIntent().getStringExtra(EXTRA_IMAGE_URL); 42 | 43 | SnappyImageViewer snappyImageViewer = (SnappyImageViewer) findViewById(R.id.snappy_image_viewer); 44 | 45 | Picasso.with(this) 46 | .load(uri) 47 | .into(snappyImageViewer.getImageView()); 48 | 49 | snappyImageViewer.addOnClosedListener(new SnappyImageViewer.OnClosedListener() { 50 | @Override 51 | public void onClosed() { 52 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 53 | getWindow().setSharedElementReturnTransition(new Fade(Fade.IN)); 54 | } 55 | ActivityCompat.finishAfterTransition(ImageViewerActivity.this); 56 | } 57 | }); 58 | ViewCompat.setTransitionName(snappyImageViewer.getImageView(), TRANSITION_NAME_PHOTO); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /demo/src/main/java/com/nshmura/snappyimageviewer/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.nshmura.snappyimageviewer.demo; 2 | 3 | import com.squareup.picasso.Picasso; 4 | 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | import android.widget.ImageView; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | 17 | final ImageView imageView = (ImageView) findViewById(R.id.image); 18 | 19 | final String uri = "https://developer.android.com/images/home/nougat_bg.jpg"; 20 | 21 | Picasso.with(this) 22 | .load(uri) 23 | .into(imageView); 24 | 25 | imageView.setOnClickListener(new View.OnClickListener() { 26 | @Override 27 | public void onClick(View v) { 28 | ImageViewerActivity.start(MainActivity.this, uri, imageView); 29 | } 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshmura/SnappyImageViewer/7497171d8dc4b4f05a110ca063dd3544bc1598e3/demo/src/main/res/drawable-xxhdpi/sample.jpg -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_image_viewer.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshmura/SnappyImageViewer/7497171d8dc4b4f05a110ca063dd3544bc1598e3/demo/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshmura/SnappyImageViewer/7497171d8dc4b4f05a110ca063dd3544bc1598e3/demo/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshmura/SnappyImageViewer/7497171d8dc4b4f05a110ca063dd3544bc1598e3/demo/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshmura/SnappyImageViewer/7497171d8dc4b4f05a110ca063dd3544bc1598e3/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshmura/SnappyImageViewer/7497171d8dc4b4f05a110ca063dd3544bc1598e3/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /demo/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ee000000 4 | 5 | -------------------------------------------------------------------------------- /demo/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /demo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SnappyImageViewer 3 | 4 | -------------------------------------------------------------------------------- /demo/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshmura/SnappyImageViewer/7497171d8dc4b4f05a110ca063dd3544bc1598e3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 02 17:44:53 JST 2016 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-2.14.1-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 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release' 3 | 4 | android { 5 | compileSdkVersion rootProject.ext.compileSdkVersion 6 | buildToolsVersion rootProject.ext.buildToolsVersion 7 | 8 | defaultConfig { 9 | minSdkVersion rootProject.ext.minSdkVersion 10 | targetSdkVersion rootProject.ext.targetSdkVersion 11 | } 12 | } 13 | 14 | publish { 15 | userOrg = rootProject.ext.POM_DEVELOPER_ID 16 | groupId = rootProject.ext.POM_GROUP 17 | artifactId = rootProject.ext.POM_ARTIFACT_ID 18 | uploadName = rootProject.ext.POM_ARTIFACT_NAME 19 | publishVersion = rootProject.ext.POM_VERSION_NAME 20 | desc = rootProject.ext.POM_DESCRIPTION 21 | website = rootProject.ext.POM_URL 22 | } -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/a13088/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/java/com/nshmura/snappyimageviewer/DegreeVelocityTracker.java: -------------------------------------------------------------------------------- 1 | package com.nshmura.snappyimageviewer; 2 | 3 | public class DegreeVelocityTracker { 4 | private float degreeVelocity; 5 | private float lastMsec; 6 | 7 | public float getDegreeVelocity() { 8 | return degreeVelocity; 9 | } 10 | 11 | public void addDegree(float degree) { 12 | float currMsec = getMsec(); 13 | float deltaMsec = currMsec - lastMsec; 14 | lastMsec = currMsec; 15 | degreeVelocity = degree / deltaMsec; 16 | } 17 | 18 | public void reset() { 19 | lastMsec = getMsec(); 20 | } 21 | 22 | public float getMsec() { 23 | return System.nanoTime() / 1000000f; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/nshmura/snappyimageviewer/SnappyDragHelper.java: -------------------------------------------------------------------------------- 1 | package com.nshmura.snappyimageviewer; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.graphics.Matrix; 6 | import android.graphics.RectF; 7 | import android.view.animation.OvershootInterpolator; 8 | 9 | class SnappyDragHelper { 10 | 11 | private float CLOSE_VELOCITY_THRESHOLD = 2500; 12 | 13 | private final int viewerWidth; 14 | private final int viewerHeight; 15 | private final int imageWidth; 16 | private final int imageHeight; 17 | private final Matrix imgInitMatrix; 18 | private Listener listener; 19 | 20 | private float bmpDegree; 21 | private float[] workPoint = new float[2]; 22 | private Matrix touchMatrix = new Matrix(); 23 | private Matrix imgMatrix = new Matrix(); 24 | private Matrix imgInvertMatrix = new Matrix(); 25 | 26 | private ValueAnimator animator; 27 | private DegreeVelocityTracker degreeVelocityTracker = new DegreeVelocityTracker(); 28 | 29 | interface Listener { 30 | void onMove(Matrix imgMatrix); 31 | 32 | void onRestored(); 33 | 34 | void onCastAwayed(); 35 | } 36 | 37 | SnappyDragHelper(int viewerWidth, int viewerHeight, int imageWidth, int imageHeight, 38 | Matrix imgInitMatrix, Listener listener) { 39 | this.viewerWidth = viewerWidth; 40 | this.viewerHeight = viewerHeight; 41 | this.imageWidth = imageWidth; 42 | this.imageHeight = imageHeight; 43 | this.imgInitMatrix = imgInitMatrix; 44 | this.listener = listener; 45 | } 46 | 47 | void onTouch() { 48 | touchMatrix.reset(); 49 | imgInvertMatrix.reset(); 50 | imgInitMatrix.invert(imgInvertMatrix); 51 | bmpDegree = 0; 52 | degreeVelocityTracker.reset(); 53 | } 54 | 55 | void onMove(float currentX, float currentY, float newX, float newY) { 56 | float dx = newX - currentX; 57 | float dy = newY - currentY; 58 | 59 | //Convert current touch point to bitmap's local coordinate. 60 | workPoint[0] = currentX; 61 | workPoint[1] = currentY; 62 | imgInvertMatrix.mapPoints(workPoint); 63 | float currentTx = workPoint[0] - imageWidth / 2f; 64 | float currentTy = workPoint[1] - imageHeight / 2f; 65 | 66 | //Convert new touch point to bitmap's local coordinate. 67 | workPoint[0] = newX; 68 | workPoint[1] = newY; 69 | imgInvertMatrix.mapPoints(workPoint); 70 | float newTx = workPoint[0] - imageWidth / 2f; 71 | float newTy = workPoint[1] - imageHeight / 2f; 72 | 73 | //calculate the degrees 74 | float radius0 = (float) Math.atan2(currentTy, currentTx); //current degree 75 | float radius1 = (float) Math.atan2(newTy, newTx); //new degree 76 | 77 | float radius = (radius1 - radius0); 78 | if (Math.abs(radius) > Math.PI) { 79 | if (radius < 0) { 80 | radius = (float) (radius + 2 * Math.PI); 81 | } else { 82 | radius = (float) (radius - 2 * Math.PI); 83 | } 84 | } 85 | 86 | float distance = getLength(newTx, newTy); //distance from center of bitmap 87 | float maxDistance = getLength(imageWidth, imageHeight); 88 | float factor = (float) Math.min(1, Math.max(0, 1.5 * Math.pow(distance / maxDistance, 1.3))); 89 | float deltaDegree = (float) (radius * factor * 180 / Math.PI); 90 | 91 | bmpDegree += deltaDegree; 92 | degreeVelocityTracker.addDegree(deltaDegree); 93 | 94 | touchMatrix.postRotate(deltaDegree, newX, newY); 95 | touchMatrix.postTranslate(dx, dy); 96 | 97 | workPoint[0] = workPoint[1] = 0; //left top 98 | touchMatrix.mapPoints(workPoint); 99 | moveTo(workPoint[0], workPoint[1], bmpDegree); 100 | } 101 | 102 | void onRelease(int xvel, int yvel) { 103 | float vel = getLength(Math.abs(xvel), Math.abs(yvel)); 104 | 105 | if (vel > CLOSE_VELOCITY_THRESHOLD) { 106 | castAway(xvel, yvel, degreeVelocityTracker.getDegreeVelocity()); 107 | 108 | } else { 109 | restore(); 110 | } 111 | } 112 | 113 | void cancelAnimation() { 114 | if (animator != null) { 115 | animator.cancel(); 116 | } 117 | } 118 | 119 | private void castAway(int xvel, int yvel, float degreeVel) { 120 | final float dx = xvel / 1000f; 121 | final float dy = yvel / 1000f; 122 | final float dDegree = Math.max(-0.5f, Math.min(0.5f, degreeVel * 5)); 123 | final Matrix centerMatrix = new Matrix(touchMatrix); 124 | 125 | //bitmap's center coordinate 126 | workPoint[0] = imageWidth / 2f; 127 | workPoint[1] = imageHeight / 2f; 128 | imgMatrix.mapPoints(workPoint); 129 | final float cx = workPoint[0]; 130 | final float cy = workPoint[1]; 131 | 132 | cancelAnimation(); 133 | animator = ValueAnimator.ofFloat(1, 0); 134 | animator.setDuration(3000); 135 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 136 | float centerX = cx; 137 | float centerY = cy; 138 | float degree = bmpDegree; 139 | float time = System.nanoTime(); 140 | 141 | @Override 142 | public void onAnimationUpdate(ValueAnimator animator) { 143 | float currTime = System.nanoTime(); 144 | float msec = (currTime - time) / 1000000f; 145 | time = currTime; 146 | 147 | centerX += dx * msec; 148 | centerY += dy * msec; 149 | degree += dDegree * msec; 150 | centerMatrix.postRotate(dDegree * msec, centerX, centerY); 151 | centerMatrix.postTranslate(dx * msec, dy * msec); 152 | 153 | workPoint[0] = workPoint[1] = 0; //left top 154 | centerMatrix.mapPoints(workPoint); 155 | float bmpX = workPoint[0]; 156 | float bmpY = workPoint[1]; 157 | 158 | moveTo(bmpX, bmpY, degree); 159 | 160 | if (isOutOfView()) { 161 | animator.cancel(); 162 | } 163 | } 164 | }); 165 | animator.addListener(new Animator.AnimatorListener() { 166 | @Override 167 | public void onAnimationStart(Animator animation) { 168 | 169 | } 170 | 171 | @Override 172 | public void onAnimationEnd(Animator animation) { 173 | listener.onCastAwayed(); 174 | animator = null; 175 | } 176 | 177 | @Override 178 | public void onAnimationCancel(Animator animation) { 179 | } 180 | 181 | @Override 182 | public void onAnimationRepeat(Animator animation) { 183 | 184 | } 185 | }); 186 | animator.start(); 187 | } 188 | 189 | private boolean isOutOfView() { 190 | RectF rect = new RectF(0, 0, imageWidth, imageHeight); 191 | imgMatrix.mapRect(rect); 192 | 193 | boolean outOfLeft = (rect.left < 0 && rect.right < 0); 194 | boolean outOfRight = (rect.left > viewerWidth && rect.right > viewerWidth); 195 | boolean outOfTop = (rect.top < 0 && rect.bottom < 0); 196 | boolean outOfBottom = (rect.top > viewerHeight && rect.bottom > viewerHeight); 197 | 198 | return outOfLeft || outOfRight || outOfTop || outOfBottom; 199 | } 200 | 201 | private void restore() { 202 | workPoint[0] = workPoint[1] = 0; //left top 203 | touchMatrix.mapPoints(workPoint); 204 | final float bmpX = workPoint[0]; 205 | final float bmpY = workPoint[1]; 206 | final float degree = bmpDegree; 207 | 208 | cancelAnimation(); 209 | animator = ValueAnimator.ofFloat(1, 0); 210 | animator.setDuration(400); 211 | animator.setInterpolator(new OvershootInterpolator(1.002f)); 212 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 213 | @Override 214 | public void onAnimationUpdate(ValueAnimator animator) { 215 | float value = (float) animator.getAnimatedValue(); 216 | moveTo(bmpX * value, bmpY * value, degree * value); 217 | } 218 | }); 219 | animator.addListener(new Animator.AnimatorListener() { 220 | @Override 221 | public void onAnimationStart(Animator animation) { 222 | 223 | } 224 | 225 | @Override 226 | public void onAnimationEnd(Animator animation) { 227 | listener.onRestored(); 228 | animator = null; 229 | } 230 | 231 | @Override 232 | public void onAnimationCancel(Animator animation) { 233 | 234 | } 235 | 236 | @Override 237 | public void onAnimationRepeat(Animator animation) { 238 | 239 | } 240 | }); 241 | animator.start(); 242 | } 243 | 244 | private void moveTo(float bmpX, float bmpY, float bmpDegree) { 245 | imgMatrix.reset(); 246 | imgMatrix.set(imgInitMatrix); 247 | imgMatrix.postTranslate(bmpX, bmpY); 248 | imgMatrix.postRotate(bmpDegree, bmpX, bmpY); 249 | imgMatrix.invert(imgInvertMatrix); 250 | listener.onMove(imgMatrix); 251 | } 252 | 253 | private float getLength(float x, float y) { 254 | return (float) Math.sqrt(x * x + y * y); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /library/src/main/java/com/nshmura/snappyimageviewer/SnappyImageView.java: -------------------------------------------------------------------------------- 1 | package com.nshmura.snappyimageviewer; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.drawable.Drawable; 7 | import android.net.Uri; 8 | import android.widget.ImageView; 9 | 10 | class SnappyImageView extends ImageView { 11 | 12 | interface Listener { 13 | void onImageChanged(); 14 | void onDraw(); 15 | } 16 | 17 | private SnappyImageView.Listener mListener; 18 | 19 | public SnappyImageView(Context context) { 20 | super(context); 21 | } 22 | 23 | 24 | @Override 25 | public void setImageBitmap(Bitmap bitmap) { 26 | super.setImageBitmap(bitmap); 27 | if (mListener != null) { 28 | mListener.onImageChanged(); 29 | } 30 | } 31 | 32 | @Override 33 | public void setImageResource(int resId) { 34 | super.setImageResource(resId); 35 | if (mListener != null) { 36 | mListener.onImageChanged(); 37 | } 38 | } 39 | 40 | @Override 41 | public void setImageURI(Uri uri) { 42 | super.setImageURI(uri); 43 | if (mListener != null) { 44 | mListener.onImageChanged(); 45 | } 46 | } 47 | 48 | @Override 49 | public void setImageDrawable(Drawable drawable) { 50 | super.setImageDrawable(drawable); 51 | if (mListener != null) { 52 | mListener.onImageChanged(); 53 | } 54 | } 55 | 56 | @Override 57 | protected void onDraw(Canvas canvas) { 58 | super.onDraw(canvas); 59 | if (mListener != null) { 60 | mListener.onDraw(); 61 | } 62 | } 63 | 64 | public void setListener(Listener listener) { 65 | mListener = listener; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /library/src/main/java/com/nshmura/snappyimageviewer/SnappyImageViewer.java: -------------------------------------------------------------------------------- 1 | package com.nshmura.snappyimageviewer; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Matrix; 7 | import android.graphics.RectF; 8 | import android.graphics.drawable.Drawable; 9 | import android.net.Uri; 10 | import android.os.Build; 11 | import android.util.AttributeSet; 12 | import android.view.MotionEvent; 13 | import android.view.VelocityTracker; 14 | import android.view.ViewConfiguration; 15 | import android.view.ViewGroup; 16 | import android.widget.FrameLayout; 17 | import android.widget.ImageView; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | public class SnappyImageViewer extends FrameLayout { 23 | 24 | public static final int INVALID_POINTER = -1; 25 | 26 | private static final int STATE_IDLE = 0; 27 | private static final int STATE_DRAGGING = 1; 28 | private static final int STATE_PINCHING = 2; 29 | private static final int STATE_ZOOMED = 5; 30 | private static final int STATE_SETTLING = 4; 31 | private static final int STATE_CLOSED = 5; 32 | 33 | private int state = STATE_IDLE; 34 | 35 | private SnappyImageView imageView; 36 | private List onClosedListeners = new ArrayList<>(); 37 | 38 | //for drag 39 | private SnappyDragHelper snappyDragHelper; 40 | private int activePointerId; 41 | private VelocityTracker velocityTracker; 42 | private int maxVelocity; 43 | private float currentX; 44 | private float currentY; 45 | 46 | private RectF initialImageRect = new RectF(); 47 | private Matrix imgInitMatrix = new Matrix(); 48 | 49 | public interface OnClosedListener { 50 | void onClosed(); 51 | } 52 | 53 | public SnappyImageViewer(Context context) { 54 | super(context); 55 | init(); 56 | } 57 | 58 | public SnappyImageViewer(Context context, AttributeSet attrs) { 59 | super(context, attrs); 60 | init(); 61 | } 62 | 63 | public SnappyImageViewer(Context context, AttributeSet attrs, int defStyleAttr) { 64 | super(context, attrs, defStyleAttr); 65 | init(); 66 | } 67 | 68 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 69 | public SnappyImageViewer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 70 | super(context, attrs, defStyleAttr, defStyleRes); 71 | init(); 72 | } 73 | 74 | private void init() { 75 | final ViewConfiguration vc = ViewConfiguration.get(getContext()); 76 | maxVelocity = vc.getScaledMaximumFlingVelocity(); 77 | 78 | imageView = new SnappyImageView(getContext()); 79 | imageView.setScaleType(ImageView.ScaleType.MATRIX); 80 | FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( 81 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 82 | imageView.setLayoutParams(layoutParams); 83 | addView(imageView); 84 | 85 | imageView.setListener(new SnappyImageView.Listener() { 86 | @Override 87 | public void onImageChanged() { 88 | updateSize(); 89 | } 90 | 91 | @Override 92 | public void onDraw() { 93 | if (snappyDragHelper == null && imageView.getDrawable() != null) { 94 | updateSize(); 95 | } 96 | } 97 | }); 98 | } 99 | 100 | public void addOnClosedListener(OnClosedListener listener) { 101 | onClosedListeners.add(listener); 102 | } 103 | 104 | public void setImageBitmap(Bitmap bitmap) { 105 | imageView.setImageBitmap(bitmap); 106 | } 107 | 108 | public void setImageResource(int resId) { 109 | imageView.setImageResource(resId); 110 | } 111 | 112 | public void setImageURI(Uri uri) { 113 | imageView.setImageURI(uri); 114 | } 115 | 116 | public void setImageDrawable(Drawable drawable) { 117 | imageView.setImageDrawable(drawable); 118 | } 119 | 120 | public ImageView getImageView() { 121 | return imageView; 122 | } 123 | 124 | public void updateSize() { 125 | if (imageView.getDrawable() == null) { 126 | return; 127 | } 128 | 129 | float imageWidth = imageView.getDrawable().getIntrinsicWidth(); 130 | float imageHeight = imageView.getDrawable().getIntrinsicHeight(); 131 | 132 | float viewWidth = getWidth() - getPaddingLeft() - getPaddingRight(); 133 | float viewHeight = getHeight() - getPaddingTop() - getPaddingBottom(); 134 | float widthRatio = viewWidth / imageWidth; 135 | float heightRatio = viewHeight / imageHeight; 136 | 137 | float scale = widthRatio < heightRatio ? widthRatio : heightRatio; 138 | float imgWidth = imageWidth * scale; 139 | float imgHeight = imageHeight * scale; 140 | 141 | float x = (viewWidth - imgWidth) / 2f; 142 | float y = (viewHeight - imgHeight) / 2f; 143 | 144 | initialImageRect.set(x, y, x + imgWidth, y + imgHeight); 145 | imgInitMatrix.reset(); 146 | imgInitMatrix.postScale(scale, scale); 147 | imgInitMatrix.postTranslate(x, y); 148 | 149 | if (snappyDragHelper != null) { 150 | snappyDragHelper.cancelAnimation(); 151 | } 152 | 153 | snappyDragHelper = new SnappyDragHelper(getWidth(), getHeight(), (int) imageWidth, (int) imageHeight, imgInitMatrix, 154 | new SnappyDragHelper.Listener() { 155 | @Override 156 | public void onMove(Matrix bmpMatrix) { 157 | imageView.setImageMatrix(bmpMatrix); 158 | } 159 | 160 | @Override 161 | public void onRestored() { 162 | setState(STATE_IDLE); 163 | } 164 | 165 | @Override 166 | public void onCastAwayed() { 167 | setState(STATE_CLOSED); 168 | notifyClosed(); 169 | } 170 | }); 171 | imageView.setImageMatrix(imgInitMatrix); 172 | } 173 | 174 | @Override 175 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 176 | super.onSizeChanged(w, h, oldw, oldh); 177 | updateSize(); 178 | } 179 | 180 | @Override 181 | public boolean onInterceptTouchEvent(MotionEvent event) { 182 | return true; 183 | } 184 | 185 | @Override 186 | public boolean onTouchEvent(MotionEvent event) { 187 | super.onTouchEvent(event); 188 | 189 | handleTouchEventForDrag(event); 190 | 191 | return true; 192 | } 193 | 194 | private void handleTouchEventForDrag(MotionEvent event) { 195 | if (imageView.getDrawable() == null) { 196 | return; 197 | } 198 | if (velocityTracker == null) { 199 | velocityTracker = VelocityTracker.obtain(); 200 | } 201 | velocityTracker.addMovement(event); 202 | 203 | switch (event.getAction()) { 204 | case MotionEvent.ACTION_DOWN: { 205 | activePointerId = event.getPointerId(event.getActionIndex()); 206 | currentX = event.getX(); 207 | currentY = event.getY(); 208 | snappyDragHelper.onTouch(); 209 | break; 210 | } 211 | 212 | case MotionEvent.ACTION_MOVE: { 213 | float x = event.getX(); 214 | float y = event.getY(); 215 | 216 | if (activePointerId == event.getPointerId(event.getActionIndex())) { 217 | if ((state == STATE_IDLE && inImageView(x, y)) || state == STATE_DRAGGING) { 218 | snappyDragHelper.onMove(currentX, currentY, x, y); 219 | setState(STATE_DRAGGING); 220 | } 221 | 222 | currentX = x; 223 | currentY = y; 224 | } 225 | break; 226 | } 227 | 228 | case MotionEvent.ACTION_POINTER_UP: 229 | case MotionEvent.ACTION_UP: 230 | case MotionEvent.ACTION_CANCEL: 231 | if (activePointerId == event.getPointerId(event.getActionIndex())) { 232 | int xvel = 0; 233 | int yvel = 0; 234 | if (velocityTracker != null) { 235 | velocityTracker.computeCurrentVelocity(1000, maxVelocity); 236 | xvel = (int) velocityTracker.getXVelocity(activePointerId); 237 | yvel = (int) velocityTracker.getYVelocity(activePointerId); 238 | 239 | velocityTracker.recycle(); 240 | velocityTracker = null; 241 | } 242 | if (state == STATE_DRAGGING) { 243 | snappyDragHelper.onRelease(xvel, yvel); 244 | setState(STATE_SETTLING); 245 | } 246 | activePointerId = INVALID_POINTER; 247 | } 248 | break; 249 | } 250 | } 251 | 252 | private boolean inImageView(float x, float y) { 253 | return initialImageRect.left <= x && x <= initialImageRect.right 254 | && initialImageRect.top <= y && y <= initialImageRect.bottom; 255 | } 256 | 257 | private void setState(int state) { 258 | this.state = state; 259 | } 260 | 261 | private void notifyClosed() { 262 | for (OnClosedListener listener : onClosedListeners) { 263 | listener.onClosed(); 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library', ':demo' 2 | --------------------------------------------------------------------------------