├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zolad │ │ └── zoominimageview │ │ └── sample │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zolad │ │ │ └── zoominimageview │ │ │ └── sample │ │ │ ├── ImageListAdapter.java │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-nodpi │ │ ├── img_1.jpg │ │ ├── img_2.jpg │ │ ├── img_3.JPG │ │ └── img_4.jpg │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── item_image.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── zolad │ └── zoominimageview │ └── sample │ └── ExampleUnitTest.java ├── screenshot └── screenshot_1.gif ├── settings.gradle └── zoominimageview ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── zolad │ └── zoominimageview │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── zolad │ │ └── zoominimageview │ │ ├── ZoomInImageView.java │ │ ├── ZoomInImageViewAttacher.java │ │ ├── animation │ │ ├── AnimCompat.java │ │ └── SpringInterpolator.java │ │ ├── gestures │ │ ├── OnScaleAndMoveGestureListener.java │ │ └── ScaleAndMoveDetector.java │ │ └── window │ │ └── WindowManagerUtil.java └── res │ ├── layout │ └── layout_zoominimage.xml │ └── values │ └── strings.xml └── test └── java └── com └── zolad └── zoominimageview └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | gen 3 | out 4 | lib 5 | 6 | .idea 7 | *.iml 8 | classes 9 | 10 | obj 11 | 12 | .DS_Store 13 | 14 | # Gradle 15 | .gradle 16 | jniLibs 17 | build 18 | local.properties 19 | reports -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ZoomInImageView 2 | ============== 3 | A zoomable ImageView for Android, can be used in AdapterView and Recyclerview. 一个可拉伸拖动的ImageView ,可在AdapterView和RecyclerView中使用。 4 | 5 | ![screenshot1~](https://raw.github.com/zolad/ZoomInImageView/master/screenshot/screenshot_1.gif) 6 | 7 | Features 8 | ============== 9 | - Zooming, using multi-touch. 10 | - Can be used in AdapterView and Recyclerview. 11 | - Easily attach and detach ImageView. 12 | - Anim zooming After release 13 | 14 | Dependency 15 | ============== 16 | ### Add this in your build.gradle file 17 | ```gradle 18 | // appcompat-v7 is required 19 | compile 'com.zolad:zoominimageview:1.0.0' 20 | ``` 21 | 22 | 23 | Usage 24 | ============== 25 | ### 1.attach ImageView 26 | 27 | ```java 28 | 31 | ``` 32 | 33 | or 34 | 35 | ```java 36 | ZoomInImageViewAttacher mIvAttacter = new ZoomInImageViewAttacher(); 37 | mIvAttacter.attachImageView(imageview); 38 | ``` 39 | 40 | or 41 | 42 | ```java 43 | ZoomInImageViewAttacher mIvAttacter = new ZoomInImageViewAttacher(imageview); 44 | ``` 45 | 46 | ### 2.other settings 47 | 48 | set Zoomable 49 | 50 | ```java 51 | mIvAttacter.setZoomable(zoomable); 52 | ``` 53 | 54 | detach ImageView 55 | 56 | ```java 57 | mIvAttacter.detach(); 58 | ``` 59 | 60 | release anim setting 61 | 62 | ```java 63 | mIvAttacter.setZoomReleaseAnimDuration(duration); 64 | mIvAttacter.setZoomReleaseAnimInterpolator(interpolator); 65 | ``` 66 | 67 | 68 | License 69 | ============== 70 | 71 | Copyright 2018 Zolad 72 | 73 | Licensed under the Apache License, Version 2.0 (the "License"); 74 | you may not use this file except in compliance with the License. 75 | You may obtain a copy of the License at 76 | 77 | http://www.apache.org/licenses/LICENSE-2.0 78 | 79 | Unless required by applicable law or agreed to in writing, software 80 | distributed under the License is distributed on an "AS IS" BASIS, 81 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 82 | See the License for the specific language governing permissions and 83 | limitations under the License. 84 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.1.2' 11 | 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | 21 | 22 | // tasks.withType(Javadoc) { 23 | // options { 24 | // encoding "UTF-8" 25 | // charSet 'UTF-8' 26 | // links "http://docs.oracle.com/javase/7/docs/api" 27 | // } 28 | // } 29 | 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jul 06 22:24:52 CST 2018 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-4.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 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 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.zolad.zoominimageview.sample" 7 | minSdkVersion 14 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'com.zolad:zoominimageview:1.0.0' 25 | 26 | implementation 'com.android.support:appcompat-v7:28.0.0-alpha3' 27 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 28 | implementation 'com.android.support:recyclerview-v7:25.3.1' 29 | implementation 'com.github.bumptech.glide:glide:3.7.0' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 33 | 34 | } 35 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/zolad/zoominimageview/sample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview.sample; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.zolad.zoominimageview.sample", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/java/com/zolad/zoominimageview/sample/ImageListAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.zolad.zoominimageview.sample; 24 | 25 | import android.content.Context; 26 | import android.support.v7.widget.RecyclerView; 27 | import android.view.LayoutInflater; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | import android.widget.TextView; 31 | import android.widget.Toast; 32 | 33 | import com.bumptech.glide.Glide; 34 | import com.zolad.zoominimageview.ZoomInImageView; 35 | 36 | import java.util.List; 37 | 38 | public class ImageListAdapter extends RecyclerView.Adapter { 39 | 40 | private List mImgList; 41 | Context context; 42 | 43 | ImageListAdapter(Context context) { 44 | 45 | this.context = context; 46 | } 47 | 48 | 49 | 50 | 51 | @Override 52 | public ImgViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 53 | LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); 54 | View view = layoutInflater.inflate(R.layout.item_image, parent, false); 55 | 56 | 57 | ImgViewHolder viewHolder = new ImgViewHolder(view); 58 | 59 | 60 | return viewHolder; 61 | } 62 | 63 | @Override 64 | public void onBindViewHolder(ImgViewHolder holder, int position) { 65 | 66 | holder.tv_position.setText("picture"+(position+1)); 67 | 68 | /** 69 | * load image resource 70 | * */ 71 | Glide.with(context).load(mImgList.get(position)).into(holder.iv_pic); 72 | 73 | 74 | holder.iv_pic.setOnClickListener(new View.OnClickListener() { 75 | @Override 76 | public void onClick(View v) { 77 | Toast.makeText(context,"click",Toast.LENGTH_SHORT).show(); 78 | } 79 | }); 80 | } 81 | 82 | 83 | @Override 84 | public int getItemCount() { 85 | return mImgList == null ? 0 : mImgList.size(); 86 | } 87 | 88 | public void setImgList(List imgList) { 89 | mImgList = imgList; 90 | notifyDataSetChanged(); 91 | } 92 | 93 | public static class ImgViewHolder extends RecyclerView.ViewHolder { 94 | public final ZoomInImageView iv_pic; 95 | public final TextView tv_position; 96 | public ImgViewHolder(View itemView) { 97 | super(itemView); 98 | iv_pic = (ZoomInImageView) itemView.findViewById(R.id.iv_pic); 99 | tv_position = (TextView) itemView.findViewById(R.id.tv_position); 100 | 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /sample/src/main/java/com/zolad/zoominimageview/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview.sample; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.widget.ImageView; 8 | 9 | import com.zolad.zoominimageview.ZoomInImageViewAttacher; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | private ImageListAdapter mAdapter; 17 | 18 | RecyclerView mRVlist; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | mRVlist = (RecyclerView) findViewById(R.id.rv_imagelist); 26 | mRVlist.setLayoutManager(new LinearLayoutManager(this)); 27 | 28 | mRVlist.setAdapter(mAdapter = new ImageListAdapter(this)); 29 | 30 | 31 | List imglist = new ArrayList<>(); 32 | imglist.add(R.drawable.img_1); 33 | imglist.add(R.drawable.img_3); 34 | imglist.add(R.drawable.img_2); 35 | imglist.add(R.drawable.img_4); 36 | 37 | mAdapter.setImgList(imglist); 38 | 39 | 40 | mAdapter.notifyDataSetChanged(); 41 | 42 | 43 | 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/img_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/drawable-nodpi/img_1.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/img_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/drawable-nodpi/img_2.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/img_3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/drawable-nodpi/img_3.JPG -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/img_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/drawable-nodpi/img_4.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/item_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 18 | 19 | 20 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ZoomInImageView 3 | 4 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /sample/src/test/java/com/zolad/zoominimageview/sample/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview.sample; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /screenshot/screenshot_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zolad/ZoomInImageView/ecfe27215868df528aded2be6b6aec4db23a2040/screenshot/screenshot_1.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample' 2 | -------------------------------------------------------------------------------- /zoominimageview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /zoominimageview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | 4 | android { 5 | compileSdkVersion 25 6 | 7 | 8 | 9 | defaultConfig { 10 | minSdkVersion 8 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | lintOptions { 27 | abortOnError false 28 | } 29 | 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | 35 | compileOnly 'com.android.support:appcompat-v7:25.1.0' 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 38 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /zoominimageview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /zoominimageview/src/androidTest/java/com/zolad/zoominimageview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.zolad.zoominimageview.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /zoominimageview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /zoominimageview/src/main/java/com/zolad/zoominimageview/ZoomInImageView.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.ImageView; 6 | 7 | /** 8 | * A zoomable ImageView for Android, can be used in AdapterView and Recyclerview. 9 | * */ 10 | public class ZoomInImageView extends ImageView { 11 | 12 | 13 | ZoomInImageViewAttacher attacher; 14 | 15 | public ZoomInImageView(Context context) { 16 | this(context,null); 17 | 18 | } 19 | 20 | public ZoomInImageView(Context context, AttributeSet attrs) { 21 | this(context, attrs, 0); 22 | 23 | } 24 | 25 | 26 | public ZoomInImageView(Context context, AttributeSet attrs, int defStyleAttr) { 27 | super(context, attrs, defStyleAttr); 28 | 29 | attacher = new ZoomInImageViewAttacher(this); 30 | 31 | 32 | 33 | 34 | } 35 | 36 | 37 | 38 | 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /zoominimageview/src/main/java/com/zolad/zoominimageview/ZoomInImageViewAttacher.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Color; 6 | import android.graphics.Matrix; 7 | import android.graphics.PixelFormat; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.MotionEvent; 11 | import android.view.ScaleGestureDetector; 12 | import android.view.View; 13 | import android.view.ViewTreeObserver; 14 | import android.view.WindowManager; 15 | import android.view.animation.Interpolator; 16 | import android.widget.ImageView; 17 | 18 | import com.zolad.zoominimageview.animation.AnimCompat; 19 | import com.zolad.zoominimageview.animation.SpringInterpolator; 20 | import com.zolad.zoominimageview.gestures.OnScaleAndMoveGestureListener; 21 | import com.zolad.zoominimageview.gestures.ScaleAndMoveDetector; 22 | import com.zolad.zoominimageview.window.WindowManagerUtil; 23 | 24 | import java.lang.ref.WeakReference; 25 | 26 | public class ZoomInImageViewAttacher implements View.OnTouchListener { 27 | 28 | private WindowManager mWindowManager; 29 | private WindowManager.LayoutParams mWindowLayoutParams; 30 | /** 31 | * drag imageview 32 | */ 33 | private ImageView mZoomIV; 34 | /** 35 | * drag image Bitmap 36 | */ 37 | private Bitmap mZoomBitmap; 38 | 39 | private int mOffsetToTop; 40 | 41 | 42 | private int mOffsetToLeft; 43 | 44 | 45 | /** 46 | * is zoomable 47 | */ 48 | private boolean mZoomEnabled = true; 49 | 50 | 51 | private ScaleAndMoveDetector mScaleGestureDetector; 52 | 53 | private int ZOOM_DURATION = 1000; 54 | 55 | private View mWindowLayout; 56 | private Matrix mSuppMatrix = new Matrix(); 57 | private Matrix mBaseMatrix = new Matrix(); 58 | private Matrix mDrawMatrix = new Matrix(); 59 | private Interpolator mAnimInterpolator = new SpringInterpolator(1f); 60 | 61 | 62 | private boolean hasInterruptParentNotToHandleTouchEvent = false; 63 | 64 | private WeakReference mImageView; 65 | 66 | 67 | public ZoomInImageViewAttacher() { 68 | 69 | } 70 | 71 | public void attachImageView(ImageView imageView) { 72 | 73 | if (imageView == null) { 74 | 75 | throw new NullPointerException("imageview is null"); 76 | 77 | } 78 | 79 | 80 | mImageView = new WeakReference(imageView); 81 | 82 | 83 | imageView.setOnTouchListener(this); 84 | 85 | 86 | initGestureDectector(); 87 | 88 | } 89 | 90 | 91 | public ZoomInImageViewAttacher(ImageView imageView) { 92 | 93 | 94 | attachImageView(imageView); 95 | 96 | 97 | } 98 | 99 | 100 | public synchronized void initGestureDectector() { 101 | 102 | 103 | mScaleGestureDetector = new ScaleAndMoveDetector(getImageView().getContext(), new OnScaleAndMoveGestureListener() { 104 | 105 | 106 | @Override 107 | public void onScaleAndMove(ScaleGestureDetector detector, float currentScale, float moveDistanceFromX, float moveDistanceFromY) { 108 | 109 | 110 | if (mZoomIV == null) { 111 | 112 | mScaleGestureDetector.setStartScale(detector.getScaleFactor()); 113 | return; 114 | } 115 | 116 | 117 | mSuppMatrix.reset(); 118 | 119 | 120 | float centerX = mOffsetToLeft + getImageView().getWidth() / 2.0f; 121 | float centerY = mOffsetToTop + getImageView().getHeight() / 2.0f; 122 | 123 | 124 | if (currentScale >= 1.0) { 125 | mSuppMatrix.postScale(currentScale, currentScale, centerX, centerY); 126 | 127 | 128 | } else { 129 | currentScale = 1; 130 | mSuppMatrix.postScale(1, 1, centerX, centerY); 131 | 132 | } 133 | 134 | mSuppMatrix.postTranslate(moveDistanceFromX, moveDistanceFromY); 135 | 136 | 137 | if (mWindowLayout != null) { 138 | if (currentScale >= 1.0) { 139 | 140 | if (currentScale > 3.0) { 141 | mWindowLayout.setBackgroundColor(Color.argb((int) 200, 0, 0, 0)); 142 | } else { 143 | mWindowLayout.setBackgroundColor(Color.argb((int) (200 * (currentScale - 1.0) / 2.0f), 0, 0, 0)); 144 | } 145 | } else { 146 | mWindowLayout.setBackgroundColor(Color.argb(0, 0, 0, 0)); 147 | } 148 | 149 | } 150 | 151 | 152 | setImageViewMatrix(getDrawMatrix()); 153 | 154 | 155 | } 156 | 157 | @Override 158 | public void onScaleBegin(ScaleGestureDetector detector) { 159 | 160 | 161 | if (mZoomBitmap == null) { 162 | 163 | getImageView().setDrawingCacheEnabled(true); 164 | 165 | mZoomBitmap = Bitmap.createBitmap(getImageView().getDrawingCache()); 166 | 167 | getImageView().destroyDrawingCache(); 168 | 169 | } 170 | createZoomImage(mZoomBitmap, mOffsetToLeft, mOffsetToTop); 171 | 172 | } 173 | 174 | @Override 175 | public void onScaleEnd(ScaleGestureDetector detector, float moveDistanceFromX, float moveDistanceFromY) { 176 | 177 | 178 | onReleaseZoom(moveDistanceFromX, moveDistanceFromY); 179 | 180 | } 181 | 182 | 183 | }); 184 | 185 | 186 | } 187 | 188 | public void setZoomReleaseAnimInterpolator(Interpolator animInterpolator) { 189 | 190 | if (animInterpolator != null) 191 | this.mAnimInterpolator = animInterpolator; 192 | } 193 | 194 | public void setZoomReleaseAnimDuration(int duration) { 195 | 196 | if (duration > 0) 197 | this.ZOOM_DURATION = duration; 198 | } 199 | 200 | 201 | public void setZoomable(boolean zoomable) { 202 | mZoomEnabled = zoomable; 203 | 204 | } 205 | 206 | 207 | public void detach() { 208 | 209 | clean(); 210 | 211 | } 212 | 213 | 214 | private void clean() { 215 | 216 | if (null != mImageView) { 217 | 218 | if (null != mImageView.get()) { 219 | 220 | mImageView.get().setOnTouchListener(null); 221 | } 222 | mImageView = null; 223 | } 224 | 225 | 226 | mWindowLayoutParams = null; 227 | mWindowLayout = null; 228 | mWindowManager = null; 229 | 230 | mScaleGestureDetector = null; 231 | 232 | recycleZoomBitmap(); 233 | } 234 | 235 | /** 236 | * On release zoom 237 | */ 238 | private void onReleaseZoom(float disx, float disy) { 239 | 240 | if (getImageView() == null) 241 | return; 242 | 243 | if (mZoomIV != null) { 244 | float centerx = mOffsetToLeft + mZoomBitmap.getWidth() / 2.0f; 245 | float centery = mOffsetToTop + mZoomBitmap.getHeight() / 2.0f; 246 | mZoomIV.post(new AnimatedZoomRunnable(getScale(), 1, 247 | centerx, centery, disx, disy)); 248 | } 249 | } 250 | 251 | 252 | /** 253 | * remove zoom layout 254 | */ 255 | private void removeZoomImage() { 256 | 257 | hasInterruptParentNotToHandleTouchEvent = false; 258 | 259 | if (mZoomIV != null) { 260 | mZoomIV.setVisibility(View.INVISIBLE); 261 | 262 | if (mWindowManager != null && mWindowLayout != null) { 263 | WindowManagerUtil.removeViewSafety(mWindowManager, mWindowLayout); 264 | } 265 | 266 | 267 | mZoomIV = null; 268 | recycleZoomBitmap(); 269 | } 270 | 271 | } 272 | 273 | 274 | private void recycleZoomBitmap() { 275 | 276 | if (null != mZoomBitmap && !mZoomBitmap.isRecycled()) { 277 | mZoomBitmap.recycle(); 278 | mZoomBitmap = null; 279 | } 280 | 281 | } 282 | 283 | 284 | /** 285 | * @param bitmap 286 | * @param mOffsetToLeft 287 | * @param mOffsetToTop 288 | */ 289 | private synchronized void createZoomImage(Bitmap bitmap, int mOffsetToLeft, int mOffsetToTop) { 290 | 291 | 292 | if (getImageView() == null) 293 | return; 294 | 295 | if (mWindowLayoutParams == null || mWindowLayout == null) { 296 | mWindowLayoutParams = new WindowManager.LayoutParams(); 297 | mWindowLayoutParams.format = PixelFormat.RGBA_8888; 298 | 299 | 300 | mWindowLayoutParams.alpha = 1f; 301 | mWindowLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; 302 | mWindowLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; 303 | 304 | 305 | mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 306 | 307 | 308 | mWindowLayout = LayoutInflater.from(getImageView().getContext()).inflate(R.layout.layout_zoominimage, null); 309 | } 310 | 311 | mWindowLayout.setClickable(true); 312 | 313 | mZoomIV = (ImageView) mWindowLayout.findViewById(R.id.iv_zoominpic); 314 | mZoomIV.setVisibility(View.VISIBLE); 315 | mZoomIV.setImageBitmap(bitmap); 316 | 317 | 318 | mBaseMatrix.reset(); 319 | mSuppMatrix.reset(); 320 | 321 | mBaseMatrix.postTranslate(mOffsetToLeft, 322 | mOffsetToTop); 323 | 324 | mDrawMatrix.set(mBaseMatrix); 325 | 326 | mZoomIV.setScaleType(ImageView.ScaleType.MATRIX); 327 | 328 | setImageViewMatrix(mDrawMatrix); 329 | 330 | 331 | mWindowLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 332 | @Override 333 | public void onGlobalLayout() { 334 | 335 | if (mImageView.get() != null) { 336 | getImageView().postDelayed(new Runnable() { 337 | @Override 338 | public void run() { 339 | getImageView().setVisibility(View.INVISIBLE); 340 | } 341 | }, 300); 342 | 343 | } 344 | } 345 | }); 346 | 347 | Log.e("ivd", "show drag image"); 348 | 349 | if (mWindowManager == null) { 350 | mWindowManager = (WindowManager) getImageView().getContext().getSystemService(Context.WINDOW_SERVICE); 351 | } 352 | 353 | WindowManagerUtil.addViewSafety(mWindowManager, mWindowLayout, mWindowLayoutParams); 354 | 355 | } 356 | 357 | 358 | public Matrix getDrawMatrix() { 359 | mDrawMatrix.set(mBaseMatrix); 360 | mDrawMatrix.postConcat(mSuppMatrix); 361 | return mDrawMatrix; 362 | } 363 | 364 | private void setImageViewMatrix(Matrix matrix) { 365 | 366 | if (null != mZoomIV) { 367 | 368 | 369 | mZoomIV.setImageMatrix(matrix); 370 | 371 | 372 | } 373 | } 374 | 375 | @Override 376 | public boolean onTouch(View view, MotionEvent event) { // 377 | 378 | 379 | if (mZoomEnabled) { 380 | 381 | if (event.getPointerCount() >= 2) { 382 | 383 | 384 | //if touch by more than two finger ,handle by itself 385 | 386 | if (!hasInterruptParentNotToHandleTouchEvent) { 387 | view.getParent().requestDisallowInterceptTouchEvent(true); 388 | hasInterruptParentNotToHandleTouchEvent = true; 389 | } 390 | 391 | } else { 392 | 393 | hasInterruptParentNotToHandleTouchEvent = false; 394 | view.getParent().requestDisallowInterceptTouchEvent(false); 395 | 396 | 397 | } 398 | } 399 | 400 | switch (event.getAction()) { 401 | case MotionEvent.ACTION_DOWN: 402 | 403 | 404 | mOffsetToTop = (int) (event.getRawY() - event.getY()); 405 | mOffsetToLeft = (int) (event.getRawX() - event.getX()); 406 | 407 | 408 | break; 409 | case MotionEvent.ACTION_MOVE: 410 | 411 | break; 412 | case MotionEvent.ACTION_UP: 413 | 414 | break; 415 | case MotionEvent.ACTION_CANCEL: 416 | 417 | break; 418 | case MotionEvent.ACTION_OUTSIDE: 419 | 420 | break; 421 | default: 422 | break; 423 | } 424 | 425 | 426 | boolean handled = false; 427 | 428 | 429 | if (mZoomEnabled && null != mScaleGestureDetector 430 | && mScaleGestureDetector.onTouchEvent(event)) { 431 | handled = true; 432 | } 433 | 434 | 435 | if (view.onTouchEvent(event)) { 436 | handled = true; 437 | } 438 | 439 | return handled; 440 | } 441 | 442 | 443 | public ImageView getImageView() { 444 | 445 | if (mImageView != null) 446 | return mImageView.get(); 447 | else { 448 | 449 | clean(); 450 | return null; 451 | } 452 | } 453 | 454 | 455 | public float getScale() { 456 | //FloatMath 457 | return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2)); 458 | } 459 | 460 | private final float[] mMatrixValues = new float[9]; 461 | 462 | private float getValue(Matrix matrix, int whichValue) { 463 | matrix.getValues(mMatrixValues); 464 | return mMatrixValues[whichValue]; 465 | } 466 | 467 | 468 | private class AnimatedZoomRunnable implements Runnable { 469 | 470 | private final float mFocalX, mFocalY; 471 | private final long mStartTime; 472 | private final float mZoomStartScale, mZoomEndScale; 473 | private final float mTranslateDistanceX, mTranslateDistanceY; 474 | 475 | 476 | public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, 477 | final float focalX, final float focalY, final float translateDistanceX, final float translateDistanceY) { 478 | mFocalX = focalX; 479 | mFocalY = focalY; 480 | mStartTime = System.currentTimeMillis(); 481 | mZoomStartScale = currentZoom; 482 | mZoomEndScale = targetZoom; 483 | mTranslateDistanceX = translateDistanceX; 484 | mTranslateDistanceY = translateDistanceY; 485 | 486 | mWindowLayout.setBackgroundColor(Color.argb(0, 0, 0, 0)); 487 | 488 | } 489 | 490 | 491 | @Override 492 | public void run() { 493 | ImageView imageView = mZoomIV; 494 | if (imageView == null) { 495 | return; 496 | } 497 | float time = 1f * (System.currentTimeMillis() - mStartTime) / ZOOM_DURATION; 498 | float t = mAnimInterpolator.getInterpolation(time); 499 | float scales = mZoomStartScale + t * (mZoomEndScale - mZoomStartScale); 500 | 501 | mSuppMatrix.reset(); 502 | 503 | 504 | mSuppMatrix.postScale(scales, scales, mFocalX, mFocalY); 505 | 506 | float x = mTranslateDistanceX + t * (0 - mTranslateDistanceX); 507 | float y = mTranslateDistanceY + t * (0 - mTranslateDistanceY); 508 | 509 | mSuppMatrix.postTranslate(x, y); 510 | 511 | setImageViewMatrix(getDrawMatrix()); 512 | 513 | float stopTime = 1f; 514 | 515 | if (mAnimInterpolator instanceof SpringInterpolator) { 516 | 517 | stopTime = 0.8f; 518 | 519 | 520 | } 521 | 522 | 523 | if (time < stopTime) { 524 | AnimCompat.postOnAnimation(imageView, this); 525 | 526 | 527 | } else { 528 | 529 | getImageView().post(new Runnable() { 530 | @Override 531 | public void run() { 532 | getImageView().setVisibility(View.VISIBLE); 533 | removeZoomImage(); 534 | } 535 | }); 536 | 537 | } 538 | 539 | 540 | } 541 | 542 | } 543 | 544 | 545 | } 546 | -------------------------------------------------------------------------------- /zoominimageview/src/main/java/com/zolad/zoominimageview/animation/AnimCompat.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview.animation; 2 | 3 | import android.os.Build; 4 | import android.view.View; 5 | 6 | /** 7 | * A Compat tool for view.postOnAnimation 8 | */ 9 | 10 | public class AnimCompat { 11 | 12 | private static final int SIXTY_FPS_INTERVAL = 1000 / 60; 13 | 14 | public static void postOnAnimation(View view, Runnable runnable) { 15 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 16 | view.postOnAnimation(runnable); 17 | } else { 18 | view.postDelayed(runnable, SIXTY_FPS_INTERVAL); 19 | 20 | } 21 | } 22 | 23 | 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /zoominimageview/src/main/java/com/zolad/zoominimageview/animation/SpringInterpolator.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview.animation; 2 | 3 | import android.view.animation.Interpolator; 4 | 5 | /** 6 | * A Spring Interpolator 7 | */ 8 | 9 | public class SpringInterpolator implements Interpolator { 10 | 11 | private float factor; 12 | 13 | public SpringInterpolator(float factor) { 14 | this.factor = factor; 15 | } 16 | 17 | @Override 18 | public float getInterpolation(float input) { 19 | 20 | return (float) (Math.pow(2, -10 * input) * Math.sin((input - factor / 4) * (2 * Math.PI) / factor) + 1); 21 | } 22 | 23 | 24 | 25 | 26 | 27 | } -------------------------------------------------------------------------------- /zoominimageview/src/main/java/com/zolad/zoominimageview/gestures/OnScaleAndMoveGestureListener.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview.gestures; 2 | 3 | import android.view.ScaleGestureDetector; 4 | 5 | public interface OnScaleAndMoveGestureListener { 6 | 7 | void onScaleBegin(ScaleGestureDetector detector); 8 | 9 | void onScaleEnd(ScaleGestureDetector detector, float moveDistanceFromX, float moveDistanceFromY); 10 | 11 | void onScaleAndMove(ScaleGestureDetector detector, float currentScale, float moveDistanceFromX, float moveDistanceFromY); 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /zoominimageview/src/main/java/com/zolad/zoominimageview/gestures/ScaleAndMoveDetector.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview.gestures; 2 | 3 | import android.content.Context; 4 | import android.view.MotionEvent; 5 | import android.view.ScaleGestureDetector; 6 | 7 | public class ScaleAndMoveDetector implements ScaleGestureDetector.OnScaleGestureListener{ 8 | 9 | private float scaleStart = 1; 10 | private float LastX = 0; 11 | private float LastY = 0; 12 | private float moveDisFromX = 0f; 13 | private float moveDisFromY = 0f; 14 | private float mCurrentScale = 1f; 15 | 16 | private final ScaleGestureDetector mDetector; 17 | private OnScaleAndMoveGestureListener mDetectorListener; 18 | 19 | 20 | public ScaleAndMoveDetector(Context context,OnScaleAndMoveGestureListener detectorListener) { 21 | 22 | mDetector = new ScaleGestureDetector(context,this); 23 | 24 | mDetectorListener = detectorListener; 25 | } 26 | 27 | @Override 28 | public boolean onScale(ScaleGestureDetector detector) { 29 | 30 | 31 | float tempx = detector.getFocusX(); 32 | float tempy = detector.getFocusY(); 33 | moveDisFromX = tempx - LastX; 34 | moveDisFromY = tempy - LastY; 35 | if(mDetectorListener!=null) 36 | mDetectorListener.onScaleAndMove(detector,getCurrentScale(detector.getScaleFactor()),moveDisFromX,moveDisFromY); 37 | return false; 38 | } 39 | 40 | @Override 41 | public boolean onScaleBegin(ScaleGestureDetector detector) { 42 | LastX = detector.getFocusX(); 43 | LastY = detector.getFocusY(); 44 | 45 | moveDisFromX = 0; 46 | moveDisFromY = 0; 47 | 48 | scaleStart = detector.getScaleFactor(); 49 | 50 | if(mDetectorListener!=null) 51 | mDetectorListener.onScaleBegin(detector); 52 | return true; 53 | } 54 | 55 | @Override 56 | public void onScaleEnd(ScaleGestureDetector detector) { 57 | 58 | if(mDetectorListener!=null) 59 | mDetectorListener.onScaleEnd(detector,moveDisFromX,moveDisFromY); 60 | 61 | } 62 | 63 | public void setStartScale(float scaleFactor){ 64 | 65 | scaleStart = scaleFactor; 66 | } 67 | 68 | 69 | private float getCurrentScale(float scaleFactor){ 70 | 71 | mCurrentScale = scaleFactor * (1f / scaleStart); 72 | return mCurrentScale; 73 | 74 | 75 | } 76 | 77 | 78 | public boolean onTouchEvent(MotionEvent ev) { 79 | return mDetector.onTouchEvent(ev); 80 | } 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /zoominimageview/src/main/java/com/zolad/zoominimageview/window/WindowManagerUtil.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview.window; 2 | 3 | import android.os.Build; 4 | import android.os.Looper; 5 | import android.support.annotation.NonNull; 6 | import android.view.View; 7 | import android.view.WindowManager; 8 | 9 | 10 | /** 11 | * A Util for WindowManager run safety 12 | */ 13 | 14 | 15 | public class WindowManagerUtil { 16 | 17 | 18 | public static synchronized void removeViewSafety(@NonNull WindowManager windowManager, @NonNull View viewNeedRemove) { 19 | 20 | 21 | if (windowManager == null || viewNeedRemove == null) 22 | return; 23 | 24 | 25 | if (Looper.myLooper() != Looper.getMainLooper()) { 26 | // Current thread is not the UI/Main thread 27 | 28 | return; 29 | 30 | } 31 | 32 | 33 | // Check is the view attaching 34 | if (isAttachedToWindow(viewNeedRemove)) { 35 | 36 | try { 37 | windowManager.removeView(viewNeedRemove); 38 | 39 | } catch (Exception e) { 40 | 41 | } 42 | 43 | return; 44 | } 45 | 46 | try { 47 | windowManager.removeView(viewNeedRemove); 48 | 49 | 50 | } catch (Exception e) { 51 | 52 | } 53 | 54 | } 55 | 56 | 57 | public static synchronized void addViewSafety(@NonNull WindowManager windowManager, @NonNull View viewNeedAdd, @NonNull WindowManager.LayoutParams params) { 58 | 59 | 60 | if (windowManager == null || viewNeedAdd == null || params == null) 61 | return; 62 | 63 | 64 | if (Looper.myLooper() != Looper.getMainLooper()) { 65 | // Current thread is not the UI/Main thread 66 | 67 | return; 68 | 69 | } 70 | 71 | 72 | if (!isAttachedToWindow(viewNeedAdd)) { 73 | 74 | try { 75 | windowManager.addView(viewNeedAdd, params); 76 | 77 | } catch (Exception e) { 78 | 79 | } 80 | 81 | 82 | } 83 | 84 | 85 | } 86 | 87 | /** 88 | * Check view is attach to window 89 | */ 90 | public static boolean isAttachedToWindow(View view) { 91 | 92 | boolean isAlreadyAttach; 93 | 94 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 95 | 96 | isAlreadyAttach = view.isAttachedToWindow(); 97 | 98 | } else { 99 | 100 | isAlreadyAttach = (view.getWindowToken() != null); 101 | 102 | } 103 | 104 | return isAlreadyAttach; 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /zoominimageview/src/main/res/layout/layout_zoominimage.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /zoominimageview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | zoominimageview 3 | 4 | -------------------------------------------------------------------------------- /zoominimageview/src/test/java/com/zolad/zoominimageview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.zolad.zoominimageview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } --------------------------------------------------------------------------------