├── .idea ├── .name ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── vcs.xml ├── modules.xml ├── runConfigurations.xml ├── compiler.xml ├── gradle.xml └── misc.xml ├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.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 │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── china │ │ │ │ └── leo │ │ │ │ └── stickloadingview │ │ │ │ ├── MainActivity.java │ │ │ │ └── widget │ │ │ │ ├── LoadingView.java │ │ │ │ └── CustomLoadingView.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── china │ │ │ └── leo │ │ │ └── stickloadingview │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── china │ │ └── leo │ │ └── stickloadingview │ │ └── ApplicationTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── keys └── leo.jks ├── leo.jks.enc ├── loading.gif ├── smaller.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .travis.yml ├── README.md ├── gradle.properties ├── gradlew.bat └── gradlew /.idea/.name: -------------------------------------------------------------------------------- 1 | StickLoadingView -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /leo.jks 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /keys/leo.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/keys/leo.jks -------------------------------------------------------------------------------- /leo.jks.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/leo.jks.enc -------------------------------------------------------------------------------- /loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/loading.gif -------------------------------------------------------------------------------- /smaller.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/smaller.gif -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | StickLoadingView 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinXiaoTao/StickLoadingView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | /gradle.properties 10 | /keys/leo.jks 11 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 26 16:24:07 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #2a80b9 7 | #ffffff 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/china/leo/stickloadingview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.china.leo.stickloadingview; 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 | { 12 | @Test 13 | public void addition_isCorrect() throws Exception 14 | { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/china/leo/stickloadingview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.china.leo.stickloadingview; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase 10 | { 11 | public ApplicationTest() 12 | { 13 | super(Application.class); 14 | } 15 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | sudo: false 3 | android: 4 | components: 5 | - tools 6 | - platform-tools 7 | - build-tools-24.0.0 8 | - extra-android-m2repository 9 | - android-24 10 | jdk: 11 | - oraclejdk8 12 | before_install: 13 | - openssl aes-256-cbc -K $encrypted_19f97a59b04f_key -iv $encrypted_19f97a59b04f_iv 14 | -in leo.jks.enc -out keys/leo.jks -d 15 | - gem install fir-cli 16 | script: 17 | - ./gradlew assembleRelease 18 | after_success: 19 | - fir p app/build/outputs/apk/app-release.apk -T $FIR_TOKEN -c "`git cat-file tag $TRAVIS_TAG`" 20 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### 参考文章 2 | ##### 效果图来源: [dribbble](https://dribbble.com/shots/2049051-Preloader) 3 | 1. [使用贝塞尔曲线绘制圆](http://www.jianshu.com/p/791d3a791ec2) 4 | 5 | ************************************************************************************ 6 | 7 | ##### 原图如下: 8 | ![原图](https://github.com/LinXiaoTao/StickLoadingView/blob/master/loading.gif) 9 | ##### 实现的效果图: 10 | ![实现效果图](https://github.com/LinXiaoTao/StickLoadingView/blob/master/smaller.gif) 11 | 12 | ************************************************************************************ 13 | 14 | ##### TODO 15 | 1. ~~大圆的抖动效果~~ 16 | 2. 贝塞尔曲线不够精细 17 | 18 | [简书地址](http://www.jianshu.com/p/4a022f9bb121) 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/china/leo/stickloadingview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.china.leo.stickloadingview; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | import com.china.leo.stickloadingview.widget.CustomLoadingView; 7 | 8 | public class MainActivity extends AppCompatActivity 9 | { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) 13 | { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | } 17 | 18 | @Override 19 | protected void onResume() 20 | { 21 | ((CustomLoadingView)findViewById(R.id.loadingView)).start(); 22 | super.onResume(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/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 /home/leo/work/android-sdk-linux/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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.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/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 19 | KEYSTORE_PASS=444164 20 | ALIAS_NAME=leo 21 | ALIAS_PASS=444164 -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | import static java.lang.System.* 2 | 3 | apply plugin: 'com.android.application' 4 | 5 | android { 6 | compileSdkVersion 24 7 | buildToolsVersion "24.0.0" 8 | 9 | defaultConfig { 10 | applicationId "com.china.leo.stickloadingview" 11 | minSdkVersion 19 12 | targetSdkVersion 23 13 | versionCode 1 14 | versionName "1.0" 15 | } 16 | 17 | signingConfigs { 18 | release { 19 | storeFile file('../keys/leo.jks') 20 | storePassword project.hasProperty("KEYSTORE_PASS") ? KEYSTORE_PASS : getenv('KEYSTORE_PASS') 21 | keyAlias project.hasProperty("ALIAS_NAME") ? ALIAS_NAME : getenv('ALIAS_NAME') 22 | keyPassword project.hasProperty("ALIAS_PASS") ? ALIAS_PASS : getenv('ALIAS_PASS') 23 | } 24 | } 25 | 26 | buildTypes { 27 | release { 28 | signingConfig signingConfigs.release 29 | minifyEnabled false 30 | // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | 34 | } 35 | 36 | dependencies { 37 | compile fileTree(include: ['*.jar'], dir: 'libs') 38 | testCompile 'junit:junit:4.12' 39 | compile 'com.orhanobut:logger:1.15' 40 | compile 'com.android.support:appcompat-v7:24.2.0' 41 | } 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 51 | 56 | 57 | 58 | 59 | 60 | 61 | 1.8 62 | 63 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/com/china/leo/stickloadingview/widget/LoadingView.java: -------------------------------------------------------------------------------- 1 | package com.path.leo.myapplication.widget; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ValueAnimator; 7 | import android.content.Context; 8 | import android.graphics.Canvas; 9 | import android.graphics.Paint; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | import android.view.animation.LinearInterpolator; 13 | 14 | import com.orhanobut.logger.Logger; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | * leo linxiaotao1993@vip.qq.com 21 | * Created on 16-8-23 下午5:47 22 | */ 23 | public class LoadingView extends View 24 | { 25 | private Paint mPaint; 26 | /** 线条长度 */ 27 | private int mLineLength = dp2px(getContext(), 40); 28 | /** 线条当前长度 */ 29 | private float mCurrentLength; 30 | /** 当前width,height */ 31 | private int mWidth = 0; 32 | private int mHeight = 0; 33 | /** 起始角度 */ 34 | private int mCanvasAngle = 60; 35 | /** 绘制圆的半径 */ 36 | private int mCireleRadius; 37 | /** 绘制圆的距离 */ 38 | private float mCircleY; 39 | /** 线条颜色 */ 40 | private int[] mColors = new int[]{0xB07ECBDA, 0xB0E6A92C, 0xB0D6014D, 0xB05ABA94}; 41 | /** 当前步骤 */ 42 | private int mCurrentStep = STEP_ONE; 43 | /** 当前状态 */ 44 | private int mCurrentStatus = STATUS_NORNAL; 45 | /** 执行的动画集合 */ 46 | private List mAnimatorList; 47 | 48 | //静止状态 49 | private static final int STATUS_NORNAL = 0; 50 | //loading状态 51 | private static final int STATUS_LOADING = 1; 52 | 53 | private static final int STEP_COUNT = 4; 54 | /** 第一步 */ 55 | private static final int STEP_ONE = 0; 56 | /** 第二步 */ 57 | private static final int STEP_TWO = 1; 58 | /** 第三步 */ 59 | private static final int STEP_THREE = 2; 60 | /** 第四步 */ 61 | private static final int STEP_FOUR = 3; 62 | /** 每次旋转角度 */ 63 | private static final int ROTATE_ANGLE = 90; 64 | /** 默认起始旋转角度 */ 65 | private static final int ROTATE_DEFALUT = 60; 66 | /** 默认动画执行时间 */ 67 | private static final int DURATION_DEFALUT = 500; 68 | 69 | 70 | public LoadingView(Context context) 71 | { 72 | this(context, null); 73 | } 74 | 75 | public LoadingView(Context context, AttributeSet attrs) 76 | { 77 | this(context, attrs, 0); 78 | } 79 | 80 | public LoadingView(Context context, AttributeSet attrs, int defStyle) 81 | { 82 | super(context, attrs, defStyle); 83 | init(); 84 | } 85 | 86 | @Override 87 | protected void onSizeChanged(int w, int h, int oldw, int oldh) 88 | { 89 | Logger.d("w = %d,h = %d,oldw = %d,oldh = %d", w, h, oldw, oldh); 90 | mWidth = w; 91 | mHeight = h; 92 | super.onSizeChanged(w, h, oldw, oldh); 93 | } 94 | 95 | @Override 96 | protected void onDraw(Canvas canvas) 97 | { 98 | drawLine(canvas, mCurrentStep % STEP_COUNT); 99 | super.onDraw(canvas); 100 | } 101 | 102 | /** 开始 */ 103 | public void start() 104 | { 105 | if (mCurrentStatus == STATUS_NORNAL) 106 | { 107 | mCurrentStatus = STATUS_LOADING; 108 | cancelAnim(); 109 | startAnimOne(); 110 | } 111 | } 112 | 113 | /** 停止 */ 114 | public void stop() 115 | { 116 | if (mCurrentStatus == STATUS_LOADING) 117 | { 118 | mCurrentStatus = STATUS_NORNAL; 119 | cancelAnim(); 120 | initData(); 121 | } 122 | } 123 | 124 | /** 清理 */ 125 | public void clean() 126 | { 127 | cancelAnim(); 128 | } 129 | 130 | /** 是否已经开始 */ 131 | public boolean isStart() 132 | { 133 | return mCurrentStatus == STATUS_LOADING; 134 | } 135 | 136 | //////////////////////////////////////////////////////////////////////////////////////////////// 137 | // 138 | // private method 139 | // 140 | //////////////////////////////////////////////////////////////////////////////////////////////// 141 | 142 | /** 初始化 */ 143 | private void init() 144 | { 145 | initView(); 146 | initData(); 147 | } 148 | 149 | /** 初始化View */ 150 | private void initView() 151 | { 152 | if (mPaint == null) 153 | { 154 | mPaint = new Paint(); 155 | mPaint.setAntiAlias(true); 156 | mPaint.setStrokeCap(Paint.Cap.ROUND); 157 | mPaint.setStrokeWidth(48f); 158 | } 159 | } 160 | 161 | /** 初始化参数 */ 162 | private void initData() 163 | { 164 | mCircleY = 0; 165 | mCurrentStep = STEP_ONE; 166 | mCurrentStatus = STATUS_NORNAL; 167 | mAnimatorList = new ArrayList<>(); 168 | mCanvasAngle = ROTATE_DEFALUT; 169 | mLineLength = dp2px(getContext(), 50); 170 | mCurrentLength = mLineLength; 171 | mCireleRadius = mLineLength / 5; 172 | } 173 | 174 | /** 取消动画 */ 175 | private void cancelAnim() 176 | { 177 | if (!mAnimatorList.isEmpty()) 178 | { 179 | for (Animator animator : mAnimatorList) 180 | { 181 | if (animator.isRunning()) 182 | animator.cancel(); 183 | } 184 | mAnimatorList.clear(); 185 | } 186 | } 187 | 188 | /** 绘制线条 */ 189 | private void drawLine(Canvas canvas, int step) 190 | { 191 | float startX = mWidth / 2 - mLineLength / 2f; 192 | float startY = mHeight / 2 - mLineLength; 193 | float endX = startX; 194 | float endY = mHeight / 2 + mLineLength; 195 | switch (step) 196 | { 197 | case STEP_ONE: 198 | for (int i = 0; i < mColors.length; i++) 199 | { 200 | mPaint.setColor(mColors[i]); 201 | canvas.rotate(mCanvasAngle + ROTATE_ANGLE * i, mWidth / 2, mHeight / 2); 202 | canvas.drawLine(startX, mHeight / 2 - mCurrentLength, endX, endY, mPaint); 203 | canvas.rotate(-(mCanvasAngle + ROTATE_ANGLE * i), mWidth / 2, mHeight / 2); 204 | } 205 | break; 206 | case STEP_TWO: 207 | for (int i = 0; i < mColors.length; i++) 208 | { 209 | mPaint.setColor(mColors[i]); 210 | canvas.rotate(mCanvasAngle + ROTATE_ANGLE * i, mWidth / 2, mHeight / 2); 211 | canvas.drawCircle(endX, endY, mCireleRadius, mPaint); 212 | canvas.rotate(-(mCanvasAngle + ROTATE_ANGLE * i), mWidth / 2, mHeight / 2); 213 | } 214 | break; 215 | case STEP_THREE: 216 | for (int i = 0; i < mColors.length; i++) 217 | { 218 | mPaint.setColor(mColors[i]); 219 | canvas.rotate(mCanvasAngle + ROTATE_ANGLE * i, mWidth / 2, mHeight / 2); 220 | canvas.drawCircle(startX, mHeight / 2 + mCircleY, mCireleRadius, mPaint); 221 | canvas.rotate(-(mCanvasAngle + ROTATE_ANGLE * i), mWidth / 2, mHeight / 2); 222 | } 223 | break; 224 | case STEP_FOUR: 225 | for (int i = 0; i < mColors.length; i++) 226 | { 227 | mPaint.setColor(mColors[i]); 228 | canvas.rotate(mCanvasAngle + ROTATE_ANGLE * i, mWidth / 2, mHeight / 2); 229 | canvas.drawLine(startX, endY, endX, mHeight / 2 + mCurrentLength, mPaint); 230 | canvas.rotate(-(mCanvasAngle + ROTATE_ANGLE * i), mWidth / 2, mHeight / 2); 231 | } 232 | break; 233 | } 234 | } 235 | 236 | /** 启动动画第一步 */ 237 | private void startAnimOne() 238 | { 239 | ValueAnimator canvasRotateAnim = ValueAnimator.ofInt(ROTATE_DEFALUT + 0, ROTATE_DEFALUT + 360); 240 | canvasRotateAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 241 | { 242 | @Override 243 | public void onAnimationUpdate(ValueAnimator animation) 244 | { 245 | mCanvasAngle = (int) animation.getAnimatedValue(); 246 | } 247 | }); 248 | 249 | ValueAnimator lineHeightAnim = ValueAnimator.ofFloat(mLineLength, -mLineLength); 250 | lineHeightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 251 | { 252 | @Override 253 | public void onAnimationUpdate(ValueAnimator animation) 254 | { 255 | mCurrentLength = (float) animation.getAnimatedValue(); 256 | invalidate(); 257 | } 258 | }); 259 | 260 | AnimatorSet animatorSet = new AnimatorSet(); 261 | animatorSet.play(canvasRotateAnim).with(lineHeightAnim); 262 | animatorSet.setInterpolator(new LinearInterpolator()); 263 | animatorSet.setDuration(DURATION_DEFALUT); 264 | animatorSet.addListener(new AnimatorListenerAdapter() 265 | { 266 | @Override 267 | public void onAnimationEnd(Animator animation) 268 | { 269 | Logger.d("第一个动画结束"); 270 | if (mCurrentStatus == STATUS_LOADING) 271 | { 272 | startAnimTwo(); 273 | mCurrentStep++; 274 | } 275 | super.onAnimationEnd(animation); 276 | } 277 | }); 278 | 279 | if (mCurrentStatus == STATUS_LOADING) 280 | { 281 | mAnimatorList.add(animatorSet); 282 | animatorSet.start(); 283 | } 284 | } 285 | 286 | /** 启动动画第二步 */ 287 | private void startAnimTwo() 288 | { 289 | ValueAnimator canvasRotateAnim = ValueAnimator.ofInt(mCanvasAngle, mCanvasAngle + 180); 290 | canvasRotateAnim.setInterpolator(new LinearInterpolator()); 291 | canvasRotateAnim.setDuration(DURATION_DEFALUT); 292 | canvasRotateAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 293 | { 294 | @Override 295 | public void onAnimationUpdate(ValueAnimator animation) 296 | { 297 | mCanvasAngle = (int) animation.getAnimatedValue(); 298 | invalidate(); 299 | } 300 | }); 301 | canvasRotateAnim.addListener(new AnimatorListenerAdapter() 302 | { 303 | 304 | @Override 305 | public void onAnimationEnd(Animator animation) 306 | { 307 | Logger.d("第二个动画结束"); 308 | if (mCurrentStatus == STATUS_LOADING) 309 | { 310 | mCurrentStep++; 311 | startAnimThree(); 312 | } 313 | super.onAnimationEnd(animation); 314 | } 315 | }); 316 | if (mCurrentStatus == STATUS_LOADING) 317 | { 318 | canvasRotateAnim.start(); 319 | mAnimatorList.add(canvasRotateAnim); 320 | } 321 | } 322 | 323 | /** 启动动画第三步 */ 324 | private void startAnimThree() 325 | { 326 | ValueAnimator canvasRotateAnim = ValueAnimator.ofInt(mCanvasAngle + 90, mCanvasAngle + 180); 327 | canvasRotateAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 328 | { 329 | @Override 330 | public void onAnimationUpdate(ValueAnimator animation) 331 | { 332 | mCanvasAngle = (int) animation.getAnimatedValue(); 333 | } 334 | }); 335 | 336 | ValueAnimator circleAnim = ValueAnimator.ofFloat(mLineLength, mLineLength / 4, mLineLength); 337 | circleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 338 | { 339 | @Override 340 | public void onAnimationUpdate(ValueAnimator animation) 341 | { 342 | mCircleY = (float) animation.getAnimatedValue(); 343 | invalidate(); 344 | } 345 | }); 346 | 347 | AnimatorSet animatorSet = new AnimatorSet(); 348 | animatorSet.play(canvasRotateAnim).with(circleAnim); 349 | animatorSet.setDuration(DURATION_DEFALUT * 2); 350 | animatorSet.setInterpolator(new LinearInterpolator()); 351 | animatorSet.addListener(new AnimatorListenerAdapter() 352 | { 353 | @Override 354 | public void onAnimationEnd(Animator animation) 355 | { 356 | Logger.d("第三个动画结束"); 357 | if (mCurrentStatus == STATUS_LOADING) 358 | { 359 | mCurrentStep++; 360 | startAnimFour(); 361 | } 362 | super.onAnimationEnd(animation); 363 | } 364 | }); 365 | if (mCurrentStatus == STATUS_LOADING) 366 | { 367 | mAnimatorList.add(animatorSet); 368 | animatorSet.start(); 369 | } 370 | } 371 | 372 | /** 启动动画第四步 */ 373 | private void startAnimFour() 374 | { 375 | ValueAnimator lineAnim = ValueAnimator.ofFloat(mLineLength, -mLineLength); 376 | lineAnim.setDuration(DURATION_DEFALUT); 377 | lineAnim.setInterpolator(new LinearInterpolator()); 378 | lineAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 379 | { 380 | @Override 381 | public void onAnimationUpdate(ValueAnimator animation) 382 | { 383 | mCurrentLength = (float) animation.getAnimatedValue(); 384 | invalidate(); 385 | } 386 | }); 387 | lineAnim.addListener(new AnimatorListenerAdapter() 388 | { 389 | @Override 390 | public void onAnimationEnd(Animator animation) 391 | { 392 | Logger.d("第四个动画结束"); 393 | if (mCurrentStatus == STATUS_LOADING) 394 | { 395 | mCurrentStep++; 396 | startAnimOne(); 397 | } 398 | super.onAnimationEnd(animation); 399 | } 400 | }); 401 | 402 | if (mCurrentStatus == STATUS_LOADING) 403 | { 404 | mAnimatorList.add(lineAnim); 405 | lineAnim.start(); 406 | } 407 | } 408 | 409 | /** dp==>px */ 410 | private int dp2px(Context context, float dp) 411 | { 412 | final float scale = context.getResources().getDisplayMetrics().density; 413 | return (int) (dp * scale + 0.5f); 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /app/src/main/java/com/china/leo/stickloadingview/widget/CustomLoadingView.java: -------------------------------------------------------------------------------- 1 | package com.china.leo.stickloadingview.widget; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.support.v4.content.ContextCompat; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | import android.view.animation.AccelerateInterpolator; 14 | import android.view.animation.DecelerateInterpolator; 15 | import android.view.animation.LinearInterpolator; 16 | 17 | import com.china.leo.stickloadingview.R; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * leo linxiaotao1993@vip.qq.com 24 | * Created on 16-8-24 下午9:58 25 | */ 26 | public class CustomLoadingView extends View 27 | { 28 | private Paint mPaint; 29 | /** 当前状态 */ 30 | private int mCurrentStatus; 31 | /** 中心圆的半径 */ 32 | private float mCircleRadius; 33 | /** 小圆的半径 */ 34 | private float mSmallRadius; 35 | private float mCenterDistance; 36 | /** 圆之间的起始距离 */ 37 | private int mStartDistance; 38 | /** 第一个小圆和中心圆最远距离 */ 39 | private float mMaxDistance; 40 | /** 是否需要绘制Path曲线 */ 41 | private boolean mIsPath; 42 | /** 当前绘制的Path */ 43 | private Path mPath; 44 | /** 绘制Path的左边距离 */ 45 | private float mPathDistance; 46 | /** 绘制Path的最小距离 */ 47 | private float mMinPathDistance; 48 | /** 两个小圆之间的距离 */ 49 | private float mDoubleDistance; 50 | /** 当前所需要绘制的小圆 */ 51 | private int mCurrentCount = CIRCLE_COUNT; 52 | /** View的宽 */ 53 | private int mWidth; 54 | /** View的高 */ 55 | private int mHeight; 56 | /** 保存的动画列表 */ 57 | private List mAnimatorList; 58 | /** 画布移动值 */ 59 | private float mTranslateValue; 60 | /** 是否开始translate动画 */ 61 | private boolean mIsTranslate; 62 | /** 当前绘制步数 */ 63 | private int mCurrentStep = STEP_ONE; 64 | /** 动画持续时间 */ 65 | private int mDuration = DURATION_DEFAULT; 66 | /** 大圆抖动动画时间 */ 67 | private int mTranslateDuration = TRANSLATE_DEFALT; 68 | 69 | /** 默认动画时间 */ 70 | private static final int DURATION_DEFAULT = 1250; 71 | private static final int TRANSLATE_DEFALT = 250; 72 | /** 绘制第一步 */ 73 | private static final int STEP_ONE = 0; 74 | /** 绘制第二步 */ 75 | private static final int STEP_TWO = 1; 76 | /** 绘制第三步 */ 77 | private static final int STEP_THREE = 2; 78 | /** 绘制第四步 */ 79 | private static final int STEP_FOUR = 3; 80 | /** 贝塞尔曲线改变值 */ 81 | private static final int PATH_CHANGE_VAL = 5; 82 | /** 大圆大小改变值 */ 83 | private static final int CIRCLE_VAL = 15; 84 | /** 小圆的数量 */ 85 | private static final int CIRCLE_COUNT = 3; 86 | /** 正常状态 */ 87 | private static final int STATUS_NORNAL = 0; 88 | /** loading状态 */ 89 | private static final int STATUS_LOADING = 1; 90 | /** 通过贝塞尔曲线绘制圆 */ 91 | private static final float CIRCLE_VALUE = 0.551915024494f; 92 | /** 线条颜色 */ 93 | private final static int[] COLORS = new int[]{0xFF7ECBDA, 0xFFE6A92C, 0xFFF0A0A5, 0xFF5ABA94}; 94 | 95 | public CustomLoadingView(Context context) 96 | { 97 | this(context, null); 98 | } 99 | 100 | public CustomLoadingView(Context context, AttributeSet attrs) 101 | { 102 | this(context, attrs, 0); 103 | } 104 | 105 | public CustomLoadingView(Context context, AttributeSet attrs, int defStyle) 106 | { 107 | super(context, attrs, defStyle); 108 | init(); 109 | } 110 | 111 | @Override 112 | protected void onDraw(Canvas canvas) 113 | { 114 | super.onDraw(canvas); 115 | 116 | drawCircle(canvas, mCurrentStep); 117 | } 118 | 119 | @Override 120 | protected void onSizeChanged(int w, int h, int oldw, int oldh) 121 | { 122 | mWidth = w; 123 | mHeight = h; 124 | super.onSizeChanged(w, h, oldw, oldh); 125 | } 126 | 127 | /** 开始 */ 128 | public void start() 129 | { 130 | if (mCurrentStatus == STATUS_NORNAL) 131 | { 132 | mCurrentStatus = STATUS_LOADING; 133 | circleOneAnim(); 134 | } 135 | } 136 | 137 | /** 停止 */ 138 | public void stop() 139 | { 140 | if (mCurrentStatus == STATUS_LOADING) 141 | { 142 | mCurrentStatus = STATUS_NORNAL; 143 | cancelAnim(); 144 | initData(); 145 | } 146 | } 147 | 148 | /** 当前loading状态 */ 149 | public boolean isStart() 150 | { 151 | return mCurrentStatus == STATUS_LOADING; 152 | } 153 | 154 | //////////////////////////////////////////////////////////////////////////////////////////////// 155 | // 156 | // private method 157 | // 158 | //////////////////////////////////////////////////////////////////////////////////////////////// 159 | 160 | /** 初始化 */ 161 | private void init() 162 | { 163 | initView(); 164 | initData(); 165 | } 166 | 167 | /** 初始化View */ 168 | private void initView() 169 | { 170 | if (mPaint == null) 171 | { 172 | mPaint = new Paint(); 173 | mPaint.setAntiAlias(true); 174 | mPaint.setColor(ContextCompat.getColor(getContext(), R.color.loading_color)); 175 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 176 | } 177 | 178 | if (mPath == null) 179 | { 180 | mPath = new Path(); 181 | } 182 | } 183 | 184 | /** 初始化参数 */ 185 | private void initData() 186 | { 187 | if (mAnimatorList == null) 188 | { 189 | mAnimatorList = new ArrayList<>(); 190 | } else 191 | { 192 | mAnimatorList.clear(); 193 | } 194 | mIsTranslate = false; 195 | mTranslateValue = 0; 196 | mTranslateDuration = TRANSLATE_DEFALT; 197 | mDuration = DURATION_DEFAULT; 198 | mCurrentStep = STEP_ONE; 199 | mCurrentCount = CIRCLE_COUNT; 200 | mIsPath = false; 201 | mCurrentStatus = STATUS_NORNAL; 202 | mCircleRadius = dp2px(getContext(), 20); 203 | mSmallRadius = mCircleRadius / 2; 204 | mStartDistance = dp2px(getContext(), 20); 205 | mMaxDistance = (mStartDistance + 2 * mSmallRadius) * mCurrentCount + mSmallRadius; 206 | mCenterDistance = mMaxDistance; 207 | mMinPathDistance = (mCircleRadius + mStartDistance + mSmallRadius) / 4 * 3; 208 | mPathDistance = 0; 209 | mDoubleDistance = mSmallRadius * 2 + mStartDistance; 210 | } 211 | 212 | /** 取消所有动画 */ 213 | private void cancelAnim() 214 | { 215 | if (!mAnimatorList.isEmpty()) 216 | { 217 | for (Animator animator : mAnimatorList) 218 | { 219 | animator.cancel(); 220 | } 221 | 222 | mAnimatorList.clear(); 223 | } 224 | } 225 | 226 | /** 中心的圆,需要绘制1个大圆,3个小圆 */ 227 | private void drawCircle(Canvas canvas, int currentStep) 228 | { 229 | float endX = 0f; 230 | float endY = 0f; 231 | float startX = 0f; 232 | float startY = 0f; 233 | 234 | //绘制大圆 235 | drawCirclePath(canvas, mPathDistance); 236 | 237 | switch (currentStep) 238 | { 239 | case STEP_ONE: 240 | endX = mWidth / 2 - mCenterDistance; 241 | endY = mHeight / 2; 242 | for (int i = 0; i < mCurrentCount; i++) 243 | { 244 | canvas.drawCircle(endX + (i * mDoubleDistance), endY, mSmallRadius, mPaint); 245 | } 246 | break; 247 | case STEP_TWO: 248 | startX = mWidth / 2 + mCenterDistance; 249 | startY = mHeight / 2; 250 | for (int i = 0; i < mCurrentCount; i++) 251 | { 252 | canvas.drawCircle(startX - i * mDoubleDistance, startY, mSmallRadius, mPaint); 253 | } 254 | break; 255 | case STEP_THREE: 256 | endX = mWidth / 2 + mCenterDistance; 257 | endY = mHeight / 2; 258 | for (int i = 0; i < mCurrentCount; i++) 259 | { 260 | canvas.drawCircle(endX - (i * mDoubleDistance), endY, mSmallRadius, mPaint); 261 | } 262 | break; 263 | case STEP_FOUR: 264 | startX = mWidth / 2 - mCenterDistance; 265 | startY = mHeight / 2; 266 | for (int i = 0; i < mCurrentCount; i++) 267 | { 268 | canvas.drawCircle(startX + i * mDoubleDistance, startY, mSmallRadius, mPaint); 269 | } 270 | break; 271 | } 272 | } 273 | 274 | /** 通过贝塞尔曲线画圆 */ 275 | private void drawCirclePath(Canvas canvas, float distance) 276 | { 277 | float m = mCircleRadius * CIRCLE_VALUE; 278 | 279 | CirclePoint p1 = new CirclePoint(mWidth / 2, mHeight / 2 - mCircleRadius); 280 | CirclePoint p2 = new CirclePoint(mWidth / 2 + m, mHeight / 2 - mCircleRadius); 281 | CirclePoint p3 = new CirclePoint(mWidth / 2 + mCircleRadius, mHeight / 2 - m); 282 | CirclePoint p4 = new CirclePoint(mWidth / 2 + mCircleRadius, mHeight / 2); 283 | 284 | CirclePoint p5 = new CirclePoint(mWidth / 2 + mCircleRadius, mHeight / 2 + m); 285 | CirclePoint p6 = new CirclePoint(mWidth / 2 + m, mHeight / 2 + mCircleRadius); 286 | CirclePoint p7 = new CirclePoint(mWidth / 2, mHeight / 2 + mCircleRadius); 287 | 288 | CirclePoint p8 = new CirclePoint(mWidth / 2 - m, mHeight / 2 + mCircleRadius); 289 | CirclePoint p9 = new CirclePoint(mWidth / 2 - mCircleRadius, mHeight / 2 + m); 290 | CirclePoint p10 = new CirclePoint(mWidth / 2 - mCircleRadius, mHeight / 2); 291 | 292 | CirclePoint p11 = new CirclePoint(mWidth / 2 - mCircleRadius, mHeight / 2 - m); 293 | CirclePoint p12 = new CirclePoint(mWidth / 2 - m, mHeight / 2 - mCircleRadius); 294 | 295 | 296 | if (mIsPath) 297 | { 298 | if (distance > 0) 299 | { 300 | p1.y += PATH_CHANGE_VAL; 301 | p2.y = p1.y; 302 | p3.x += distance; 303 | p4.x = p3.x; 304 | p5.x = p3.x; 305 | p6.y -= PATH_CHANGE_VAL; 306 | p7.y = p6.y; 307 | p8.y = p6.y; 308 | p9.x -= PATH_CHANGE_VAL / 2; 309 | p10.x = p9.x; 310 | p11.x = p9.x; 311 | p12.y = p1.y; 312 | 313 | } else 314 | { 315 | p1.y += PATH_CHANGE_VAL; 316 | p2.y = p1.y; 317 | p3.x += PATH_CHANGE_VAL / 2; 318 | p4.x = p3.x; 319 | p5.x = p3.x; 320 | p6.y -= PATH_CHANGE_VAL; 321 | p7.y = p6.y; 322 | p8.y = p6.y; 323 | p9.x += distance; 324 | p10.x = p9.x; 325 | p11.x = p9.x; 326 | p12.y = p1.y; 327 | } 328 | } else 329 | { 330 | p1.x += mTranslateValue; 331 | p7.x += mTranslateValue; 332 | if (mTranslateValue > 0) 333 | { 334 | p2.x += mTranslateValue; 335 | p3.x += mTranslateValue; 336 | p4.x += mTranslateValue; 337 | p5.x += mTranslateValue; 338 | p6.x += mTranslateValue; 339 | 340 | p8.x += mTranslateValue / 6; 341 | p9.x += mTranslateValue / 6; 342 | p10.x += mTranslateValue / 6; 343 | p11.x += mTranslateValue / 6; 344 | p12.x += mTranslateValue / 6; 345 | 346 | } else 347 | { 348 | p2.x += mTranslateValue / 5; 349 | p3.x += mTranslateValue / 5; 350 | p4.x += mTranslateValue / 5; 351 | p5.x += mTranslateValue / 5; 352 | p6.x += mTranslateValue / 5; 353 | 354 | p8.x += mTranslateValue; 355 | p9.x += mTranslateValue; 356 | p10.x += mTranslateValue; 357 | p11.x += mTranslateValue; 358 | p12.x += mTranslateValue; 359 | } 360 | 361 | 362 | } 363 | 364 | mPath.reset(); 365 | mPath.moveTo(p1.x, p1.y); 366 | mPath.cubicTo(p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); 367 | mPath.cubicTo(p5.x, p5.y, p6.x, p6.y, p7.x, p7.y); 368 | mPath.cubicTo(p8.x, p8.y, p9.x, p9.y, p10.x, p10.y); 369 | mPath.cubicTo(p11.x, p11.y, p12.x, p2.y, p1.x, p1.y); 370 | canvas.drawPath(mPath, mPaint); 371 | } 372 | 373 | /** dp==>px */ 374 | private int dp2px(Context context, float dp) 375 | { 376 | final float scale = context.getResources().getDisplayMetrics().density; 377 | // Logger.d(scale); 378 | return (int) (dp * scale + 0.5f); 379 | } 380 | 381 | /** 第一个动画 */ 382 | private void circleOneAnim() 383 | { 384 | ValueAnimator translationAnim = ValueAnimator.ofFloat(mMaxDistance, 0f); 385 | translationAnim.setInterpolator(new AccelerateInterpolator()); 386 | translationAnim.setDuration(mDuration); 387 | translationAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 388 | { 389 | @Override 390 | public void onAnimationUpdate(ValueAnimator animation) 391 | { 392 | // Logger.d(mCenterDistance); 393 | mCenterDistance = (float) animation.getAnimatedValue(); 394 | if (mCenterDistance <= mMaxDistance - mDoubleDistance && mCurrentCount == CIRCLE_COUNT) 395 | { 396 | mCurrentCount--; 397 | mCircleRadius += CIRCLE_VAL; 398 | } else if (mCenterDistance <= mMaxDistance - 2 * mDoubleDistance && mCurrentCount == CIRCLE_COUNT - 1) 399 | { 400 | mCurrentCount--; 401 | // mCircleRadius += CIRCLE_VAL; 402 | } else if (mCenterDistance <= mMaxDistance - 3 * mDoubleDistance && mCurrentCount == CIRCLE_COUNT - 2) 403 | { 404 | mCurrentCount--; 405 | // mCircleRadius += CIRCLE_VAL; 406 | } 407 | 408 | calculatePath(mCurrentStep); 409 | // Logger.d(mCenterDistance); 410 | invalidate(); 411 | } 412 | }); 413 | translationAnim.addListener(new AnimatorListenerAdapter() 414 | { 415 | @Override 416 | public void onAnimationEnd(Animator animation) 417 | { 418 | // Logger.d("动画结束"); 419 | if (mCurrentStatus == STATUS_LOADING && mCurrentStep == STEP_ONE) 420 | { 421 | mCurrentStep = STEP_TWO; 422 | circleTwoAnim(); 423 | } 424 | super.onAnimationEnd(animation); 425 | } 426 | }); 427 | if (mCurrentStatus == STATUS_LOADING) 428 | { 429 | mAnimatorList.add(translationAnim); 430 | translationAnim.start(); 431 | } 432 | } 433 | 434 | /** 第二个动画 */ 435 | private void circleTwoAnim() 436 | { 437 | ValueAnimator translationAnim = ValueAnimator.ofFloat(0f, mMaxDistance); 438 | translationAnim.setDuration(mDuration); 439 | translationAnim.setInterpolator(new DecelerateInterpolator()); 440 | translationAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 441 | { 442 | @Override 443 | public void onAnimationUpdate(ValueAnimator animation) 444 | { 445 | mCenterDistance = (float) animation.getAnimatedValue(); 446 | // Logger.d(mCenterDistance); 447 | if (mCenterDistance >= (mCircleRadius - mSmallRadius) && mCurrentCount == 0) 448 | { 449 | mCircleRadius -= CIRCLE_VAL; 450 | mCurrentCount++; 451 | } else if (mCenterDistance >= (mCircleRadius + mDoubleDistance - mSmallRadius) && mCurrentCount == 1) 452 | { 453 | mCurrentCount++; 454 | // mCircleRadius -= CIRCLE_VAL; 455 | } else if (mCenterDistance >= (mCircleRadius + mDoubleDistance * 2 - mSmallRadius) && mCurrentCount == 2) 456 | { 457 | mCurrentCount++; 458 | // mCircleRadius -= CIRCLE_VAL; 459 | // translateAnim(); 460 | } 461 | 462 | calculatePath(mCurrentStep); 463 | 464 | if (mCurrentCount == CIRCLE_COUNT && !mIsPath && !mIsTranslate) 465 | { 466 | translateAnim(mCurrentStep, 0f, 30f, 0f); 467 | } 468 | 469 | invalidate(); 470 | } 471 | }); 472 | 473 | if (mCurrentStatus == STATUS_LOADING && mCurrentStep == STEP_TWO) 474 | { 475 | mAnimatorList.add(translationAnim); 476 | translationAnim.start(); 477 | } 478 | } 479 | 480 | /** 第三个动画 */ 481 | private void circleThreeAnim() 482 | { 483 | ValueAnimator translationAnim = ValueAnimator.ofFloat(mMaxDistance, 0f); 484 | translationAnim.setDuration(mDuration); 485 | translationAnim.setInterpolator(new AccelerateInterpolator()); 486 | translationAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 487 | { 488 | @Override 489 | public void onAnimationUpdate(ValueAnimator animation) 490 | { 491 | mCenterDistance = (float) animation.getAnimatedValue(); 492 | if (mCenterDistance <= mMaxDistance - mDoubleDistance && mCurrentCount == CIRCLE_COUNT) 493 | { 494 | mCurrentCount--; 495 | mCircleRadius += CIRCLE_VAL; 496 | } else if (mCenterDistance <= mMaxDistance - 2 * mDoubleDistance && mCurrentCount == CIRCLE_COUNT - 1) 497 | { 498 | mCurrentCount--; 499 | // mCircleRadius += CIRCLE_VAL; 500 | } else if (mCenterDistance <= mMaxDistance - 3 * mDoubleDistance && mCurrentCount == CIRCLE_COUNT - 2) 501 | { 502 | mCurrentCount--; 503 | // mCircleRadius += CIRCLE_VAL; 504 | } 505 | calculatePath(mCurrentStep); 506 | // Logger.d(mCenterDistance); 507 | invalidate(); 508 | } 509 | }); 510 | translationAnim.addListener(new AnimatorListenerAdapter() 511 | { 512 | @Override 513 | public void onAnimationEnd(Animator animation) 514 | { 515 | if (mCurrentStep == STEP_THREE && mCurrentStatus == STATUS_LOADING) 516 | { 517 | mCurrentStep = STEP_FOUR; 518 | circleFourAnim(); 519 | } 520 | super.onAnimationEnd(animation); 521 | } 522 | }); 523 | 524 | if (mCurrentStep == STEP_THREE && mCurrentStatus == STATUS_LOADING) 525 | { 526 | mAnimatorList.add(translationAnim); 527 | translationAnim.start(); 528 | } 529 | } 530 | 531 | /** 第四个动画 */ 532 | private void circleFourAnim() 533 | { 534 | ValueAnimator translationAnim = ValueAnimator.ofFloat(0f, mMaxDistance); 535 | translationAnim.setDuration(mDuration); 536 | translationAnim.setInterpolator(new DecelerateInterpolator()); 537 | translationAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 538 | { 539 | @Override 540 | public void onAnimationUpdate(ValueAnimator animation) 541 | { 542 | mCenterDistance = (float) animation.getAnimatedValue(); 543 | if (mCenterDistance >= (mCircleRadius - mSmallRadius) && mCurrentCount == 0) 544 | { 545 | mCircleRadius -= CIRCLE_VAL; 546 | mCurrentCount++; 547 | } else if (mCenterDistance >= (mCircleRadius + mDoubleDistance - mSmallRadius) && mCurrentCount == 1) 548 | { 549 | mCurrentCount++; 550 | // mCircleRadius -= CIRCLE_VAL; 551 | } else if (mCenterDistance >= (mCircleRadius + mDoubleDistance * 2 - mSmallRadius) && mCurrentCount == 2) 552 | { 553 | mCurrentCount++; 554 | // mCircleRadius -= CIRCLE_VAL; 555 | } 556 | 557 | calculatePath(mCurrentStep); 558 | if (mCurrentCount == CIRCLE_COUNT && !mIsPath && !mIsTranslate) 559 | { 560 | translateAnim(mCurrentStep, 0f, -30f, 0f); 561 | } 562 | invalidate(); 563 | } 564 | }); 565 | if (mCurrentStep == STEP_FOUR && mCurrentStatus == STATUS_LOADING) 566 | { 567 | mAnimatorList.add(translationAnim); 568 | translationAnim.start(); 569 | } 570 | } 571 | 572 | /** 大圆抖动动画 */ 573 | private void translateAnim(final int step, float... valus) 574 | { 575 | final ValueAnimator roteAnim = ValueAnimator.ofFloat(valus); 576 | roteAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 577 | { 578 | @Override 579 | public void onAnimationUpdate(ValueAnimator animation) 580 | { 581 | mTranslateValue = (float) animation.getAnimatedValue(); 582 | invalidate(); 583 | } 584 | }); 585 | roteAnim.addListener(new AnimatorListenerAdapter() 586 | { 587 | @Override 588 | public void onAnimationStart(Animator animation) 589 | { 590 | mIsTranslate = true; 591 | super.onAnimationStart(animation); 592 | } 593 | 594 | @Override 595 | public void onAnimationEnd(Animator animation) 596 | { 597 | mIsTranslate = false; 598 | if (step == STEP_TWO) 599 | { 600 | mCurrentStep = STEP_THREE; 601 | circleThreeAnim(); 602 | } else if (step == STEP_FOUR) 603 | { 604 | mCurrentStep = STEP_ONE; 605 | circleOneAnim(); 606 | } 607 | mTranslateValue = 0f; 608 | super.onAnimationEnd(animation); 609 | } 610 | }); 611 | roteAnim.setDuration(mTranslateDuration); 612 | roteAnim.setInterpolator(new LinearInterpolator()); 613 | roteAnim.start(); 614 | if (mCurrentStatus == STATUS_LOADING) 615 | { 616 | mAnimatorList.add(roteAnim); 617 | postDelayed(new Runnable() 618 | { 619 | @Override 620 | public void run() 621 | { 622 | roteAnim.start(); 623 | } 624 | }, 250); 625 | } 626 | } 627 | 628 | /** 计算path */ 629 | private void calculatePath(int step) 630 | { 631 | mIsPath = false; 632 | float distance = Math.abs(mCenterDistance - (mCurrentCount - 1) * mDoubleDistance); 633 | if (mCurrentCount > 0) 634 | { 635 | switch (step) 636 | { 637 | case STEP_ONE: 638 | if (distance < (mMinPathDistance - mCenterDistance / 8)) 639 | { 640 | mPaint.setColor(COLORS[step]); 641 | mIsPath = true; 642 | mPathDistance = -distance; 643 | // Logger.d("min = %f,disatnce = %f",mMinDxistance,mPathDistance); 644 | } 645 | break; 646 | case STEP_TWO: 647 | if (distance < mMinPathDistance) 648 | { 649 | mPaint.setColor(COLORS[step]); 650 | mIsPath = true; 651 | mPathDistance = distance; 652 | } 653 | break; 654 | case STEP_THREE: 655 | if (distance < (mMinPathDistance - mCenterDistance / 8)) 656 | { 657 | mPaint.setColor(COLORS[step]); 658 | mIsPath = true; 659 | mPathDistance = distance; 660 | } 661 | break; 662 | case STEP_FOUR: 663 | if (distance < mMinPathDistance) 664 | { 665 | mPaint.setColor(COLORS[step]); 666 | mIsPath = true; 667 | mPathDistance = -distance; 668 | } 669 | } 670 | } 671 | } 672 | 673 | private class CirclePoint 674 | { 675 | float x; 676 | float y; 677 | 678 | public CirclePoint(float x, float y) 679 | { 680 | this.x = x; 681 | this.y = y; 682 | } 683 | } 684 | } 685 | --------------------------------------------------------------------------------