├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── samples ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mabeijianxi │ │ └── samples │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mabeijianxi │ │ │ └── samples │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-xhdpi │ │ └── red_bg.9.png │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── includeview.xml │ │ └── item.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 │ └── test │ └── java │ └── com │ └── mabeijianxi │ └── simple │ └── ExampleUnitTest.java ├── settings.gradle ├── simple.apk └── stickyDotsLib ├── .gitignore ├── build.gradle ├── libs └── nineoldandroids-2.4.0.jar ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── mabeijianxi │ └── stickydotslib │ └── ApplicationTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── mabeijianxi │ │ └── stickydotslib │ │ ├── utils │ │ ├── DisplayUtils.java │ │ └── GeometryUtil.java │ │ └── view │ │ ├── StickyView.java │ │ └── StickyViewHelper.java └── res │ ├── drawable │ └── out_anim.xml │ └── mipmap-xhdpi │ ├── pop1.png │ ├── pop2.png │ ├── pop3.png │ ├── pop4.png │ └── pop5.png └── test └── java └── com └── mabeijianxi └── stickydots └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | 10 | # Android Studio Navigation editor temp files 11 | .navigation/ 12 | 13 | # Java class files 14 | *.class 15 | 16 | # Log Files 17 | *.log 18 | 19 | # Files for the Dalvik VM 20 | *.dex 21 | 22 | # Generated files 23 | bin/ 24 | gen/ 25 | 26 | # Gradle files 27 | .gradle/ 28 | build/ 29 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | stickyDots -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Android 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 64 | 65 | 66 | Android API 19 Platform 67 | 68 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## stickyDots 2 | #####类似QQ的小红点,QQ can drag the little red dot. 3 | 更多介绍[http://blog.csdn.net/mabeijianxi/article/details/50560361](http://blog.csdn.net/mabeijianxi/article/details/50560361) 4 | 5 | 6 | 先看下效果图: 7 | 8 | ![demo](http://7xq6db.com1.z0.glb.clouddn.com/stickydots.gif) 9 | 10 | 11 | [Download Demo](https://github.com/mabeijianxi/stickyDots/blob/master/simple.apk) 12 | 13 | ## Usage 14 | 15 | ###Step-1 16 | 17 | ###Gradle 18 | `dependencies { 19 | compile project(':stickyDotsLib')}` 20 | 21 | --- 22 | ###Step-2 23 | Choose the right place to create it,for example: 24 | 25 | // Note for needed to achieve drag effect of view needs to 26 | 27 | // individually specify a layout file,and layout is best cannot have viewGroup, 28 | 29 | // or view shown above the text may in drag does not recognize. 30 | 31 | // This is in order to facilitate, in order to reduce consumption. 32 | 33 | StickyViewHelper stickyViewHelper = new StickyViewHelper(mContext, viewHolder.mDragView,R.layout.includeview); 34 | 35 | The layout of includeview like this: 36 | 37 | `` 38 | `` 50 | 51 | You can monitor each process of mobile: 52 | 53 | 54 | /** 55 | * view在范围内移动指此此Runnable 56 | * @param viewInRangeMoveRun 57 | */ 58 | public void setViewInRangeMoveRun(Runnable viewInRangeMoveRun) { 59 | this.viewInRangeMoveRun = viewInRangeMoveRun; 60 | } 61 | 62 | /** 63 | * view在范围外移动执行此Runnable 64 | * @param viewOutRangeMoveRun 65 | */ 66 | public void setViewOutRangeMoveRun(Runnable viewOutRangeMoveRun) { 67 | this.viewOutRangeMoveRun = viewOutRangeMoveRun; 68 | } 69 | 70 | /** 71 | * view移出过范围,最后在范围内松手执行次Runnable 72 | * @param viewOut2InRangeUpRun 73 | */ 74 | public void setViewOut2InRangeUpRun(Runnable viewOut2InRangeUpRun) { 75 | this.viewOut2InRangeUpRun = viewOut2InRangeUpRun; 76 | } 77 | 78 | /** 79 | * view没有移出过范围,在范围内松手 80 | * @param mViewInRangeUpRun 81 | */ 82 | public void setViewInRangeUpRun(Runnable mViewInRangeUpRun) { 83 | this.mViewInRangeUpRun = mViewInRangeUpRun; 84 | } 85 | 86 | /** 87 | * view移出范围,最后在范围外松手 88 | * @param viewOutRangeUpRun 89 | */ 90 | public void setViewOutRangeUpRun(Runnable viewOutRangeUpRun) { 91 | this.viewOutRangeUpRun = viewOutRangeUpRun; 92 | }` 93 | 94 | You can also specify mapping rules: 95 | 96 | 97 | 98 | /** 99 | * 设置最大拖拽范围 100 | * @param mFarthestDistance px 101 | */ 102 | public void setFarthestDistance(float mFarthestDistance) { 103 | this.mFarthestDistance = mFarthestDistance; 104 | } 105 | 106 | /** 107 | * 设置拖拽过程中固定圆变化的最小半径值 108 | * @param mMinFixRadius px 109 | */ 110 | public void setMinFixRadius(float mMinFixRadius) { 111 | this.mMinFixRadius = mMinFixRadius; 112 | } 113 | 114 | /** 115 | * 设置固定圆半径 116 | * @param mFixRadius px 117 | */ 118 | public void setFixRadius(float mFixRadius) { 119 | this.mFixRadius = mFixRadius; 120 | } 121 | 122 | /** 123 | * 设置绘制颜色 124 | * @param mPathColor 125 | */ 126 | public void setmPathColor(int mPathColor) { 127 | this.mPathColor = mPathColor; 128 | }` 129 | 130 | 131 | ##Theory 132 | 133 | ![Theory](http://7xq6db.com1.z0.glb.clouddn.com/%E7%B2%98%E6%80%A7%E6%8E%A7%E4%BB%B6.png) 134 | 135 | It can be decomposed into three parts, a fixed circle, a drag and drop round, a connection rod. 136 | According to the fingers to move the position of the drawing is ok.It need a little geometry knowledge. 137 | 138 | Draw a fixed circle: 139 | `canvas.drawCircle(mFixCanterPoint.x, mFixCanterPoint.y, mFixRadius, 140 | mPaint);` 141 | 142 | Moving to a point of tangency: 143 | `mPath.moveTo(mFixTangentPointes[0].x, mFixTangentPointes[0].y);` 144 | 145 | 146 | According to the two circles tangent point on quadratic bezier curve drawing: 147 | `mPath.quadTo(mCanterPoint.x, mCanterPoint.y, 148 | mDragTangentPoint[0].x, mDragTangentPoint[0].y);` 149 | 150 | Another point of tangency direction to draw a straight line: 151 | ` mPath.lineTo(mDragTangentPoint[1].x, mDragTangentPoint[1].y);` 152 | 153 | Is a quadratic bezier curve: 154 | ` mPath.quadTo(mCanterPoint.x, mCanterPoint.y, 155 | mFixTangentPointes[1].x, mFixTangentPointes[1].y);` 156 | 157 | Form a closed image, in fact, the end connection: 158 | ` mPath.close();` 159 | ` canvas.drawPath(mPath, mPaint);` 160 | 161 | Finally draw drag the circle is complete: 162 | `canvas.drawCircle(mDragCanterPoint.x, mDragCanterPoint.y, 163 | mDragRadius, mPaint);` 164 | 165 | 166 | About the second order bezier curve: 167 | 168 | ![](http://7xq6db.com1.z0.glb.clouddn.com/240px-Bezier_2_big.gif) 169 | 170 | It is the starting point and a form of control points according to certain algorithm 171 | 172 | ####Specific, you can download to learn more.Or see my blog for details,[http://blog.csdn.net/mabeijianxi/article/details/50560361](http://blog.csdn.net/mabeijianxi/article/details/50560361) 173 | 174 | ##有什么不足的地方欢迎大家提出,也很乐意和大家交流,我的联系邮箱是*mabeijianxi@gmail.com* 175 | -------------------------------------------------------------------------------- /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:1.5.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /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/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 21 11:34:03 PDT 2015 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.8-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 | -------------------------------------------------------------------------------- /samples/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /samples/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.mabeijianxi.simple" 9 | minSdkVersion 9 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.1' 26 | compile project(':stickyDotsLib') 27 | } 28 | -------------------------------------------------------------------------------- /samples/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 D:\android-sdk-windows/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 | -------------------------------------------------------------------------------- /samples/src/androidTest/java/com/mabeijianxi/samples/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.mabeijianxi.samples; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /samples/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/src/main/java/com/mabeijianxi/samples/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mabeijianxi.samples; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.BaseAdapter; 10 | import android.widget.ListView; 11 | import android.widget.TextView; 12 | 13 | import com.mabeijianxi.stickydotslib.utils.DisplayUtils; 14 | import com.mabeijianxi.stickydotslib.view.StickyViewHelper; 15 | 16 | import java.util.ArrayList; 17 | 18 | /** 19 | * 20 | */ 21 | public class MainActivity extends AppCompatActivity { 22 | private ListView lv; 23 | private MyAdapter myAdapter; 24 | private Context mContext=this; 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_main); 29 | lv = (ListView) findViewById(R.id.lv); 30 | myAdapter = new MyAdapter(); 31 | lv.setAdapter(myAdapter); 32 | } 33 | 34 | class MyAdapter extends BaseAdapter { 35 | /** 36 | * 用于记录需要删除的view的position 37 | */ 38 | ArrayList removeList =new ArrayList(); 39 | @Override 40 | public int getCount() { 41 | return 1000; 42 | } 43 | 44 | @Override 45 | public Object getItem(int position) { 46 | return null; 47 | } 48 | 49 | @Override 50 | public long getItemId(int position) { 51 | return 0; 52 | } 53 | 54 | @Override 55 | public View getView(final int position, View convertView, ViewGroup parent) { 56 | final ViewHolder viewHolder; 57 | if(convertView==null){ 58 | viewHolder = new ViewHolder(); 59 | convertView = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false); 60 | viewHolder.mDragView = (TextView) convertView.findViewById(R.id.mDragView); 61 | convertView.setTag(viewHolder); 62 | } 63 | else { 64 | viewHolder = (ViewHolder) convertView.getTag(); 65 | } 66 | if(removeList.contains(position)){ 67 | viewHolder.mDragView.setVisibility(View.GONE); 68 | } 69 | else { 70 | viewHolder.mDragView.setVisibility(View.VISIBLE); 71 | viewHolder.mDragView.setText(String.valueOf(position)); 72 | } 73 | /** 74 | * 注意对于需要实现拖拽效果的view需要单独指定一个布局文件,并且次布局最好不能有viewGroup, 75 | * 否则view上面显示的文字可能在拖拽时不能识别,这样一是为了方便,二是为了减少消耗 76 | * 布局方式请参考xml文件 77 | */ 78 | StickyViewHelper stickyViewHelper = new StickyViewHelper(mContext, viewHolder.mDragView,R.layout.includeview); 79 | // 如果你还想监听拖拽过程的坐标变化,你可以在创建对象的时候这样: 80 | /* new StickyViewHelper(mContext, viewHolder.mDragView,R.layout.includeview){ 81 | @Override 82 | public void inRangeMove(PointF dragCanterPoint) { 83 | super.inRangeMove(dragCanterPoint); 84 | // DoSomething 85 | 86 | } 87 | 88 | @Override 89 | public void outRangeMove(PointF dragCanterPoint) { 90 | super.outRangeMove(dragCanterPoint); 91 | // DoSomething 92 | } 93 | // ...... 94 | };*/ 95 | setViewOut2InRangeUp(stickyViewHelper); 96 | setViewOutRangeUp(position, stickyViewHelper); 97 | setViewInRangeUp(stickyViewHelper); 98 | setViewInRangeMove(stickyViewHelper); 99 | setViewOutRangeMove(stickyViewHelper); 100 | return convertView; 101 | } 102 | 103 | /** 104 | * view在范围外移动执行此Runnable 105 | * @param stickyViewHelper 106 | */ 107 | private void setViewOutRangeMove(StickyViewHelper stickyViewHelper) { 108 | stickyViewHelper.setViewOutRangeMoveRun(new Runnable() { 109 | @Override 110 | public void run() { 111 | DisplayUtils.showToast(mContext, "ViewOutRangeMove"); 112 | } 113 | }); 114 | } 115 | 116 | /** 117 | * view在范围内移动指此此Runnable 118 | * @param stickyViewHelper 119 | */ 120 | private void setViewInRangeMove(StickyViewHelper stickyViewHelper) { 121 | stickyViewHelper.setViewInRangeMoveRun(new Runnable() { 122 | @Override 123 | public void run() { 124 | DisplayUtils.showToast(mContext, "ViewInRangeMove"); 125 | } 126 | }); 127 | } 128 | 129 | /** 130 | * view没有移出过范围,在范围内松手 131 | * @param stickyViewHelper 132 | */ 133 | private void setViewInRangeUp(StickyViewHelper stickyViewHelper) { 134 | stickyViewHelper.setViewInRangeUpRun(new Runnable() { 135 | @Override 136 | public void run() { 137 | DisplayUtils.showToast(mContext, "ViewInRangeUp"); 138 | myAdapter.notifyDataSetChanged(); 139 | } 140 | }); 141 | } 142 | 143 | /** 144 | * view移出范围,最后在范围外松手 145 | * @param position 146 | * @param stickyViewHelper 147 | */ 148 | private void setViewOutRangeUp(final int position, StickyViewHelper stickyViewHelper) { 149 | stickyViewHelper.setViewOutRangeUpRun(new Runnable() { 150 | @Override 151 | public void run() { 152 | DisplayUtils.showToast(mContext, "ViewOutRangeUp"); 153 | removeList.add(position); 154 | myAdapter.notifyDataSetChanged(); 155 | } 156 | }); 157 | } 158 | 159 | /** 160 | * view移出过范围,最后在范围内松手执行次Runnable 161 | * @param stickyViewHelper 162 | */ 163 | private void setViewOut2InRangeUp(StickyViewHelper stickyViewHelper) { 164 | stickyViewHelper.setViewOut2InRangeUpRun(new Runnable() { 165 | @Override 166 | public void run() { 167 | DisplayUtils.showToast(mContext, "ViewOut2InRangeUp"); 168 | myAdapter.notifyDataSetChanged(); 169 | } 170 | }); 171 | } 172 | 173 | } 174 | static class ViewHolder{ 175 | TextView mDragView; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /samples/src/main/res/drawable-xhdpi/red_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/samples/src/main/res/drawable-xhdpi/red_bg.9.png -------------------------------------------------------------------------------- /samples/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/includeview.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/samples/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/samples/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/samples/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/samples/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/samples/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /samples/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /samples/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /samples/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Simple 3 | 4 | -------------------------------------------------------------------------------- /samples/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/src/test/java/com/mabeijianxi/simple/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.mabeijianxi.samples; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':stickyDotsLib', ':samples' 2 | -------------------------------------------------------------------------------- /simple.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/simple.apk -------------------------------------------------------------------------------- /stickyDotsLib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /stickyDotsLib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 9 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile 'com.android.support:appcompat-v7:23.1.1' 23 | compile files('libs/nineoldandroids-2.4.0.jar') 24 | } 25 | -------------------------------------------------------------------------------- /stickyDotsLib/libs/nineoldandroids-2.4.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/stickyDotsLib/libs/nineoldandroids-2.4.0.jar -------------------------------------------------------------------------------- /stickyDotsLib/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 D:\android-sdk-windows/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 | -------------------------------------------------------------------------------- /stickyDotsLib/src/androidTest/java/com/mabeijianxi/stickydotslib/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.mabeijianxi.stickydotslib; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /stickyDotsLib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /stickyDotsLib/src/main/java/com/mabeijianxi/stickydotslib/utils/DisplayUtils.java: -------------------------------------------------------------------------------- 1 | package com.mabeijianxi.stickydotslib.utils; 2 | 3 | import android.content.Context; 4 | import android.graphics.Rect; 5 | import android.support.v4.view.MotionEventCompat; 6 | import android.util.DisplayMetrics; 7 | import android.util.TypedValue; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.widget.Toast; 11 | 12 | public class DisplayUtils { 13 | 14 | public static Toast mToast; 15 | 16 | public static void showToast(Context mContext, String msg) { 17 | if (mToast == null) { 18 | mToast = Toast.makeText(mContext, "", Toast.LENGTH_SHORT); 19 | } 20 | mToast.setText(msg); 21 | mToast.show(); 22 | } 23 | 24 | /** 25 | * dip 转换成 px 26 | * @param dip 27 | * @param context 28 | * @return 29 | */ 30 | public static float dip2Dimension(float dip, Context context) { 31 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 32 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics); 33 | } 34 | /** 35 | * @param dip 36 | * @param context 37 | * @param complexUnit {@link TypedValue#COMPLEX_UNIT_DIP} {@link TypedValue#COMPLEX_UNIT_SP}} 38 | * @return 39 | */ 40 | public static float toDimension(float dip, Context context, int complexUnit) { 41 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 42 | return TypedValue.applyDimension(complexUnit, dip, displayMetrics); 43 | } 44 | 45 | /** 获取状态栏高度 46 | * @param v 47 | * @return 48 | */ 49 | public static int getStatusBarHeight(View v) { 50 | if (v == null) { 51 | return 0; 52 | } 53 | Rect frame = new Rect(); 54 | v.getWindowVisibleDisplayFrame(frame); 55 | return frame.top; 56 | } 57 | 58 | public static String getActionName(MotionEvent event) { 59 | String action = "unknow"; 60 | switch (MotionEventCompat.getActionMasked(event)) { 61 | case MotionEvent.ACTION_DOWN: 62 | action = "ACTION_DOWN"; 63 | break; 64 | case MotionEvent.ACTION_MOVE: 65 | action = "ACTION_MOVE"; 66 | break; 67 | case MotionEvent.ACTION_UP: 68 | action = "ACTION_UP"; 69 | break; 70 | case MotionEvent.ACTION_CANCEL: 71 | action = "ACTION_CANCEL"; 72 | break; 73 | case MotionEvent.ACTION_SCROLL: 74 | action = "ACTION_SCROLL"; 75 | break; 76 | case MotionEvent.ACTION_OUTSIDE: 77 | action = "ACTION_SCROLL"; 78 | break; 79 | default: 80 | break; 81 | } 82 | return action; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /stickyDotsLib/src/main/java/com/mabeijianxi/stickydotslib/utils/GeometryUtil.java: -------------------------------------------------------------------------------- 1 | package com.mabeijianxi.stickydotslib.utils; 2 | 3 | import android.graphics.PointF; 4 | 5 | /** 6 | * 几何图形工具 7 | */ 8 | public class GeometryUtil { 9 | 10 | /** 11 | * As meaning of method name. 12 | * 获得两点之间的距离 13 | * @param p0 14 | * @param p1 15 | * @return 16 | */ 17 | public static float getDistanceBetween2Points(PointF p0, PointF p1) { 18 | float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2)); 19 | return distance; 20 | } 21 | 22 | /** 23 | * Get middle point between p1 and p2. 24 | * 获得两点连线的中点 25 | * @param p1 26 | * @param p2 27 | * @return 28 | */ 29 | public static PointF getMiddlePoint(PointF p1, PointF p2) { 30 | return new PointF((p1.x + p2.x) / 2.0f, (p1.y + p2.y) / 2.0f); 31 | } 32 | 33 | /** 34 | * Get point between p1 and p2 by percent. 35 | * 根据百分比获取两点之间的某个点坐标 36 | * @param p1 37 | * @param p2 38 | * @param percent 39 | * @return 40 | */ 41 | public static PointF getPointByPercent(PointF p1, PointF p2, float percent) { 42 | return new PointF(evaluateValue(percent, p1.x , p2.x), evaluateValue(percent, p1.y , p2.y)); 43 | } 44 | 45 | /** 46 | * 根据分度值,计算从start到end中,fraction位置的值。fraction范围为0 -> 1 47 | * @param fraction 48 | * @param start 49 | * @param end 50 | * @return 51 | */ 52 | public static float evaluateValue(float fraction, Number start, Number end){ 53 | return start.floatValue() + (end.floatValue() - start.floatValue()) * fraction; 54 | } 55 | 56 | /** 57 | * Get the point of intersection between circle and line. 58 | * 获取 通过指定圆心,斜率为lineK的直线与圆的交点。 59 | * 60 | * @param pMiddle The circle center point. 61 | * @param radius The circle radius. 62 | * @param lineK The slope of line which cross the pMiddle. 63 | * @return 64 | */ 65 | public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) { 66 | PointF[] points = new PointF[2]; 67 | 68 | float radian, xOffset = 0, yOffset = 0; 69 | if(lineK != null){ 70 | 71 | radian= (float) Math.atan(lineK); 72 | xOffset = (float) (Math.cos(radian) * radius); 73 | yOffset = (float) (Math.sin(radian) * radius); 74 | }else { 75 | xOffset = radius; 76 | yOffset = 0; 77 | } 78 | points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y + yOffset); 79 | points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y - yOffset); 80 | 81 | return points; 82 | } 83 | } -------------------------------------------------------------------------------- /stickyDotsLib/src/main/java/com/mabeijianxi/stickydotslib/view/StickyView.java: -------------------------------------------------------------------------------- 1 | package com.mabeijianxi.stickydotslib.view; 2 | 3 | 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.PixelFormat; 10 | import android.graphics.PointF; 11 | import android.view.Gravity; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.view.WindowManager; 15 | import android.view.animation.OvershootInterpolator; 16 | 17 | import com.mabeijianxi.stickydotslib.utils.DisplayUtils; 18 | import com.mabeijianxi.stickydotslib.utils.GeometryUtil; 19 | import com.nineoldandroids.animation.Animator; 20 | import com.nineoldandroids.animation.ValueAnimator; 21 | 22 | /** 23 | * Created by mabeijianxi on 2016/1/19. 24 | */ 25 | public class StickyView extends View { 26 | 27 | private final Context mContext; 28 | /** 29 | * 拖拽圆到固定圆之间的距离 30 | */ 31 | private float rangeMove; 32 | /** 33 | * 拖拽的view 34 | */ 35 | private View mDragView; 36 | /** 37 | * 拖拽圆的圆心 38 | */ 39 | private PointF mDragCanterPoint = new PointF(250, 550); 40 | /** 41 | * 固定圆的圆心 42 | */ 43 | private PointF mFixCanterPoint = new PointF(250, 550); 44 | /** 45 | * 控制点 46 | */ 47 | private PointF mCanterPoint = new PointF(250, 250); 48 | /** 49 | * 固定圆的切点 50 | */ 51 | private PointF[] mFixTangentPointes = new PointF[]{new PointF(100, 190), 52 | new PointF(100, 210)}; 53 | 54 | /** 55 | * 拖拽圆的切点 56 | */ 57 | private PointF[] mDragTangentPoint = new PointF[]{new PointF(200, 180), 58 | new PointF(200, 220)}; 59 | /** 60 | * 最大拖拽范围 61 | */ 62 | private float mFarthestDistance; 63 | /** 64 | * 动画中固定员的最小半径 65 | */ 66 | private float mMinFixRadius; 67 | /** 68 | * 拖拽圆半径 69 | */ 70 | private float mDragRadius; 71 | /** 72 | * 固定圆半径 73 | */ 74 | private float mFixRadius; 75 | /** 76 | * 超出范围 77 | */ 78 | private boolean isOut; 79 | /** 80 | * 在超出范围的地方松手 81 | */ 82 | private boolean isOutUp; 83 | private float startX; 84 | private float startY; 85 | private Paint mPaint; 86 | private Path mPath; 87 | private WindowManager mWm; 88 | private int mDragViewHeight; 89 | private int mDragViewWidth; 90 | private int mStatusBarHeight; 91 | private WindowManager.LayoutParams mParams; 92 | private DragStickViewListener dragStickViewListener; 93 | 94 | public StickyView(Context context, View mDragView, WindowManager mWm) { 95 | super(context); 96 | this.mContext=context; 97 | this.mDragView = mDragView; 98 | this.mWm = mWm; 99 | init(); 100 | 101 | } 102 | 103 | private void init() { 104 | mPaint = new Paint(); 105 | mPaint.setColor(Color.RED); 106 | mPaint.setAntiAlias(true); 107 | mPath = new Path(); 108 | // 需要手动测量 109 | mDragView.measure(1, 1); 110 | mDragViewHeight = mDragView.getMeasuredHeight() / 2; 111 | mDragViewWidth = mDragView.getMeasuredWidth() / 2; 112 | mDragRadius = mDragViewHeight; 113 | 114 | mFixRadius=DisplayUtils.dip2Dimension(8,mContext); 115 | mFarthestDistance=DisplayUtils.dip2Dimension(80,mContext); 116 | mMinFixRadius=DisplayUtils.dip2Dimension(3f,mContext); 117 | 118 | mParams = new WindowManager.LayoutParams(); 119 | mParams.format = PixelFormat.TRANSLUCENT; 120 | mParams.height = WindowManager.LayoutParams.WRAP_CONTENT; 121 | mParams.width = WindowManager.LayoutParams.WRAP_CONTENT; 122 | mParams.gravity = Gravity.TOP | Gravity.LEFT; 123 | } 124 | 125 | 126 | public int getStatusBarHeight() { 127 | return mStatusBarHeight; 128 | } 129 | 130 | /** 131 | * 设置状态栏高度,最好外面传进来,当view还没有绑定到窗体的时候是测量不到的 132 | * @param mStatusBarHeight 133 | */ 134 | public void setStatusBarHeight(int mStatusBarHeight) { 135 | this.mStatusBarHeight = mStatusBarHeight; 136 | } 137 | 138 | @Override 139 | protected void onDraw(Canvas canvas) { 140 | super.onDraw(canvas); 141 | canvas.save(); 142 | // 需要去除状态栏高度偏差 143 | canvas.translate(0, -mStatusBarHeight); 144 | // 移出了范围后将不再绘制链接部分和固定圆 145 | if (!isOut) { 146 | // 根据圆心距动态绘制固定圆大小 147 | float mFixRadius = updateStickRadius(); 148 | canvas.drawCircle(mFixCanterPoint.x, mFixCanterPoint.y, mFixRadius, 149 | mPaint); 150 | // 设置控制点,这里的控制点选择的是两圆心连接成的直线的中心位置 151 | mCanterPoint.set((mDragCanterPoint.x + mFixCanterPoint.x) / 2, 152 | (mDragCanterPoint.y + mFixCanterPoint.y) / 2); 153 | // 接下来是计算两个圆的外切点,会用到一点几何知识,忘了的回去找高中老师 154 | float dy = mDragCanterPoint.y - mFixCanterPoint.y; 155 | float dx = mDragCanterPoint.x - mFixCanterPoint.x; 156 | 157 | if (dx != 0) { 158 | float k1 = dy / dx; 159 | float k2 = -1 / k1; 160 | mDragTangentPoint = GeometryUtil.getIntersectionPoints( 161 | mDragCanterPoint, mDragRadius, (double) k2); 162 | mFixTangentPointes = GeometryUtil.getIntersectionPoints( 163 | mFixCanterPoint, mFixRadius, (double) k2); 164 | } else { 165 | mDragTangentPoint = GeometryUtil.getIntersectionPoints( 166 | mDragCanterPoint, mDragRadius, (double) 0); 167 | mFixTangentPointes = GeometryUtil.getIntersectionPoints( 168 | mFixCanterPoint, mFixRadius, (double) 0); 169 | } 170 | // 必须重设上一次的路径 171 | mPath.reset(); 172 | // moveTo顾名思义就是移动到某个位置,这里移动到固定圆的第一个外切点 173 | mPath.moveTo(mFixTangentPointes[0].x, mFixTangentPointes[0].y); 174 | // quadTo是绘制二阶贝塞尔曲线,这种曲线很想ps里面画矢量路径的那种。二阶的话需要一个控制点,一个起点一个终点 175 | mPath.quadTo(mCanterPoint.x, mCanterPoint.y, 176 | mDragTangentPoint[0].x, mDragTangentPoint[0].y); 177 | // 从上一个点绘制一条直线到下面这个位置 178 | mPath.lineTo(mDragTangentPoint[1].x, mDragTangentPoint[1].y); 179 | // 再绘制一条二阶贝塞尔曲线 180 | mPath.quadTo(mCanterPoint.x, mCanterPoint.y, 181 | mFixTangentPointes[1].x, mFixTangentPointes[1].y); 182 | // 执行close,表示形成闭合路径 183 | mPath.close(); 184 | // 绘制到界面上 185 | canvas.drawPath(mPath, mPaint); 186 | 187 | } 188 | // 当在范围外松手的时候是不再绘制拖拽圆的 189 | if (!isOutUp) { 190 | canvas.drawCircle(mDragCanterPoint.x, mDragCanterPoint.y, 191 | mDragRadius, mPaint); 192 | } 193 | // 参考范围,没实际作用 194 | /*mPaint.setStyle(Paint.Style.STROKE); 195 | canvas.drawCircle(mFixCanterPoint.x, mFixCanterPoint.y, mFarthestDistance, mPaint); 196 | mPaint.setStyle(Paint.Style.FILL);*/ 197 | 198 | canvas.restore(); 199 | } 200 | 201 | /** 202 | * 设置固定圆、拖拽圆、控制点的圆心坐标 203 | * 204 | * @param x 205 | * @param y 206 | */ 207 | public void setShowCanterPoint(float x, float y) { 208 | mFixCanterPoint.set(x, y); 209 | mDragCanterPoint.set(x, y); 210 | mCanterPoint.set(x, y); 211 | invalidate(); 212 | } 213 | 214 | @Override 215 | public boolean onTouchEvent(MotionEvent event) { 216 | switch (event.getAction()) { 217 | case MotionEvent.ACTION_DOWN: 218 | isOut = false; 219 | startX = event.getRawX(); 220 | startY = event.getRawY(); 221 | updateDragCenterPoint(startX, startY); 222 | break; 223 | case MotionEvent.ACTION_MOVE: 224 | float endX = event.getRawX(); 225 | float endY = event.getRawY(); 226 | // 更加手的移动位置绘制拖拽圆的位置 227 | updateDragCenterPoint(endX, endY); 228 | distance(); 229 | // 当移出了规定的范围的时候 230 | if (rangeMove > mFarthestDistance) { 231 | isOut = true; 232 | 233 | if (dragStickViewListener != null) { 234 | dragStickViewListener.outRangeMove(mDragCanterPoint); 235 | } 236 | 237 | } else { 238 | // 不能把isOut改为false,因为移出一次后就算它移出过了 239 | // isOut=false; 240 | isOutUp = false; 241 | if (dragStickViewListener != null) { 242 | dragStickViewListener.inRangeMove(mDragCanterPoint); 243 | } 244 | 245 | } 246 | break; 247 | case MotionEvent.ACTION_UP: 248 | // 防止误操作 249 | mDragView.setEnabled(false); 250 | this.setEnabled(false); 251 | distance(); 252 | if (isOut) { 253 | outUp(); 254 | } 255 | // 没有超出,做动画 256 | else { 257 | inUp(); 258 | } 259 | invalidate(); 260 | break; 261 | } 262 | return true; 263 | } 264 | 265 | 266 | /** 267 | * 计算此时拖拽圆心到固定圆心的距离 268 | */ 269 | private void distance() { 270 | rangeMove = GeometryUtil.getDistanceBetween2Points( 271 | mFixCanterPoint, mDragCanterPoint); 272 | } 273 | 274 | /** 275 | * 移动的时候一直在范围内,最后在范围内松手 276 | */ 277 | private void inUp() { 278 | final PointF startPoint = new PointF(mDragCanterPoint.x, 279 | mDragCanterPoint.y); 280 | final PointF endPoint = new PointF(mFixCanterPoint.x, 281 | mFixCanterPoint.y); 282 | ValueAnimator animator = ValueAnimator.ofFloat(1.0f); 283 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 284 | 285 | @Override 286 | public void onAnimationUpdate(ValueAnimator animation) { 287 | float fraction = animation.getAnimatedFraction(); 288 | PointF byPercent = GeometryUtil.getPointByPercent( 289 | startPoint, endPoint, fraction); 290 | updateDragCenterPoint(byPercent.x, byPercent.y); 291 | } 292 | }); 293 | addUpEndAnimListener(animator); 294 | animator.setInterpolator(new OvershootInterpolator(4.0f)); 295 | animator.setDuration(500); 296 | animator.start(); 297 | } 298 | 299 | /** 300 | * 移动出规定范围 301 | */ 302 | private void outUp() { 303 | // 外面松手 304 | if (rangeMove > mFarthestDistance) { 305 | isOutUp = true; 306 | if (dragStickViewListener != null) { 307 | dragStickViewListener.outRangeUp(mDragCanterPoint); 308 | } 309 | } 310 | // 里面松手 311 | else { 312 | isOutUp = false; 313 | if (dragStickViewListener != null) { 314 | dragStickViewListener.out2InRangeUp(mDragCanterPoint); 315 | } 316 | } 317 | updateDragCenterPoint(mFixCanterPoint.x, mFixCanterPoint.y); 318 | } 319 | 320 | /** 321 | * 没有超出范围松手会弹后的回调 322 | * 323 | * @param animator 324 | */ 325 | private void addUpEndAnimListener(ValueAnimator animator) { 326 | animator.addListener(new Animator.AnimatorListener() { 327 | @Override 328 | public void onAnimationStart(Animator animation) { 329 | 330 | } 331 | 332 | @Override 333 | public void onAnimationEnd(Animator animation) { 334 | if (dragStickViewListener != null) { 335 | dragStickViewListener.inRangeUp(mDragCanterPoint); 336 | } 337 | } 338 | 339 | @Override 340 | public void onAnimationCancel(Animator animation) { 341 | 342 | } 343 | 344 | @Override 345 | public void onAnimationRepeat(Animator animation) { 346 | 347 | } 348 | }); 349 | } 350 | 351 | /** 352 | * 更新拖拽圆圆心 353 | */ 354 | private void updateDragCenterPoint(float x, float y) { 355 | mDragCanterPoint.set(x, y); 356 | updateManagerView(x, y); 357 | invalidate(); 358 | } 359 | 360 | private void updateManagerView(float x, float y) { 361 | mParams.x=(int)(x-mDragViewWidth); 362 | mParams.y=(int)(y-mDragViewHeight-mStatusBarHeight); 363 | try { 364 | mWm.updateViewLayout(mDragView, mParams); 365 | } 366 | catch (Exception e){ 367 | 368 | } 369 | } 370 | @Override 371 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 372 | super.onSizeChanged(w, h, oldw, oldh); 373 | /** 374 | * 获取状态栏高度 375 | */ 376 | mStatusBarHeight = DisplayUtils.getStatusBarHeight(this); 377 | 378 | } 379 | 380 | /** 381 | * 计算拖动过程中固定圆的半径 382 | */ 383 | private float updateStickRadius() { 384 | float distance = GeometryUtil.getDistanceBetween2Points(mDragCanterPoint, mFixCanterPoint); 385 | distance = Math.min(distance, mFarthestDistance); 386 | float percent = distance * 1.0f / mFarthestDistance; 387 | 388 | return mFixRadius + (mMinFixRadius - mFixRadius) * percent; 389 | } 390 | 391 | /** 392 | * 设置画笔颜色 393 | * @param mPaintColor 394 | */ 395 | public void setPaintColor(int mPaintColor) { 396 | if(mPaint!=null){ 397 | mPaint.setColor(mPaintColor); 398 | } 399 | } 400 | 401 | /** 402 | * 得到最大拖拽范围 403 | * @return 404 | */ 405 | public float getmFarthestDistance() { 406 | return mFarthestDistance; 407 | } 408 | 409 | /** 410 | * 设置最大拖拽范围 411 | * @param mFarthestDistance 412 | */ 413 | public void setFarthestDistance(float mFarthestDistance) { 414 | this.mFarthestDistance = mFarthestDistance; 415 | } 416 | public float getmMinFixRadius() { 417 | return mMinFixRadius; 418 | } 419 | 420 | /** 421 | * 设置拖拽过程中固定圆变化的最小半径值 422 | * @param mMinFixRadius 423 | */ 424 | public void setMinFixRadius(float mMinFixRadius) { 425 | this.mMinFixRadius = mMinFixRadius; 426 | } 427 | public float getmDragRadius() { 428 | return mDragRadius; 429 | } 430 | 431 | /** 432 | * 设置拖拽圆半径 433 | * @param mDragRadius 434 | */ 435 | public void setmDragRadius(float mDragRadius) { 436 | this.mDragRadius = mDragRadius; 437 | } 438 | 439 | public float getmFixRadius() { 440 | return mFixRadius; 441 | } 442 | 443 | /** 444 | * 设置固定圆半径 445 | * @param mFixRadius 446 | */ 447 | public void setFixRadius(float mFixRadius) { 448 | this.mFixRadius = mFixRadius; 449 | } 450 | /** 451 | * 拖拽过程监听接口 452 | */ 453 | public interface DragStickViewListener { 454 | /** 455 | * 在范围内移动回调 456 | * @param dragCanterPoint 拖拽的中心坐标 457 | */ 458 | void inRangeMove(PointF dragCanterPoint); 459 | /** 460 | * 在范围外移动回调 461 | * @param dragCanterPoint 拖拽的中心坐标 462 | */ 463 | void outRangeMove(PointF dragCanterPoint); 464 | /** 465 | * 当移出了规定范围,最后在范围内松手的回调 466 | * @param dragCanterPoint 467 | */ 468 | void out2InRangeUp(PointF dragCanterPoint); 469 | /** 470 | * 当移出了规定范围,最后在范围外松手的回调 471 | * @param dragCanterPoint 472 | */ 473 | void outRangeUp(PointF dragCanterPoint); 474 | /** 475 | * 一直没有移动出范围,在范围内松手的回调 476 | * @param dragCanterPoint 477 | */ 478 | void inRangeUp(PointF dragCanterPoint); 479 | } 480 | 481 | public DragStickViewListener getDragStickViewListener() { 482 | return dragStickViewListener; 483 | } 484 | 485 | public void setDragStickViewListener(DragStickViewListener dragStickViewListener) { 486 | this.dragStickViewListener = dragStickViewListener; 487 | } 488 | } 489 | -------------------------------------------------------------------------------- /stickyDotsLib/src/main/java/com/mabeijianxi/stickydotslib/view/StickyViewHelper.java: -------------------------------------------------------------------------------- 1 | package com.mabeijianxi.stickydotslib.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.PixelFormat; 5 | import android.graphics.PointF; 6 | import android.graphics.drawable.AnimationDrawable; 7 | import android.support.v4.view.MotionEventCompat; 8 | import android.view.Gravity; 9 | import android.view.LayoutInflater; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewParent; 13 | import android.view.WindowManager; 14 | import android.widget.ImageView; 15 | import android.widget.TextView; 16 | 17 | import com.mabeijianxi.stickydotslib.R; 18 | import com.mabeijianxi.stickydotslib.utils.DisplayUtils; 19 | 20 | /** 21 | * Created by mabeijianxi on 2016/1/19. 22 | * StickyView辅助类,让StickyView使用更加简单,起到桥梁的作用 23 | * 也可以更具自己需求复写某些方法 24 | */ 25 | public class StickyViewHelper implements View.OnTouchListener, StickyView.DragStickViewListener { 26 | 27 | private int dragViewLayouId; 28 | private Runnable viewInRangeMoveRun; 29 | private Runnable viewOutRangeMoveRun; 30 | private Runnable viewOut2InRangeUpRun; 31 | private Runnable viewOutRangeUpRun; 32 | private Runnable mViewInRangeUpRun; 33 | private WindowManager mWm; 34 | private WindowManager.LayoutParams mParams; 35 | private StickyView mStickyView; 36 | private View mDragView; 37 | private final Context mContext; 38 | private View mShowView; 39 | private int mStatusBarHeight; 40 | private float mMinFixRadius; 41 | private float mFixRadius; 42 | private float mFarthestDistance; 43 | private int mPathColor; 44 | 45 | 46 | public StickyViewHelper(Context mContext, View mShowView, int dragViewLayouId) { 47 | this.mContext = mContext; 48 | this.mShowView = mShowView; 49 | this.dragViewLayouId=dragViewLayouId; 50 | /** 51 | * 这步比较关键,当触摸到外部小圆点的时候会执行StickyViewHelper实现的onTouch方法 52 | */ 53 | mShowView.setOnTouchListener(this); 54 | mParams = new WindowManager.LayoutParams(); 55 | mParams.format = PixelFormat.TRANSLUCENT; 56 | 57 | } 58 | 59 | @Override 60 | public boolean onTouch(View v, MotionEvent event) { 61 | 62 | int action = MotionEventCompat.getActionMasked(event); 63 | if (action == MotionEvent.ACTION_DOWN) { 64 | ViewParent parent = v.getParent(); 65 | if (parent == null) { 66 | return false; 67 | } 68 | parent.requestDisallowInterceptTouchEvent(true); 69 | 70 | mStatusBarHeight = DisplayUtils.getStatusBarHeight(mShowView); 71 | mShowView.setVisibility(View.INVISIBLE); 72 | /** 73 | * 当手指触摸小圆点的时候这个对象将被创建,我试过不这样,直接用mShowView, 74 | * 动画做完以后WindowManager执行remove,mShowView再加添回其对应的父布局 75 | * 看着没问题,但是下次再按下这个小圆点就得不到它在屏幕上的坐标,points里面是0,0 76 | * 第一次计算的时候会产生误差。具体原因还在查询。 77 | */ 78 | mDragView = LayoutInflater.from(mContext).inflate(dragViewLayouId, null, false); 79 | // 文本内容复制 80 | copyText(); 81 | 82 | mWm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 83 | 84 | mStickyView = new StickyView(mContext, mDragView, mWm); 85 | // 初始化数据 86 | initStickyViewData(); 87 | // 注册拖拽过程的监听回调 88 | mStickyView.setDragStickViewListener(this); 89 | // 开始添加的窗体让其显示 90 | mWm.addView(mStickyView, mParams); 91 | mWm.addView(mDragView, mParams); 92 | } 93 | /** 94 | * 当执行完了以上初始操作后把事件交由StickyView处理触摸 95 | */ 96 | mStickyView.onTouchEvent(event); 97 | return true; 98 | } 99 | 100 | /** 101 | * 初始化StickyView的 102 | */ 103 | private void initStickyViewData() { 104 | // 计算小圆点在屏幕的坐标 105 | int[] points = new int[2]; 106 | mShowView.getLocationInWindow(points); 107 | int x = points[0] + mShowView.getWidth() / 2; 108 | int y = points[1] + mShowView.getHeight() / 2; 109 | // 需要外部设置,当StickyView还没有执行完dispatchAttachedToWindow()时是计算不出其高度的 110 | mStickyView.setStatusBarHeight(mStatusBarHeight); 111 | if(mFarthestDistance>0){ 112 | mStickyView.setFarthestDistance(mFarthestDistance); 113 | } 114 | if(mMinFixRadius>0){ 115 | mStickyView.setMinFixRadius(mMinFixRadius); 116 | } 117 | if(mFixRadius>0){ 118 | mStickyView.setFixRadius(mFixRadius); 119 | } 120 | if(mPathColor!=0){ 121 | mStickyView.setPaintColor(mPathColor); 122 | } 123 | // 初始化做作画的圆和控制点坐标 124 | mStickyView.setShowCanterPoint(x, y); 125 | } 126 | 127 | /** 128 | * 复制文本内容 129 | */ 130 | private void copyText() { 131 | if(mShowView instanceof TextView &&mDragView instanceof TextView){ 132 | ((TextView)mDragView).setText((((TextView) mShowView).getText().toString())); 133 | } 134 | } 135 | 136 | /** 137 | * 设置最大拖拽范围 138 | * @param mFarthestDistance px 139 | */ 140 | public void setFarthestDistance(float mFarthestDistance) { 141 | this.mFarthestDistance = mFarthestDistance; 142 | } 143 | 144 | /** 145 | * 设置拖拽过程中固定圆变化的最小半径值 146 | * @param mMinFixRadius px 147 | */ 148 | public void setMinFixRadius(float mMinFixRadius) { 149 | this.mMinFixRadius = mMinFixRadius; 150 | } 151 | 152 | /** 153 | * 设置固定圆半径 154 | * @param mFixRadius px 155 | */ 156 | public void setFixRadius(float mFixRadius) { 157 | this.mFixRadius = mFixRadius; 158 | } 159 | 160 | /** 161 | * 设置绘制颜色 162 | * @param mPathColor 163 | */ 164 | public void setmPathColor(int mPathColor) { 165 | this.mPathColor = mPathColor; 166 | } 167 | /** 168 | * 在范围内移动回调 169 | * @param dragCanterPoint 拖拽的中心坐标 170 | */ 171 | @Override 172 | public void inRangeMove(PointF dragCanterPoint) { 173 | if(viewInRangeMoveRun !=null){ 174 | viewInRangeMoveRun.run(); 175 | } 176 | } 177 | 178 | /** 179 | * 在范围外移动回调 180 | * @param dragCanterPoint 拖拽的中心坐标 181 | */ 182 | @Override 183 | public void outRangeMove(PointF dragCanterPoint) { 184 | if(viewOutRangeMoveRun !=null){ 185 | viewOutRangeMoveRun.run(); 186 | } 187 | } 188 | 189 | /** 190 | * 当移出了规定范围,最后在范围内松手的回调 191 | * @param dragCanterPoint 192 | */ 193 | @Override 194 | public void out2InRangeUp(PointF dragCanterPoint) { 195 | removeView(); 196 | if(viewOut2InRangeUpRun !=null){ 197 | viewOut2InRangeUpRun.run(); 198 | } 199 | } 200 | 201 | /** 202 | * 当移出了规定范围,最后在范围外松手的回调 203 | * @param dragCanterPoint 204 | */ 205 | @Override 206 | public void outRangeUp(PointF dragCanterPoint) { 207 | removeView(); 208 | playAnim(dragCanterPoint); 209 | } 210 | 211 | /** 212 | * 一直没有移动出范围,在范围内松手的回调 213 | * @param dragCanterPoint 214 | */ 215 | @Override 216 | public void inRangeUp(PointF dragCanterPoint) { 217 | removeView(); 218 | if(mViewInRangeUpRun !=null){ 219 | mViewInRangeUpRun.run(); 220 | } 221 | } 222 | /** 223 | * 播放移除动画(帧动画),这个过程根据个人喜好 224 | * @param dragCanterPoint 225 | */ 226 | private void playAnim(PointF dragCanterPoint) { 227 | final ImageView imageView = new ImageView(mContext); 228 | imageView.setImageResource(R.drawable.out_anim); 229 | final AnimationDrawable mAnimDrawable = (AnimationDrawable) imageView 230 | .getDrawable(); 231 | mParams.gravity= Gravity.TOP|Gravity.LEFT; 232 | // 这里得到的是其真实的大小,因为此时还得不到其测量值 233 | int intrinsicWidth = imageView.getDrawable().getIntrinsicWidth(); 234 | int intrinsicHeight = imageView.getDrawable().getIntrinsicHeight(); 235 | 236 | mParams.x= (int) dragCanterPoint.x-intrinsicWidth/2; 237 | mParams.y= (int) dragCanterPoint.y-intrinsicHeight/2-mStatusBarHeight; 238 | mParams.width=WindowManager.LayoutParams.WRAP_CONTENT; 239 | mParams.height=WindowManager.LayoutParams.WRAP_CONTENT; 240 | // 获取播放一次帧动画的总时长 241 | long duration = getAnimDuration(mAnimDrawable); 242 | 243 | mWm.addView(imageView, mParams); 244 | mAnimDrawable.start(); 245 | // 由于帧动画不能定时停止,只能采用这种办法 246 | imageView.postDelayed(new Runnable() { 247 | @Override 248 | public void run() { 249 | mAnimDrawable.stop(); 250 | imageView.clearAnimation(); 251 | mWm.removeView(imageView); 252 | if (viewOutRangeUpRun != null) { 253 | viewOutRangeUpRun.run(); 254 | } 255 | } 256 | },duration); 257 | } 258 | 259 | /** 260 | * 得到帧动画的摧毁时间 261 | * @param mAnimDrawable 262 | * @return 263 | */ 264 | private long getAnimDuration(AnimationDrawable mAnimDrawable) { 265 | long duration=0; 266 | for(int i=0;i 2 | 4 | 5 | 8 | 11 | 14 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /stickyDotsLib/src/main/res/mipmap-xhdpi/pop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/stickyDotsLib/src/main/res/mipmap-xhdpi/pop1.png -------------------------------------------------------------------------------- /stickyDotsLib/src/main/res/mipmap-xhdpi/pop2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/stickyDotsLib/src/main/res/mipmap-xhdpi/pop2.png -------------------------------------------------------------------------------- /stickyDotsLib/src/main/res/mipmap-xhdpi/pop3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/stickyDotsLib/src/main/res/mipmap-xhdpi/pop3.png -------------------------------------------------------------------------------- /stickyDotsLib/src/main/res/mipmap-xhdpi/pop4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/stickyDotsLib/src/main/res/mipmap-xhdpi/pop4.png -------------------------------------------------------------------------------- /stickyDotsLib/src/main/res/mipmap-xhdpi/pop5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabeijianxi/stickyDots/986d23763ac44e875600558ed414dba5575f6cb9/stickyDotsLib/src/main/res/mipmap-xhdpi/pop5.png -------------------------------------------------------------------------------- /stickyDotsLib/src/test/java/com/mabeijianxi/stickydots/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.mabeijianxi.stickydotslib; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } --------------------------------------------------------------------------------