├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── demo.gif ├── gradle └── wrapper │ └── gradle-wrapper.jar ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── baoyz │ │ └── widget │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── baoyz │ │ └── widget │ │ ├── ArcDrawable.java │ │ ├── CirclesDrawable.java │ │ ├── MaterialDrawable.java │ │ ├── PullRefreshLayout.java │ │ ├── RefreshDrawable.java │ │ ├── RingDrawable.java │ │ ├── SmartisanDrawable.java │ │ └── WaterDropDrawable.java │ └── res │ └── values │ └── attrs.xml ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── baoyz │ │ └── pullrefreshlayout │ │ └── sample │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── baoyz │ │ └── pullrefreshlayout │ │ └── sample │ │ ├── DemoActivity.java │ │ ├── ListViewActivity.java │ │ ├── RecyclerViewActivity.java │ │ └── ScrollViewActivity.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_demo.xml │ ├── activity_list_view.xml │ ├── activity_recycler_view.xml │ └── activity_scroll_view.xml │ ├── menu │ └── demo.xml │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Specific files to exclude 2 | com_crashlytics_export_strings.xml 3 | 4 | # Any keystores 5 | *.keystore 6 | 7 | ### Android 8 | ########### 9 | # built application files 10 | *.apk 11 | *.ap_ 12 | 13 | # files for the dex VM 14 | *.dex 15 | 16 | # Java class files 17 | *.class 18 | 19 | # generated files 20 | bin/ 21 | gen/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | *.properties 25 | 26 | ### Linux 27 | ######### 28 | !.gitignore 29 | *~ 30 | 31 | ### Windows 32 | ############ 33 | # Windows image file caches 34 | Thumbs.db 35 | ehthumbs.db 36 | 37 | # Folder config file 38 | Desktop.ini 39 | 40 | # Recycle Bin used on file shares 41 | $RECYCLE.BIN/ 42 | 43 | ### IntelliJ 44 | *.iml 45 | *.ipr 46 | *.iws 47 | .idea/ 48 | 49 | ### Gradle 50 | .gradle/ 51 | build/ 52 | out/ 53 | 54 | #Maven 55 | target 56 | release.properties 57 | pom.xml.* 58 | 59 | ### AppEngine 60 | google_generated/ 61 | datanucleus.log  -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 baoyongzhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | android-PullRefreshLayout 2 | ========================= 3 | 4 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-android--PullRefreshLayout-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/1084) 5 | 6 | This component like SwipeRefreshLayout, it is more beautiful than SwipeRefreshLayout. 7 | 8 | # Demo 9 |

10 | Screenshot 11 |

12 | 13 | # Usage 14 | 15 | Add dependency. 16 | 17 | ``` 18 | dependencies { 19 | compile 'com.baoyz.pullrefreshlayout:library:1.2.0' 20 | } 21 | ``` 22 | 23 | Use method like SwipeRefreshLayout's usage. 24 | 25 | Use it in your layout xml. 26 | 27 | ```xml 28 | 32 | 33 | 34 | 35 | 36 | 37 | ``` 38 | 39 | Get instance and use it. 40 | 41 | ```java 42 | PullRefreshLayout layout = (PullRefreshLayout) findViewById(...); 43 | 44 | // listen refresh event 45 | layout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() { 46 | @Override 47 | public void onRefresh() { 48 | // start refresh 49 | } 50 | }); 51 | 52 | // refresh complete 53 | layout.setRefreshing(false); 54 | 55 | ``` 56 | 57 | Change the refresh style, there are five styles of use, `MATERIAL`、`CIRCLES`、 `WATER_DROP`、`RING` and `SMARTISAN`. 58 | 59 | In java, call `setRefreshStyle` method. 60 | 61 | ```java 62 | layout.setRefreshStyle(PullRefreshLayout.STYLE_CIRCLES); 63 | 64 | ``` 65 | 66 | In xml, use attributes. 67 | 68 | ```xml 69 | 74 | 75 | 76 | 77 | ``` 78 | 79 | Change the color scheme. 80 | In java, call `setColorSchemeColors` method. The int array length must be 4. 81 | 82 | ```java 83 | layout.setColorSchemeColors(int []); 84 | 85 | ``` 86 | 87 | For Smartisan style, it has only one color, can call 'setColor' method, to set one color. 88 | 89 | ```java 90 | layout.setColor(int); 91 | ``` 92 | 93 | In xml, use attributes. 94 | 95 | ```xml 96 | 102 | 103 | 104 | 105 | ``` 106 | 107 | If you do not like these styles, you can customize the refresh style. 108 | 109 | ```java 110 | class CustomDrawable extends RefreshDrawable{ 111 | 112 | @Override 113 | public void setPercent(float percent) { 114 | // Percentage of the maximum distance of the drop-down refresh. 115 | } 116 | 117 | @Override 118 | public void setColorSchemeColors(int[] colorSchemeColors) { 119 | 120 | } 121 | 122 | @Override 123 | public void offsetTopAndBottom(int offset) { 124 | // Drop-down offset. 125 | } 126 | 127 | @Override 128 | public void start() { 129 | isRunning = true; 130 | // Refresh started, start refresh animation. 131 | } 132 | 133 | @Override 134 | public void stop() { 135 | isRunning = false; 136 | // Refresh completed, stop refresh animation. 137 | } 138 | 139 | @Override 140 | public boolean isRunning() { 141 | return isRunning; 142 | } 143 | 144 | @Override 145 | public void draw(Canvas canvas) { 146 | // Draw custom style. 147 | } 148 | 149 | } 150 | 151 | ``` 152 | 153 | Call `setRefreshDrawable()` method to use your custom refresh drawable. 154 | 155 | ```java 156 | layout.setRefreshDrawable(new CustomDrawable()); 157 | ``` 158 | 159 | # Thanks 160 | 161 | * [SwipeRefreshLayout](https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html) 162 | * [GoogleProgressBar](https://github.com/jpardogo/GoogleProgressBar) 163 | 164 | License 165 | ======= 166 | 167 | The MIT License (MIT) 168 | 169 | Copyright (c) 2014 baoyongzhang 170 | 171 | Permission is hereby granted, free of charge, to any person obtaining a copy 172 | of this software and associated documentation files (the "Software"), to deal 173 | in the Software without restriction, including without limitation the rights 174 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 175 | copies of the Software, and to permit persons to whom the Software is 176 | furnished to do so, subject to the following conditions: 177 | 178 | The above copyright notice and this permission notice shall be included in all 179 | copies or substantial portions of the Software. 180 | 181 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 182 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 183 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 184 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 185 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 186 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 187 | SOFTWARE. 188 | 189 | 190 | -------------------------------------------------------------------------------- /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 | maven { 7 | url "https://jcenter.bintray.com" 8 | } 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:1.3.0' 13 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' 14 | classpath 'com.github.dcendents:android-maven-plugin:1.2' 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | jcenter() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baoyongzhang/android-PullRefreshLayout/3b439e1d6762880063eedf4b347eaa1f417d086d/demo.gif -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baoyongzhang/android-PullRefreshLayout/3b439e1d6762880063eedf4b347eaa1f417d086d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "21.0.2" 6 | resourcePrefix "refresh" 7 | 8 | defaultConfig { 9 | minSdkVersion 8 10 | targetSdkVersion 21 11 | versionCode 2 12 | versionName "1.0.1" 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(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:support-v4:21.0.0' 25 | compile 'com.android.support:support-annotations:21.0.3' 26 | } 27 | 28 | apply plugin: 'com.github.dcendents.android-maven' 29 | apply plugin: 'com.jfrog.bintray' 30 | 31 | version = "1.2.0" 32 | def siteUrl = 'https://github.com/baoyongzhang/android-PullRefreshLayout' 33 | def gitUrl = 'https://github.com/baoyongzhang/android-PullRefreshLayout.git' 34 | group = "com.baoyz.pullrefreshlayout" 35 | install { 36 | repositories.mavenInstaller { 37 | // This generates POM.xml with proper parameters 38 | pom { 39 | project { 40 | packaging 'aar' 41 | // Add your description here 42 | name 'This component like SwipeRefreshLayout' 43 | url siteUrl 44 | // Set your license 45 | licenses { 46 | license { 47 | name 'The MIT License (MIT)' 48 | url 'http://baoyz.com/licenses/LICENSE.txt' 49 | } 50 | } 51 | developers { 52 | developer { 53 | id 'baoyongzhang' 54 | name 'baoyongzhang' 55 | email 'baoyz94@gmail.com' 56 | } 57 | } 58 | scm { 59 | connection gitUrl 60 | developerConnection gitUrl 61 | url siteUrl 62 | } 63 | } 64 | } 65 | } 66 | } 67 | task sourcesJar(type: Jar) { 68 | from android.sourceSets.main.java.srcDirs 69 | classifier = 'sources' 70 | } 71 | task javadoc(type: Javadoc) { 72 | source = android.sourceSets.main.java.srcDirs 73 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 74 | } 75 | task javadocJar(type: Jar, dependsOn: javadoc) { 76 | classifier = 'javadoc' 77 | from javadoc.destinationDir 78 | } 79 | artifacts { 80 | archives javadocJar 81 | archives sourcesJar 82 | } 83 | Properties properties = new Properties() 84 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 85 | bintray { 86 | user = properties.getProperty("bintray.user") 87 | key = properties.getProperty("bintray.apikey") 88 | configurations = ['archives'] 89 | pkg { 90 | repo = "maven" 91 | name = "PullRefreshLayout" 92 | websiteUrl = siteUrl 93 | vcsUrl = gitUrl 94 | licenses = ["MIT"] 95 | publish = true 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/baoyz/Developer/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/baoyz/widget/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.widget; 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 | } -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/baoyz/widget/ArcDrawable.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.graphics.RectF; 9 | import android.os.Handler; 10 | import android.util.TypedValue; 11 | import android.view.View; 12 | import android.view.animation.Animation; 13 | import android.view.animation.Transformation; 14 | 15 | /** 16 | * Created by baoyz on 14/11/2. 17 | */ 18 | class ArcDrawable extends RefreshDrawable{ 19 | 20 | private static final int MAX_LEVEL = 200; 21 | 22 | private boolean isRunning; 23 | private RectF mBounds; 24 | private int mWidth; 25 | private int mHeight; 26 | private int mTop; 27 | private int mOffsetTop; 28 | private Paint mPaint; 29 | private float mAngle; 30 | private int[] mColorSchemeColors; 31 | private Handler mHandler = new Handler(); 32 | private int mLevel; 33 | 34 | ArcDrawable(Context context, PullRefreshLayout layout) { 35 | super(context, layout); 36 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 37 | mPaint.setColor(Color.RED); 38 | } 39 | 40 | @Override 41 | public void setPercent(float percent) { 42 | mPaint.setColor(evaluate(percent, mColorSchemeColors[3], mColorSchemeColors[0])); 43 | } 44 | 45 | @Override 46 | public void setColorSchemeColors(int[] colorSchemeColors) { 47 | mColorSchemeColors = colorSchemeColors; 48 | } 49 | 50 | @Override 51 | public void offsetTopAndBottom(int offset) { 52 | mTop += offset; 53 | mOffsetTop += offset; 54 | float offsetTop = mOffsetTop; 55 | if (mOffsetTop > getRefreshLayout().getFinalOffset()){ 56 | offsetTop = getRefreshLayout().getFinalOffset(); 57 | } 58 | mAngle = 360 * (offsetTop / getRefreshLayout().getFinalOffset()); 59 | invalidateSelf(); 60 | } 61 | 62 | @Override 63 | public void start() { 64 | isRunning = true; 65 | mHandler.post(mAnimationTask); 66 | } 67 | 68 | private Runnable mAnimationTask = new Runnable(){ 69 | @Override 70 | public void run() { 71 | if (isRunning()){ 72 | mLevel++; 73 | if (mLevel > MAX_LEVEL) 74 | mLevel = 0; 75 | updateLevel(mLevel); 76 | invalidateSelf(); 77 | mHandler.postDelayed(this, 20); 78 | } 79 | } 80 | }; 81 | 82 | private void updateLevel(int level) { 83 | int animationLevel = level == MAX_LEVEL ? 0 : level; 84 | 85 | int stateForLevel = (animationLevel / 50); 86 | 87 | float percent = level % 50 / 50f; 88 | int startColor = mColorSchemeColors[stateForLevel]; 89 | int endColor = mColorSchemeColors[(stateForLevel + 1) % mColorSchemeColors.length]; 90 | mPaint.setColor(evaluate(percent, startColor, endColor)); 91 | } 92 | 93 | @Override 94 | public void stop() { 95 | isRunning = false; 96 | mHandler.removeCallbacks(mAnimationTask); 97 | } 98 | 99 | @Override 100 | public boolean isRunning() { 101 | return isRunning; 102 | } 103 | 104 | @Override 105 | protected void onBoundsChange(Rect bounds) { 106 | super.onBoundsChange(bounds); 107 | mWidth = dp2px(40); 108 | mHeight = mWidth; 109 | mBounds = new RectF(bounds.width() / 2 - mWidth / 2, bounds.top, bounds.width() / 2 + mWidth / 2, bounds.top + mHeight); 110 | } 111 | 112 | @Override 113 | public void draw(Canvas canvas) { 114 | canvas.save(); 115 | // canvas.translate(0, mTop); 116 | drawRing(canvas); 117 | canvas.restore(); 118 | } 119 | 120 | private void drawRing(Canvas canvas){ 121 | canvas.drawArc(mBounds, 270, mAngle, true, mPaint); 122 | } 123 | 124 | private int dp2px(int dp) { 125 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); 126 | } 127 | 128 | private int evaluate(float fraction, int startValue, int endValue) { 129 | int startInt = startValue; 130 | int startA = (startInt >> 24) & 0xff; 131 | int startR = (startInt >> 16) & 0xff; 132 | int startG = (startInt >> 8) & 0xff; 133 | int startB = startInt & 0xff; 134 | 135 | int endInt = endValue; 136 | int endA = (endInt >> 24) & 0xff; 137 | int endR = (endInt >> 16) & 0xff; 138 | int endG = (endInt >> 8) & 0xff; 139 | int endB = endInt & 0xff; 140 | 141 | return ((startA + (int) (fraction * (endA - startA))) << 24) | 142 | ((startR + (int) (fraction * (endR - startR))) << 16) | 143 | ((startG + (int) (fraction * (endG - startG))) << 8) | 144 | ((startB + (int) (fraction * (endB - startB)))); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /library/src/main/java/com/baoyz/widget/CirclesDrawable.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.ColorFilter; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.Rect; 9 | import android.graphics.RectF; 10 | import android.os.Handler; 11 | import android.util.TypedValue; 12 | 13 | import java.security.InvalidParameterException; 14 | 15 | /** 16 | * Created by baoyz on 14/10/31. 17 | */ 18 | class CirclesDrawable extends RefreshDrawable implements Runnable { 19 | 20 | private static final float MAX_LEVEL = 10000; 21 | private static final float CIRCLE_COUNT = ProgressStates.values().length; 22 | private static final float MAX_LEVEL_PER_CIRCLE = MAX_LEVEL / CIRCLE_COUNT; 23 | private static final int ALPHA_OPAQUE = 255; 24 | 25 | private Paint mFstHalfPaint; 26 | private Paint mScndHalfPaint; 27 | private Paint mAbovePaint; 28 | private RectF mOval = new RectF(); 29 | private int mDiameter; 30 | private Path mPath; 31 | private int mHalf; 32 | private ProgressStates mCurrentState; 33 | private int mControlPointMinimum; 34 | private int mControlPointMaximum; 35 | private int mAxisValue; 36 | private ColorFilter mColorFilter; 37 | private static int mColor1; 38 | private static int mColor2; 39 | private static int mColor3; 40 | private static int mColor4; 41 | private int fstColor, scndColor; 42 | private boolean goesBackward; 43 | private Handler mHandler = new Handler(); 44 | private int mLevel; 45 | private boolean isRunning; 46 | private int mTop; 47 | private int mDrawWidth; 48 | private int mDrawHeight; 49 | private Rect mBounds; 50 | 51 | public CirclesDrawable(Context context, PullRefreshLayout layout) { 52 | super(context, layout); 53 | } 54 | 55 | @Override 56 | public void start() { 57 | mLevel = 2500; 58 | isRunning = true; 59 | mHandler.postDelayed(this, 10); 60 | } 61 | 62 | @Override 63 | public void stop() { 64 | isRunning = false; 65 | mHandler.removeCallbacks(this); 66 | } 67 | 68 | @Override 69 | public boolean isRunning() { 70 | return isRunning; 71 | } 72 | 73 | @Override 74 | public void setColorSchemeColors(int[] colorSchemeColors) { 75 | initCirclesProgress(colorSchemeColors); 76 | } 77 | 78 | @Override 79 | public void setPercent(float percent) { 80 | int level = (int) (2500 * percent); 81 | 82 | updateLevel(level); 83 | } 84 | 85 | private void updateLevel(int level){ 86 | int animationLevel = level == MAX_LEVEL ? 0 : level; 87 | 88 | int stateForLevel = (int) (animationLevel / MAX_LEVEL_PER_CIRCLE); 89 | mCurrentState = ProgressStates.values()[stateForLevel]; 90 | 91 | resetColor(mCurrentState); 92 | int levelForCircle = (int) (animationLevel % MAX_LEVEL_PER_CIRCLE); 93 | 94 | boolean halfPassed; 95 | if (!goesBackward) { 96 | halfPassed = levelForCircle != (int) (animationLevel % (MAX_LEVEL_PER_CIRCLE / 2)); 97 | } else { 98 | halfPassed = levelForCircle == (int) (animationLevel % (MAX_LEVEL_PER_CIRCLE / 2)); 99 | levelForCircle = (int) (MAX_LEVEL_PER_CIRCLE - levelForCircle); 100 | } 101 | 102 | mFstHalfPaint.setColor(fstColor); 103 | mScndHalfPaint.setColor(scndColor); 104 | 105 | if (!halfPassed) { 106 | mAbovePaint.setColor(mScndHalfPaint.getColor()); 107 | } else { 108 | mAbovePaint.setColor(mFstHalfPaint.getColor()); 109 | } 110 | 111 | mAbovePaint.setAlpha(200 + (int) (55 * (levelForCircle / MAX_LEVEL_PER_CIRCLE))); 112 | 113 | mAxisValue = (int) (mControlPointMinimum + (mControlPointMaximum - mControlPointMinimum) * (levelForCircle / MAX_LEVEL_PER_CIRCLE)); 114 | 115 | } 116 | 117 | @Override 118 | public void offsetTopAndBottom(int offset) { 119 | mTop += offset; 120 | invalidateSelf(); 121 | } 122 | 123 | @Override 124 | public void run() { 125 | mLevel += 80; 126 | if (mLevel > MAX_LEVEL) 127 | mLevel = 0; 128 | if (isRunning) { 129 | mHandler.postDelayed(this, 20); 130 | updateLevel(mLevel); 131 | invalidateSelf(); 132 | } 133 | } 134 | 135 | private enum ProgressStates { 136 | FOLDING_DOWN, 137 | FOLDING_LEFT, 138 | FOLDING_UP, 139 | FOLDING_RIGHT 140 | } 141 | 142 | private void initCirclesProgress(int[] colors) { 143 | initColors(colors); 144 | mPath = new Path(); 145 | 146 | Paint basePaint = new Paint(); 147 | basePaint.setAntiAlias(true); 148 | 149 | mFstHalfPaint = new Paint(basePaint); 150 | mScndHalfPaint = new Paint(basePaint); 151 | mAbovePaint = new Paint(basePaint); 152 | 153 | setColorFilter(mColorFilter); 154 | } 155 | 156 | private void initColors(int[] colors) { 157 | if (colors == null || colors.length < 4) 158 | throw new InvalidParameterException("The color scheme length must be 4"); 159 | mColor1 = colors[0]; 160 | mColor2 = colors[1]; 161 | mColor3 = colors[2]; 162 | mColor4 = colors[3]; 163 | } 164 | 165 | @Override 166 | protected void onBoundsChange(Rect bounds) { 167 | super.onBoundsChange(bounds); 168 | mDrawWidth = dp2px(40); 169 | mDrawHeight = mDrawWidth; 170 | mTop = -mDrawHeight - (getRefreshLayout().getFinalOffset() - mDrawHeight) / 2; 171 | mBounds = bounds; 172 | measureCircleProgress(mDrawWidth, mDrawHeight); 173 | } 174 | 175 | private void resetColor(ProgressStates currentState) { 176 | switch (currentState) { 177 | case FOLDING_DOWN: 178 | fstColor = mColor1; 179 | scndColor = mColor2; 180 | goesBackward = false; 181 | break; 182 | case FOLDING_LEFT: 183 | fstColor = mColor1; 184 | scndColor = mColor3; 185 | goesBackward = true; 186 | break; 187 | case FOLDING_UP: 188 | fstColor = mColor3; 189 | scndColor = mColor4; 190 | goesBackward = true; 191 | break; 192 | case FOLDING_RIGHT: 193 | fstColor = mColor2; 194 | scndColor = mColor4; 195 | goesBackward = false; 196 | break; 197 | } 198 | } 199 | 200 | @Override 201 | public void draw(Canvas canvas) { 202 | if (mCurrentState != null) { 203 | canvas.save(); 204 | canvas.translate(mBounds.width() / 2 - mDrawWidth / 2, mTop); 205 | makeCirclesProgress(canvas); 206 | canvas.restore(); 207 | } 208 | } 209 | 210 | private void measureCircleProgress(int width, int height) { 211 | mDiameter = Math.min(width, height); 212 | mHalf = mDiameter / 2; 213 | mOval.set(0, 0, mDiameter, mDiameter); 214 | mControlPointMinimum = -mDiameter / 6; 215 | mControlPointMaximum = mDiameter + mDiameter / 6; 216 | } 217 | 218 | private void makeCirclesProgress(Canvas canvas) { 219 | 220 | switch (mCurrentState) { 221 | case FOLDING_DOWN: 222 | case FOLDING_UP: 223 | drawYMotion(canvas); 224 | break; 225 | case FOLDING_RIGHT: 226 | case FOLDING_LEFT: 227 | drawXMotion(canvas); 228 | break; 229 | } 230 | 231 | canvas.drawPath(mPath, mAbovePaint); 232 | } 233 | 234 | private void drawXMotion(Canvas canvas) { 235 | canvas.drawArc(mOval, 90, 180, true, mFstHalfPaint); 236 | canvas.drawArc(mOval, -270, -180, true, mScndHalfPaint); 237 | mPath.reset(); 238 | mPath.moveTo(mHalf, 0); 239 | mPath.cubicTo(mAxisValue, 0, mAxisValue, mDiameter, mHalf, mDiameter); 240 | } 241 | 242 | private void drawYMotion(Canvas canvas) { 243 | canvas.drawArc(mOval, 0, -180, true, mFstHalfPaint); 244 | canvas.drawArc(mOval, -180, -180, true, mScndHalfPaint); 245 | mPath.reset(); 246 | mPath.moveTo(0, mHalf); 247 | mPath.cubicTo(0, mAxisValue, mDiameter, mAxisValue, mDiameter, mHalf); 248 | } 249 | 250 | @Override 251 | public void setColorFilter(ColorFilter cf) { 252 | this.mColorFilter = cf; 253 | mFstHalfPaint.setColorFilter(cf); 254 | mScndHalfPaint.setColorFilter(cf); 255 | mAbovePaint.setColorFilter(cf); 256 | } 257 | 258 | private int dp2px(int dp) { 259 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /library/src/main/java/com/baoyz/widget/MaterialDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.baoyz.widget; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.graphics.Canvas; 22 | import android.graphics.Color; 23 | import android.graphics.ColorFilter; 24 | import android.graphics.Paint; 25 | import android.graphics.Paint.Style; 26 | import android.graphics.Path; 27 | import android.graphics.PixelFormat; 28 | import android.graphics.RadialGradient; 29 | import android.graphics.Rect; 30 | import android.graphics.RectF; 31 | import android.graphics.Shader; 32 | import android.graphics.drawable.Animatable; 33 | import android.graphics.drawable.Drawable; 34 | import android.graphics.drawable.ShapeDrawable; 35 | import android.graphics.drawable.shapes.OvalShape; 36 | import android.support.annotation.IntDef; 37 | import android.support.annotation.NonNull; 38 | import android.util.DisplayMetrics; 39 | import android.util.Log; 40 | import android.util.TypedValue; 41 | import android.view.View; 42 | import android.view.animation.AccelerateDecelerateInterpolator; 43 | import android.view.animation.Animation; 44 | import android.view.animation.Interpolator; 45 | import android.view.animation.LinearInterpolator; 46 | import android.view.animation.Transformation; 47 | 48 | import java.lang.annotation.Retention; 49 | import java.lang.annotation.RetentionPolicy; 50 | import java.util.ArrayList; 51 | 52 | /** 53 | * Fancy progress indicator for Material theme. 54 | * 55 | * @hide 56 | */ 57 | class MaterialDrawable extends RefreshDrawable implements Animatable { 58 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 59 | private static final Interpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); 60 | private static final Interpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); 61 | private static final Interpolator EASE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); 62 | 63 | @Retention(RetentionPolicy.CLASS) 64 | @IntDef({LARGE, DEFAULT}) 65 | public @interface ProgressDrawableSize { 66 | } 67 | 68 | // Maps to ProgressBar.Large style 69 | static final int LARGE = 0; 70 | // Maps to ProgressBar default style 71 | static final int DEFAULT = 1; 72 | 73 | // Maps to ProgressBar default style 74 | private static final int CIRCLE_DIAMETER = 40; 75 | private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 76 | private static final float STROKE_WIDTH = 2.5f; 77 | 78 | // Maps to ProgressBar.Large style 79 | private static final int CIRCLE_DIAMETER_LARGE = 56; 80 | private static final float CENTER_RADIUS_LARGE = 12.5f; 81 | private static final float STROKE_WIDTH_LARGE = 3f; 82 | 83 | private static final float MAX_PROGRESS_ANGLE = .8f; 84 | private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; 85 | private static final int MAX_ALPHA = 255; 86 | 87 | private final int[] COLORS = new int[]{ 88 | Color.BLACK 89 | }; 90 | 91 | /** 92 | * The duration of a single progress spin in milliseconds. 93 | */ 94 | private static final int ANIMATION_DURATION = 1000 * 80 / 60; 95 | 96 | /** 97 | * The number of points in the progress "star". 98 | */ 99 | private static final float NUM_POINTS = 5f; 100 | /** 101 | * The list of animators operating on this drawable. 102 | */ 103 | private final ArrayList mAnimators = new ArrayList(); 104 | 105 | /** 106 | * The indicator ring, used to manage animation state. 107 | */ 108 | private final Ring mRing; 109 | 110 | /** 111 | * Canvas rotation in degrees. 112 | */ 113 | private float mRotation; 114 | 115 | /** 116 | * Layout info for the arrowhead in dp 117 | */ 118 | private static final int ARROW_WIDTH = 10; 119 | private static final int ARROW_HEIGHT = 5; 120 | private static final float ARROW_OFFSET_ANGLE = 5; 121 | 122 | /** 123 | * Layout info for the arrowhead for the large spinner in dp 124 | */ 125 | private static final int ARROW_WIDTH_LARGE = 12; 126 | private static final int ARROW_HEIGHT_LARGE = 6; 127 | private static final float MAX_PROGRESS_ARC = .8f; 128 | 129 | /** 130 | * Circle Drawable * 131 | */ 132 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 133 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 134 | // PX 135 | private static final float X_OFFSET = 0f; 136 | private static final float Y_OFFSET = 1.75f; 137 | private static final float SHADOW_RADIUS = 3.5f; 138 | private static final int SHADOW_ELEVATION = 4; 139 | 140 | private Resources mResources; 141 | private View mParent; 142 | private Animation mAnimation; 143 | private float mRotationCount; 144 | private double mWidth; 145 | private double mHeight; 146 | private Animation mFinishAnimation; 147 | private int mShadowRadius; 148 | private int mPadding; 149 | private ShapeDrawable mCircle; 150 | private int mTop; 151 | private int mDiameter; 152 | 153 | public MaterialDrawable(Context context, PullRefreshLayout parent) { 154 | super(context, parent); 155 | mParent = parent; 156 | mResources = context.getResources(); 157 | 158 | mRing = new Ring(mCallback); 159 | mRing.setColors(COLORS); 160 | 161 | updateSizes(DEFAULT); 162 | setupAnimators(); 163 | createCircleDrawable(); 164 | setBackgroundColor(CIRCLE_BG_LIGHT); 165 | mDiameter = dp2px(40); 166 | mTop = -mDiameter - (getRefreshLayout().getFinalOffset() - mDiameter) / 2; 167 | } 168 | 169 | private void createCircleDrawable() { 170 | float radius = CIRCLE_DIAMETER / 2; 171 | final float density = getContext().getResources().getDisplayMetrics().density; 172 | final int diameter = (int) (radius * density * 2); 173 | final int shadowYOffset = (int) (density * Y_OFFSET); 174 | final int shadowXOffset = (int) (density * X_OFFSET); 175 | 176 | mShadowRadius = (int) (density * SHADOW_RADIUS); 177 | 178 | OvalShape oval = new OvalShadow(mShadowRadius, diameter); 179 | mCircle = new ShapeDrawable(oval); 180 | // ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); 181 | mCircle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 182 | KEY_SHADOW_COLOR); 183 | mPadding = (int) mShadowRadius; 184 | 185 | mCircle.getPaint().setColor(Color.WHITE); 186 | } 187 | 188 | private class OvalShadow extends OvalShape { 189 | private RadialGradient mRadialGradient; 190 | private int mShadowRadius; 191 | private Paint mShadowPaint; 192 | private int mCircleDiameter; 193 | 194 | public OvalShadow(int shadowRadius, int circleDiameter) { 195 | super(); 196 | mShadowPaint = new Paint(); 197 | mShadowRadius = shadowRadius; 198 | mCircleDiameter = circleDiameter; 199 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, 200 | mShadowRadius, new int[] { 201 | FILL_SHADOW_COLOR, Color.TRANSPARENT 202 | }, null, Shader.TileMode.CLAMP); 203 | mShadowPaint.setShader(mRadialGradient); 204 | } 205 | 206 | @Override 207 | public void draw(Canvas canvas, Paint paint) { 208 | final int x = MaterialDrawable.this.getBounds().centerX(); 209 | final int y = MaterialDrawable.this.getBounds().centerY(); 210 | canvas.drawCircle(x, y, (mCircleDiameter / 2 + mShadowRadius), 211 | mShadowPaint); 212 | canvas.drawCircle(x, y, (mCircleDiameter / 2), paint); 213 | } 214 | } 215 | 216 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 217 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 218 | final Ring ring = mRing; 219 | final DisplayMetrics metrics = mResources.getDisplayMetrics(); 220 | final float screenDensity = metrics.density; 221 | 222 | mWidth = progressCircleWidth * screenDensity; 223 | mHeight = progressCircleHeight * screenDensity; 224 | ring.setStrokeWidth((float) strokeWidth * screenDensity); 225 | ring.setCenterRadius(centerRadius * screenDensity); 226 | ring.setColorIndex(0); 227 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 228 | ring.setInsets((int) mWidth, (int) mHeight); 229 | } 230 | 231 | /** 232 | * Set the overall size for the progress spinner. This updates the radius 233 | * and stroke width of the ring. 234 | */ 235 | public void updateSizes(@ProgressDrawableSize int size) { 236 | if (size == LARGE) { 237 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 238 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 239 | } else { 240 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 241 | ARROW_WIDTH, ARROW_HEIGHT); 242 | } 243 | } 244 | 245 | /** 246 | * @param show Set to true to display the arrowhead on the progress spinner. 247 | */ 248 | public void showArrow(boolean show) { 249 | mRing.setShowArrow(show); 250 | } 251 | 252 | /** 253 | * @param scale Set the scale of the arrowhead for the spinner. 254 | */ 255 | public void setArrowScale(float scale) { 256 | mRing.setArrowScale(scale); 257 | } 258 | 259 | /** 260 | * Set the start and end trim for the progress spinner arc. 261 | * 262 | * @param startAngle start angle 263 | * @param endAngle end angle 264 | */ 265 | public void setStartEndTrim(float startAngle, float endAngle) { 266 | mRing.setStartTrim(startAngle); 267 | mRing.setEndTrim(endAngle); 268 | } 269 | 270 | /** 271 | * Set the amount of rotation to apply to the progress spinner. 272 | * 273 | * @param rotation Rotation is from [0..1] 274 | */ 275 | public void setProgressRotation(float rotation) { 276 | mRing.setRotation(rotation); 277 | } 278 | 279 | /** 280 | * Update the background color of the circle image view. 281 | */ 282 | public void setBackgroundColor(int color) { 283 | mRing.setBackgroundColor(color); 284 | } 285 | 286 | @Override 287 | public void setPercent(float percent) { 288 | if (percent < .4f) 289 | return; 290 | percent = (percent - .4f) / .6f; 291 | setAlpha((int) (MAX_ALPHA * percent)); 292 | showArrow(true); 293 | float strokeStart = ((percent) * .8f); 294 | setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); 295 | setArrowScale(Math.min(1f, percent)); 296 | float rotation = percent < .8f ? 0 : (percent - .8f) / .2f * .25f; 297 | setProgressRotation(rotation); 298 | } 299 | 300 | /** 301 | * Set the colors used in the progress animation from color resources. 302 | * The first color will also be the color of the bar that grows in response 303 | * to a user swipe gesture. 304 | * 305 | * @param colors 306 | */ 307 | public void setColorSchemeColors(int... colors) { 308 | mRing.setColors(colors); 309 | mRing.setColorIndex(0); 310 | } 311 | 312 | @Override 313 | public void offsetTopAndBottom(int offset) { 314 | mTop += offset; 315 | invalidateSelf(); 316 | } 317 | // 318 | // @Override 319 | // public int getIntrinsicHeight() { 320 | // return (int) mHeight; 321 | // } 322 | // 323 | // @Override 324 | // public int getIntrinsicWidth() { 325 | // return (int) mWidth; 326 | // } 327 | 328 | @Override 329 | public void draw(Canvas c) { 330 | Rect bounds = getBounds(); 331 | final int saveCount = c.save(); 332 | c.translate(0, mTop); 333 | mCircle.draw(c); 334 | // c.scale((float)mWidth / bounds.width(), (float)mHeight / bounds.height()); 335 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 336 | mRing.draw(c, bounds); 337 | c.restoreToCount(saveCount); 338 | } 339 | 340 | @Override 341 | protected void onBoundsChange(Rect bounds) { 342 | super.onBoundsChange(bounds); 343 | 344 | } 345 | 346 | @Override 347 | public void setBounds(int left, int top, int right, int bottom) { 348 | int w = right - left; 349 | super.setBounds(w / 2 - mDiameter / 2, top, w / 2 + mDiameter / 2, mDiameter + top); 350 | } 351 | 352 | private int dp2px(int dp) { 353 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); 354 | } 355 | 356 | @Override 357 | public void setAlpha(int alpha) { 358 | mRing.setAlpha(alpha); 359 | } 360 | 361 | public int getAlpha() { 362 | return mRing.getAlpha(); 363 | } 364 | 365 | @Override 366 | public void setColorFilter(ColorFilter colorFilter) { 367 | mRing.setColorFilter(colorFilter); 368 | } 369 | 370 | @SuppressWarnings("unused") 371 | void setRotation(float rotation) { 372 | mRotation = rotation; 373 | invalidateSelf(); 374 | } 375 | 376 | @SuppressWarnings("unused") 377 | private float getRotation() { 378 | return mRotation; 379 | } 380 | 381 | @Override 382 | public int getOpacity() { 383 | return PixelFormat.TRANSLUCENT; 384 | } 385 | 386 | @Override 387 | public boolean isRunning() { 388 | final ArrayList animators = mAnimators; 389 | final int N = animators.size(); 390 | for (int i = 0; i < N; i++) { 391 | final Animation animator = animators.get(i); 392 | if (animator.hasStarted() && !animator.hasEnded()) { 393 | return true; 394 | } 395 | } 396 | return false; 397 | } 398 | 399 | @Override 400 | public void start() { 401 | mAnimation.reset(); 402 | mRing.storeOriginals(); 403 | // Already showing some part of the ring 404 | if (mRing.getEndTrim() != mRing.getStartTrim()) { 405 | mParent.startAnimation(mFinishAnimation); 406 | } else { 407 | mRing.setColorIndex(0); 408 | mRing.resetOriginals(); 409 | mParent.startAnimation(mAnimation); 410 | } 411 | } 412 | 413 | @Override 414 | public void stop() { 415 | mParent.clearAnimation(); 416 | setRotation(0); 417 | mRing.setShowArrow(false); 418 | mRing.setColorIndex(0); 419 | mRing.resetOriginals(); 420 | } 421 | 422 | private void setupAnimators() { 423 | final Ring ring = mRing; 424 | final Animation finishRingAnimation = new Animation() { 425 | public void applyTransformation(float interpolatedTime, Transformation t) { 426 | // shrink back down and complete a full rotation before starting other circles 427 | // Rotation goes between [0..1]. 428 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() 429 | / MAX_PROGRESS_ARC) + 1f); 430 | final float startTrim = ring.getStartingStartTrim() 431 | + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) 432 | * interpolatedTime; 433 | ring.setStartTrim(startTrim); 434 | final float rotation = ring.getStartingRotation() 435 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 436 | ring.setRotation(rotation); 437 | ring.setArrowScale(1 - interpolatedTime); 438 | } 439 | }; 440 | finishRingAnimation.setInterpolator(EASE_INTERPOLATOR); 441 | finishRingAnimation.setDuration(ANIMATION_DURATION / 2); 442 | finishRingAnimation.setAnimationListener(new Animation.AnimationListener() { 443 | 444 | @Override 445 | public void onAnimationStart(Animation animation) { 446 | } 447 | 448 | @Override 449 | public void onAnimationEnd(Animation animation) { 450 | ring.goToNextColor(); 451 | ring.storeOriginals(); 452 | ring.setShowArrow(false); 453 | mParent.startAnimation(mAnimation); 454 | } 455 | 456 | @Override 457 | public void onAnimationRepeat(Animation animation) { 458 | } 459 | }); 460 | final Animation animation = new Animation() { 461 | @Override 462 | public void applyTransformation(float interpolatedTime, Transformation t) { 463 | // The minProgressArc is calculated from 0 to create an angle that 464 | // matches the stroke width. 465 | final float minProgressArc = (float) Math.toRadians(ring.getStrokeWidth() 466 | / (2 * Math.PI * ring.getCenterRadius())); 467 | final float startingEndTrim = ring.getStartingEndTrim(); 468 | final float startingTrim = ring.getStartingStartTrim(); 469 | final float startingRotation = ring.getStartingRotation(); 470 | 471 | // Offset the minProgressArc to where the endTrim is located. 472 | final float minArc = MAX_PROGRESS_ARC - minProgressArc; 473 | final float endTrim = startingEndTrim 474 | + (minArc * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime)); 475 | ring.setEndTrim(endTrim); 476 | 477 | final float startTrim = startingTrim 478 | + (MAX_PROGRESS_ARC * END_CURVE_INTERPOLATOR 479 | .getInterpolation(interpolatedTime)); 480 | ring.setStartTrim(startTrim); 481 | 482 | final float rotation = startingRotation + (0.25f * interpolatedTime); 483 | ring.setRotation(rotation); 484 | 485 | float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime) 486 | + (720.0f * (mRotationCount / NUM_POINTS)); 487 | setRotation(groupRotation); 488 | } 489 | }; 490 | animation.setRepeatCount(Animation.INFINITE); 491 | animation.setRepeatMode(Animation.RESTART); 492 | animation.setInterpolator(LINEAR_INTERPOLATOR); 493 | animation.setDuration(ANIMATION_DURATION); 494 | animation.setAnimationListener(new Animation.AnimationListener() { 495 | 496 | @Override 497 | public void onAnimationStart(Animation animation) { 498 | mRotationCount = 0; 499 | } 500 | 501 | @Override 502 | public void onAnimationEnd(Animation animation) { 503 | // do nothing 504 | } 505 | 506 | @Override 507 | public void onAnimationRepeat(Animation animation) { 508 | ring.storeOriginals(); 509 | ring.goToNextColor(); 510 | ring.setStartTrim(ring.getEndTrim()); 511 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 512 | } 513 | }); 514 | mFinishAnimation = finishRingAnimation; 515 | mAnimation = animation; 516 | } 517 | 518 | private final Callback mCallback = new Callback() { 519 | @Override 520 | public void invalidateDrawable(Drawable d) { 521 | invalidateSelf(); 522 | } 523 | 524 | @Override 525 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 526 | scheduleSelf(what, when); 527 | } 528 | 529 | @Override 530 | public void unscheduleDrawable(Drawable d, Runnable what) { 531 | unscheduleSelf(what); 532 | } 533 | }; 534 | 535 | private static class Ring { 536 | private final RectF mTempBounds = new RectF(); 537 | private final Paint mPaint = new Paint(); 538 | private final Paint mArrowPaint = new Paint(); 539 | 540 | private final Callback mCallback; 541 | 542 | private float mStartTrim = 0.0f; 543 | private float mEndTrim = 0.0f; 544 | private float mRotation = 0.0f; 545 | private float mStrokeWidth = 5.0f; 546 | private float mStrokeInset = 2.5f; 547 | 548 | private int[] mColors; 549 | // mColorIndex represents the offset into the available mColors that the 550 | // progress circle should currently display. As the progress circle is 551 | // animating, the mColorIndex moves by one to the next available color. 552 | private int mColorIndex; 553 | private float mStartingStartTrim; 554 | private float mStartingEndTrim; 555 | private float mStartingRotation; 556 | private boolean mShowArrow; 557 | private Path mArrow; 558 | private float mArrowScale; 559 | private double mRingCenterRadius; 560 | private int mArrowWidth; 561 | private int mArrowHeight; 562 | private int mAlpha; 563 | private final Paint mCirclePaint = new Paint(); 564 | private int mBackgroundColor; 565 | 566 | public Ring(Callback callback) { 567 | mCallback = callback; 568 | 569 | mPaint.setStrokeCap(Paint.Cap.SQUARE); 570 | mPaint.setAntiAlias(true); 571 | mPaint.setStyle(Style.STROKE); 572 | 573 | mArrowPaint.setStyle(Style.FILL); 574 | mArrowPaint.setAntiAlias(true); 575 | } 576 | 577 | public void setBackgroundColor(int color) { 578 | mBackgroundColor = color; 579 | } 580 | 581 | /** 582 | * Set the dimensions of the arrowhead. 583 | * 584 | * @param width Width of the hypotenuse of the arrow head 585 | * @param height Height of the arrow point 586 | */ 587 | public void setArrowDimensions(float width, float height) { 588 | mArrowWidth = (int) width; 589 | mArrowHeight = (int) height; 590 | } 591 | 592 | /** 593 | * Draw the progress spinner 594 | */ 595 | public void draw(Canvas c, Rect bounds) { 596 | final RectF arcBounds = mTempBounds; 597 | arcBounds.set(bounds); 598 | arcBounds.inset(mStrokeInset, mStrokeInset); 599 | 600 | final float startAngle = (mStartTrim + mRotation) * 360; 601 | final float endAngle = (mEndTrim + mRotation) * 360; 602 | float sweepAngle = endAngle - startAngle; 603 | 604 | mPaint.setColor(mColors[mColorIndex]); 605 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); 606 | 607 | drawTriangle(c, startAngle, sweepAngle, bounds); 608 | 609 | if (mAlpha < 255) { 610 | mCirclePaint.setColor(mBackgroundColor); 611 | mCirclePaint.setAlpha(255 - mAlpha); 612 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, 613 | mCirclePaint); 614 | } 615 | } 616 | 617 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 618 | if (mShowArrow) { 619 | if (mArrow == null) { 620 | mArrow = new Path(); 621 | mArrow.setFillType(Path.FillType.EVEN_ODD); 622 | } else { 623 | mArrow.reset(); 624 | } 625 | 626 | // Adjust the position of the triangle so that it is inset as 627 | // much as the arc, but also centered on the arc. 628 | float inset = (int) mStrokeInset / 2 * mArrowScale; 629 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 630 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 631 | 632 | // Update the path each time. This works around an issue in SKIA 633 | // where concatenating a rotation matrix to a scale matrix 634 | // ignored a starting negative rotation. This appears to have 635 | // been fixed as of API 21. 636 | mArrow.moveTo(0, 0); 637 | mArrow.lineTo(mArrowWidth * mArrowScale, 0); 638 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 639 | * mArrowScale)); 640 | mArrow.offset(x - inset, y); 641 | mArrow.close(); 642 | // draw a triangle 643 | mArrowPaint.setColor(mColors[mColorIndex]); 644 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 645 | bounds.exactCenterY()); 646 | c.drawPath(mArrow, mArrowPaint); 647 | } 648 | } 649 | 650 | /** 651 | * Set the colors the progress spinner alternates between. 652 | * 653 | * @param colors Array of integers describing the colors. Must be non-null. 654 | */ 655 | public void setColors(@NonNull int[] colors) { 656 | mColors = colors; 657 | // if colors are reset, make sure to reset the color index as well 658 | setColorIndex(0); 659 | } 660 | 661 | /** 662 | * @param index Index into the color array of the color to display in 663 | * the progress spinner. 664 | */ 665 | public void setColorIndex(int index) { 666 | mColorIndex = index; 667 | } 668 | 669 | /** 670 | * Proceed to the next available ring color. This will automatically 671 | * wrap back to the beginning of colors. 672 | */ 673 | public void goToNextColor() { 674 | mColorIndex = (mColorIndex + 1) % (mColors.length); 675 | } 676 | 677 | public void setColorFilter(ColorFilter filter) { 678 | mPaint.setColorFilter(filter); 679 | invalidateSelf(); 680 | } 681 | 682 | /** 683 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 684 | */ 685 | public void setAlpha(int alpha) { 686 | mAlpha = alpha; 687 | } 688 | 689 | /** 690 | * @return Current alpha of the progress spinner and arrowhead. 691 | */ 692 | public int getAlpha() { 693 | return mAlpha; 694 | } 695 | 696 | /** 697 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 698 | */ 699 | public void setStrokeWidth(float strokeWidth) { 700 | mStrokeWidth = strokeWidth; 701 | mPaint.setStrokeWidth(strokeWidth); 702 | invalidateSelf(); 703 | } 704 | 705 | @SuppressWarnings("unused") 706 | public float getStrokeWidth() { 707 | return mStrokeWidth; 708 | } 709 | 710 | @SuppressWarnings("unused") 711 | public void setStartTrim(float startTrim) { 712 | mStartTrim = startTrim; 713 | invalidateSelf(); 714 | } 715 | 716 | @SuppressWarnings("unused") 717 | public float getStartTrim() { 718 | return mStartTrim; 719 | } 720 | 721 | public float getStartingStartTrim() { 722 | return mStartingStartTrim; 723 | } 724 | 725 | public float getStartingEndTrim() { 726 | return mStartingEndTrim; 727 | } 728 | 729 | @SuppressWarnings("unused") 730 | public void setEndTrim(float endTrim) { 731 | mEndTrim = endTrim; 732 | invalidateSelf(); 733 | } 734 | 735 | @SuppressWarnings("unused") 736 | public float getEndTrim() { 737 | return mEndTrim; 738 | } 739 | 740 | @SuppressWarnings("unused") 741 | public void setRotation(float rotation) { 742 | mRotation = rotation; 743 | invalidateSelf(); 744 | } 745 | 746 | @SuppressWarnings("unused") 747 | public float getRotation() { 748 | return mRotation; 749 | } 750 | 751 | public void setInsets(int width, int height) { 752 | final float minEdge = (float) Math.min(width, height); 753 | float insets; 754 | if (mRingCenterRadius <= 0 || minEdge < 0) { 755 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 756 | } else { 757 | insets = (float) (minEdge / 2.0f - mRingCenterRadius); 758 | } 759 | mStrokeInset = insets; 760 | } 761 | 762 | @SuppressWarnings("unused") 763 | public float getInsets() { 764 | return mStrokeInset; 765 | } 766 | 767 | /** 768 | * @param centerRadius Inner radius in px of the circle the progress 769 | * spinner arc traces. 770 | */ 771 | public void setCenterRadius(double centerRadius) { 772 | mRingCenterRadius = centerRadius; 773 | } 774 | 775 | public double getCenterRadius() { 776 | return mRingCenterRadius; 777 | } 778 | 779 | /** 780 | * @param show Set to true to show the arrow head on the progress spinner. 781 | */ 782 | public void setShowArrow(boolean show) { 783 | if (mShowArrow != show) { 784 | mShowArrow = show; 785 | invalidateSelf(); 786 | } 787 | } 788 | 789 | /** 790 | * @param scale Set the scale of the arrowhead for the spinner. 791 | */ 792 | public void setArrowScale(float scale) { 793 | if (scale != mArrowScale) { 794 | mArrowScale = scale; 795 | invalidateSelf(); 796 | } 797 | } 798 | 799 | /** 800 | * @return The amount the progress spinner is currently rotated, between [0..1]. 801 | */ 802 | public float getStartingRotation() { 803 | return mStartingRotation; 804 | } 805 | 806 | /** 807 | * If the start / end trim are offset to begin with, store them so that 808 | * animation starts from that offset. 809 | */ 810 | public void storeOriginals() { 811 | mStartingStartTrim = mStartTrim; 812 | mStartingEndTrim = mEndTrim; 813 | mStartingRotation = mRotation; 814 | } 815 | 816 | /** 817 | * Reset the progress spinner to default rotation, start and end angles. 818 | */ 819 | public void resetOriginals() { 820 | mStartingStartTrim = 0; 821 | mStartingEndTrim = 0; 822 | mStartingRotation = 0; 823 | setStartTrim(0); 824 | setEndTrim(0); 825 | setRotation(0); 826 | } 827 | 828 | private void invalidateSelf() { 829 | mCallback.invalidateDrawable(null); 830 | } 831 | } 832 | 833 | /** 834 | * Squishes the interpolation curve into the second half of the animation. 835 | */ 836 | private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator { 837 | @Override 838 | public float getInterpolation(float input) { 839 | return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f)); 840 | } 841 | } 842 | 843 | /** 844 | * Squishes the interpolation curve into the first half of the animation. 845 | */ 846 | private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator { 847 | @Override 848 | public float getInterpolation(float input) { 849 | return super.getInterpolation(Math.min(1, input * 2.0f)); 850 | } 851 | } 852 | } 853 | -------------------------------------------------------------------------------- /library/src/main/java/com/baoyz/widget/PullRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.support.v4.view.MotionEventCompat; 7 | import android.support.v4.view.ViewCompat; 8 | import android.util.AttributeSet; 9 | import android.util.TypedValue; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewConfiguration; 13 | import android.view.ViewGroup; 14 | import android.view.animation.Animation; 15 | import android.view.animation.DecelerateInterpolator; 16 | import android.view.animation.Interpolator; 17 | import android.view.animation.Transformation; 18 | import android.widget.AbsListView; 19 | import android.widget.ImageView; 20 | 21 | import java.security.InvalidParameterException; 22 | 23 | /** 24 | * Created by baoyz on 14/10/30. 25 | */ 26 | public class PullRefreshLayout extends ViewGroup { 27 | 28 | private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; 29 | private static final int DRAG_MAX_DISTANCE = 64; 30 | private static final int INVALID_POINTER = -1; 31 | private static final float DRAG_RATE = .5f; 32 | 33 | public static final int STYLE_MATERIAL = 0; 34 | public static final int STYLE_CIRCLES = 1; 35 | public static final int STYLE_WATER_DROP = 2; 36 | public static final int STYLE_RING = 3; 37 | public static final int STYLE_SMARTISAN = 4; 38 | 39 | private View mTarget; 40 | private ImageView mRefreshView; 41 | private Interpolator mDecelerateInterpolator; 42 | private int mTouchSlop; 43 | private int mSpinnerFinalOffset; 44 | private int mTotalDragDistance; 45 | private RefreshDrawable mRefreshDrawable; 46 | private int mCurrentOffsetTop; 47 | private boolean mRefreshing; 48 | private int mActivePointerId; 49 | private boolean mIsBeingDragged; 50 | private float mInitialMotionY; 51 | private int mFrom; 52 | private boolean mNotify; 53 | private OnRefreshListener mListener; 54 | private int[] mColorSchemeColors; 55 | 56 | public int mDurationToStartPosition; 57 | public int mDurationToCorrectPosition; 58 | private int mInitialOffsetTop; 59 | private boolean mDispatchTargetTouchDown; 60 | private float mDragPercent; 61 | 62 | public PullRefreshLayout(Context context) { 63 | this(context, null); 64 | } 65 | 66 | public PullRefreshLayout(Context context, AttributeSet attrs) { 67 | super(context, attrs); 68 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.refresh_PullRefreshLayout); 69 | final int type = a.getInteger(R.styleable.refresh_PullRefreshLayout_refreshType, STYLE_MATERIAL); 70 | final int colorsId = a.getResourceId(R.styleable.refresh_PullRefreshLayout_refreshColors, 0); 71 | final int colorId = a.getResourceId(R.styleable.refresh_PullRefreshLayout_refreshColor, 0); 72 | a.recycle(); 73 | 74 | mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); 75 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 76 | int defaultDuration = getResources().getInteger(android.R.integer.config_mediumAnimTime); 77 | mDurationToStartPosition = defaultDuration; 78 | mDurationToCorrectPosition = defaultDuration; 79 | mSpinnerFinalOffset = mTotalDragDistance = dp2px(DRAG_MAX_DISTANCE); 80 | 81 | if (colorsId > 0) { 82 | mColorSchemeColors = context.getResources().getIntArray(colorsId); 83 | } else { 84 | mColorSchemeColors = new int[]{Color.rgb(0xC9, 0x34, 0x37), Color.rgb(0x37, 0x5B, 0xF1), Color.rgb(0xF7, 0xD2, 0x3E), Color.rgb(0x34, 0xA3, 0x50)}; 85 | } 86 | 87 | if (colorId > 0) { 88 | mColorSchemeColors = new int[]{context.getResources().getColor(colorId)}; 89 | } 90 | 91 | mRefreshView = new ImageView(context); 92 | setRefreshStyle(type); 93 | mRefreshView.setVisibility(View.GONE); 94 | addView(mRefreshView, 0); 95 | setWillNotDraw(false); 96 | ViewCompat.setChildrenDrawingOrderEnabled(this, true); 97 | } 98 | 99 | public void setColorSchemeColors(int... colorSchemeColors) { 100 | mColorSchemeColors = colorSchemeColors; 101 | mRefreshDrawable.setColorSchemeColors(colorSchemeColors); 102 | } 103 | 104 | public void setColor(int color) { 105 | setColorSchemeColors(color); 106 | } 107 | 108 | public void setRefreshStyle(int type) { 109 | setRefreshing(false); 110 | switch (type) { 111 | case STYLE_MATERIAL: 112 | mRefreshDrawable = new MaterialDrawable(getContext(), this); 113 | break; 114 | case STYLE_CIRCLES: 115 | mRefreshDrawable = new CirclesDrawable(getContext(), this); 116 | break; 117 | case STYLE_WATER_DROP: 118 | mRefreshDrawable = new WaterDropDrawable(getContext(), this); 119 | break; 120 | case STYLE_RING: 121 | mRefreshDrawable = new RingDrawable(getContext(), this); 122 | break; 123 | case STYLE_SMARTISAN: 124 | mRefreshDrawable = new SmartisanDrawable(getContext(), this); 125 | break; 126 | default: 127 | throw new InvalidParameterException("Type does not exist"); 128 | } 129 | mRefreshDrawable.setColorSchemeColors(mColorSchemeColors); 130 | mRefreshView.setImageDrawable(mRefreshDrawable); 131 | } 132 | 133 | public void setRefreshDrawable(RefreshDrawable drawable) { 134 | setRefreshing(false); 135 | mRefreshDrawable = drawable; 136 | mRefreshDrawable.setColorSchemeColors(mColorSchemeColors); 137 | mRefreshView.setImageDrawable(mRefreshDrawable); 138 | } 139 | 140 | public int getFinalOffset() { 141 | return mSpinnerFinalOffset; 142 | } 143 | 144 | @Override 145 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 146 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 147 | 148 | ensureTarget(); 149 | if (mTarget == null) 150 | return; 151 | 152 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingRight() - getPaddingLeft(), MeasureSpec.EXACTLY); 153 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY); 154 | mTarget.measure(widthMeasureSpec, heightMeasureSpec); 155 | mRefreshView.measure(widthMeasureSpec, heightMeasureSpec); 156 | // mRefreshView.measure(MeasureSpec.makeMeasureSpec(mRefreshViewWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mRefreshViewHeight, MeasureSpec.EXACTLY)); 157 | } 158 | 159 | private void ensureTarget() { 160 | if (mTarget != null) 161 | return; 162 | if (getChildCount() > 0) { 163 | for (int i = 0; i < getChildCount(); i++) { 164 | View child = getChildAt(i); 165 | if (child != mRefreshView) 166 | mTarget = child; 167 | } 168 | } 169 | } 170 | 171 | @Override 172 | public boolean onInterceptTouchEvent(MotionEvent ev) { 173 | 174 | if (!isEnabled() || (canChildScrollUp() && !mRefreshing)) { 175 | return false; 176 | } 177 | 178 | final int action = MotionEventCompat.getActionMasked(ev); 179 | 180 | switch (action) { 181 | case MotionEvent.ACTION_DOWN: 182 | if (!mRefreshing) { 183 | setTargetOffsetTop(0, true); 184 | } 185 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 186 | mIsBeingDragged = false; 187 | final float initialMotionY = getMotionEventY(ev, mActivePointerId); 188 | if (initialMotionY == -1) { 189 | return false; 190 | } 191 | mInitialMotionY = initialMotionY; 192 | mInitialOffsetTop = mCurrentOffsetTop; 193 | mDispatchTargetTouchDown = false; 194 | mDragPercent = 0; 195 | break; 196 | case MotionEvent.ACTION_MOVE: 197 | if (mActivePointerId == INVALID_POINTER) { 198 | return false; 199 | } 200 | final float y = getMotionEventY(ev, mActivePointerId); 201 | if (y == -1) { 202 | return false; 203 | } 204 | final float yDiff = y - mInitialMotionY; 205 | if (mRefreshing) { 206 | mIsBeingDragged = !(yDiff < 0 && mCurrentOffsetTop <= 0); 207 | } else if (yDiff > mTouchSlop && !mIsBeingDragged) { 208 | mIsBeingDragged = true; 209 | } 210 | break; 211 | case MotionEvent.ACTION_UP: 212 | case MotionEvent.ACTION_CANCEL: 213 | mIsBeingDragged = false; 214 | mActivePointerId = INVALID_POINTER; 215 | break; 216 | case MotionEventCompat.ACTION_POINTER_UP: 217 | onSecondaryPointerUp(ev); 218 | break; 219 | } 220 | 221 | return mIsBeingDragged; 222 | } 223 | 224 | @Override 225 | public boolean onTouchEvent(MotionEvent ev) { 226 | 227 | if (!mIsBeingDragged) { 228 | return super.onTouchEvent(ev); 229 | } 230 | 231 | final int action = MotionEventCompat.getActionMasked(ev); 232 | 233 | switch (action) { 234 | case MotionEvent.ACTION_MOVE: { 235 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 236 | if (pointerIndex < 0) { 237 | return false; 238 | } 239 | 240 | final float y = MotionEventCompat.getY(ev, pointerIndex); 241 | final float yDiff = y - mInitialMotionY; 242 | int targetY; 243 | if (mRefreshing) { 244 | targetY = (int) (mInitialOffsetTop + yDiff); 245 | if (canChildScrollUp()) { 246 | targetY = -1; 247 | mInitialMotionY = y; 248 | mInitialOffsetTop = 0; 249 | if (mDispatchTargetTouchDown) { 250 | mTarget.dispatchTouchEvent(ev); 251 | } else { 252 | MotionEvent obtain = MotionEvent.obtain(ev); 253 | obtain.setAction(MotionEvent.ACTION_DOWN); 254 | mDispatchTargetTouchDown = true; 255 | mTarget.dispatchTouchEvent(obtain); 256 | } 257 | } else { 258 | if (targetY < 0) { 259 | if (mDispatchTargetTouchDown) { 260 | mTarget.dispatchTouchEvent(ev); 261 | } else { 262 | MotionEvent obtain = MotionEvent.obtain(ev); 263 | obtain.setAction(MotionEvent.ACTION_DOWN); 264 | mDispatchTargetTouchDown = true; 265 | mTarget.dispatchTouchEvent(obtain); 266 | } 267 | targetY = 0; 268 | } else if (targetY > mTotalDragDistance) { 269 | targetY = mTotalDragDistance; 270 | } else { 271 | if (mDispatchTargetTouchDown) { 272 | MotionEvent obtain = MotionEvent.obtain(ev); 273 | obtain.setAction(MotionEvent.ACTION_CANCEL); 274 | mDispatchTargetTouchDown = false; 275 | mTarget.dispatchTouchEvent(obtain); 276 | } 277 | } 278 | } 279 | } else { 280 | final float scrollTop = yDiff * DRAG_RATE; 281 | float originalDragPercent = scrollTop / mTotalDragDistance; 282 | if (originalDragPercent < 0) { 283 | return false; 284 | } 285 | mDragPercent = Math.min(1f, Math.abs(originalDragPercent)); 286 | float extraOS = Math.abs(scrollTop) - mTotalDragDistance; 287 | float slingshotDist = mSpinnerFinalOffset; 288 | float tensionSlingshotPercent = Math.max(0, 289 | Math.min(extraOS, slingshotDist * 2) / slingshotDist); 290 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( 291 | (tensionSlingshotPercent / 4), 2)) * 2f; 292 | float extraMove = (slingshotDist) * tensionPercent * 2; 293 | targetY = (int) ((slingshotDist * mDragPercent) + extraMove); 294 | if (mRefreshView.getVisibility() != View.VISIBLE) { 295 | mRefreshView.setVisibility(View.VISIBLE); 296 | } 297 | if (scrollTop < mTotalDragDistance) { 298 | mRefreshDrawable.setPercent(mDragPercent); 299 | } 300 | } 301 | setTargetOffsetTop(targetY - mCurrentOffsetTop, true); 302 | break; 303 | } 304 | case MotionEventCompat.ACTION_POINTER_DOWN: 305 | final int index = MotionEventCompat.getActionIndex(ev); 306 | mActivePointerId = MotionEventCompat.getPointerId(ev, index); 307 | break; 308 | case MotionEventCompat.ACTION_POINTER_UP: 309 | onSecondaryPointerUp(ev); 310 | break; 311 | case MotionEvent.ACTION_UP: 312 | case MotionEvent.ACTION_CANCEL: { 313 | if (mActivePointerId == INVALID_POINTER) { 314 | return false; 315 | } 316 | if (mRefreshing) { 317 | if (mDispatchTargetTouchDown) { 318 | mTarget.dispatchTouchEvent(ev); 319 | mDispatchTargetTouchDown = false; 320 | } 321 | return false; 322 | } 323 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 324 | final float y = MotionEventCompat.getY(ev, pointerIndex); 325 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 326 | mIsBeingDragged = false; 327 | if (overscrollTop > mTotalDragDistance) { 328 | setRefreshing(true, true); 329 | } else { 330 | mRefreshing = false; 331 | animateOffsetToStartPosition(); 332 | } 333 | mActivePointerId = INVALID_POINTER; 334 | return false; 335 | } 336 | } 337 | 338 | return true; 339 | } 340 | 341 | public void setDurations(int durationToStartPosition, int durationToCorrectPosition) { 342 | mDurationToStartPosition = durationToStartPosition; 343 | mDurationToCorrectPosition = durationToCorrectPosition; 344 | } 345 | 346 | private void animateOffsetToStartPosition() { 347 | mFrom = mCurrentOffsetTop; 348 | mAnimateToStartPosition.reset(); 349 | mAnimateToStartPosition.setDuration(mDurationToStartPosition); 350 | mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); 351 | mAnimateToStartPosition.setAnimationListener(mToStartListener); 352 | mRefreshView.clearAnimation(); 353 | mRefreshView.startAnimation(mAnimateToStartPosition); 354 | } 355 | 356 | private void animateOffsetToCorrectPosition() { 357 | mFrom = mCurrentOffsetTop; 358 | mAnimateToCorrectPosition.reset(); 359 | mAnimateToCorrectPosition.setDuration(mDurationToCorrectPosition); 360 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator); 361 | mAnimateToCorrectPosition.setAnimationListener(mRefreshListener); 362 | mRefreshView.clearAnimation(); 363 | mRefreshView.startAnimation(mAnimateToCorrectPosition); 364 | } 365 | 366 | private final Animation mAnimateToStartPosition = new Animation() { 367 | @Override 368 | public void applyTransformation(float interpolatedTime, Transformation t) { 369 | moveToStart(interpolatedTime); 370 | } 371 | }; 372 | 373 | private final Animation mAnimateToCorrectPosition = new Animation() { 374 | @Override 375 | public void applyTransformation(float interpolatedTime, Transformation t) { 376 | int endTarget = mSpinnerFinalOffset; 377 | int targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime)); 378 | int offset = targetTop - mTarget.getTop(); 379 | setTargetOffsetTop(offset, false /* requires update */); 380 | } 381 | }; 382 | 383 | private void moveToStart(float interpolatedTime) { 384 | int targetTop = mFrom - (int) (mFrom * interpolatedTime); 385 | int offset = targetTop - mTarget.getTop(); 386 | setTargetOffsetTop(offset, false); 387 | mRefreshDrawable.setPercent(mDragPercent * (1 - interpolatedTime)); 388 | } 389 | 390 | public void setRefreshing(boolean refreshing) { 391 | if (mRefreshing != refreshing) { 392 | setRefreshing(refreshing, false /* notify */); 393 | } 394 | } 395 | 396 | private void setRefreshing(boolean refreshing, final boolean notify) { 397 | if (mRefreshing != refreshing) { 398 | mNotify = notify; 399 | ensureTarget(); 400 | mRefreshing = refreshing; 401 | if (mRefreshing) { 402 | mRefreshDrawable.setPercent(1f); 403 | animateOffsetToCorrectPosition(); 404 | } else { 405 | animateOffsetToStartPosition(); 406 | } 407 | } 408 | } 409 | 410 | private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() { 411 | @Override 412 | public void onAnimationStart(Animation animation) { 413 | mRefreshView.setVisibility(View.VISIBLE); 414 | } 415 | 416 | @Override 417 | public void onAnimationRepeat(Animation animation) { 418 | } 419 | 420 | @Override 421 | public void onAnimationEnd(Animation animation) { 422 | if (mRefreshing) { 423 | mRefreshDrawable.start(); 424 | if (mNotify) { 425 | if (mListener != null) { 426 | mListener.onRefresh(); 427 | } 428 | } 429 | } else { 430 | mRefreshDrawable.stop(); 431 | mRefreshView.setVisibility(View.GONE); 432 | animateOffsetToStartPosition(); 433 | } 434 | mCurrentOffsetTop = mTarget.getTop(); 435 | } 436 | }; 437 | 438 | private Animation.AnimationListener mToStartListener = new Animation.AnimationListener() { 439 | @Override 440 | public void onAnimationStart(Animation animation) { 441 | mRefreshDrawable.stop(); 442 | } 443 | 444 | @Override 445 | public void onAnimationRepeat(Animation animation) { 446 | } 447 | 448 | @Override 449 | public void onAnimationEnd(Animation animation) { 450 | // mRefreshDrawable.stop(); 451 | mRefreshView.setVisibility(View.GONE); 452 | mCurrentOffsetTop = mTarget.getTop(); 453 | } 454 | }; 455 | 456 | private void onSecondaryPointerUp(MotionEvent ev) { 457 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 458 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 459 | if (pointerId == mActivePointerId) { 460 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 461 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 462 | } 463 | } 464 | 465 | private float getMotionEventY(MotionEvent ev, int activePointerId) { 466 | final int index = MotionEventCompat.findPointerIndex(ev, activePointerId); 467 | if (index < 0) { 468 | return -1; 469 | } 470 | return MotionEventCompat.getY(ev, index); 471 | } 472 | 473 | private void setTargetOffsetTop(int offset, boolean requiresUpdate) { 474 | // mRefreshView.bringToFront(); 475 | mTarget.offsetTopAndBottom(offset); 476 | mCurrentOffsetTop = mTarget.getTop(); 477 | mRefreshDrawable.offsetTopAndBottom(offset); 478 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { 479 | invalidate(); 480 | } 481 | } 482 | 483 | private boolean canChildScrollUp() { 484 | if (android.os.Build.VERSION.SDK_INT < 14) { 485 | if (mTarget instanceof AbsListView) { 486 | final AbsListView absListView = (AbsListView) mTarget; 487 | return absListView.getChildCount() > 0 488 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 489 | .getTop() < absListView.getPaddingTop()); 490 | } else { 491 | return mTarget.getScrollY() > 0; 492 | } 493 | } else { 494 | return ViewCompat.canScrollVertically(mTarget, -1); 495 | } 496 | } 497 | 498 | @Override 499 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 500 | 501 | ensureTarget(); 502 | if (mTarget == null) 503 | return; 504 | 505 | int height = getMeasuredHeight(); 506 | int width = getMeasuredWidth(); 507 | int left = getPaddingLeft(); 508 | int top = getPaddingTop(); 509 | int right = getPaddingRight(); 510 | int bottom = getPaddingBottom(); 511 | 512 | mTarget.layout(left, top + mTarget.getTop(), left + width - right, top + height - bottom + mTarget.getTop()); 513 | mRefreshView.layout(left, top, left + width - right, top + height - bottom); 514 | } 515 | 516 | private int dp2px(int dp) { 517 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); 518 | } 519 | 520 | public void setOnRefreshListener(OnRefreshListener listener) { 521 | mListener = listener; 522 | } 523 | 524 | public static interface OnRefreshListener { 525 | public void onRefresh(); 526 | } 527 | } 528 | -------------------------------------------------------------------------------- /library/src/main/java/com/baoyz/widget/RefreshDrawable.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.ColorFilter; 5 | import android.graphics.PixelFormat; 6 | import android.graphics.drawable.Animatable; 7 | import android.graphics.drawable.Drawable; 8 | 9 | /** 10 | * Created by baoyz on 14/10/29. 11 | */ 12 | public abstract class RefreshDrawable extends Drawable implements Drawable.Callback, Animatable { 13 | 14 | private PullRefreshLayout mRefreshLayout; 15 | 16 | public RefreshDrawable(Context context, PullRefreshLayout layout) { 17 | mRefreshLayout = layout; 18 | } 19 | 20 | public Context getContext(){ 21 | return mRefreshLayout != null ? mRefreshLayout.getContext() : null; 22 | } 23 | 24 | public PullRefreshLayout getRefreshLayout(){ 25 | return mRefreshLayout; 26 | } 27 | 28 | public abstract void setPercent(float percent); 29 | public abstract void setColorSchemeColors(int[] colorSchemeColors); 30 | 31 | public abstract void offsetTopAndBottom(int offset); 32 | 33 | @Override 34 | public void invalidateDrawable(Drawable who) { 35 | final Callback callback = getCallback(); 36 | if (callback != null) { 37 | callback.invalidateDrawable(this); 38 | } 39 | } 40 | 41 | @Override 42 | public void scheduleDrawable(Drawable who, Runnable what, long when) { 43 | final Callback callback = getCallback(); 44 | if (callback != null) { 45 | callback.scheduleDrawable(this, what, when); 46 | } 47 | } 48 | 49 | @Override 50 | public void unscheduleDrawable(Drawable who, Runnable what) { 51 | final Callback callback = getCallback(); 52 | if (callback != null) { 53 | callback.unscheduleDrawable(this, what); 54 | } 55 | } 56 | 57 | @Override 58 | public int getOpacity() { 59 | return PixelFormat.TRANSLUCENT; 60 | } 61 | 62 | @Override 63 | public void setAlpha(int alpha) { 64 | 65 | } 66 | 67 | @Override 68 | public void setColorFilter(ColorFilter cf) { 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /library/src/main/java/com/baoyz/widget/RingDrawable.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.graphics.Rect; 8 | import android.graphics.RectF; 9 | import android.os.Handler; 10 | import android.util.TypedValue; 11 | 12 | /** 13 | * Created by baoyz on 14/11/2. 14 | */ 15 | class RingDrawable extends RefreshDrawable { 16 | 17 | private static final int MAX_LEVEL = 200; 18 | 19 | private boolean isRunning; 20 | private RectF mBounds; 21 | private int mWidth; 22 | private int mHeight; 23 | private Paint mPaint; 24 | private Path mPath; 25 | private float mAngle; 26 | private int[] mColorSchemeColors; 27 | private Handler mHandler = new Handler(); 28 | private int mLevel; 29 | private float mDegress; 30 | 31 | RingDrawable(Context context, PullRefreshLayout layout) { 32 | super(context, layout); 33 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 34 | mPaint.setStyle(Paint.Style.STROKE); 35 | mPaint.setStrokeWidth(dp2px(3)); 36 | mPaint.setStrokeCap(Paint.Cap.ROUND); 37 | mPath = new Path(); 38 | } 39 | 40 | @Override 41 | public void setPercent(float percent) { 42 | mPaint.setColor(evaluate(percent, mColorSchemeColors[0], mColorSchemeColors[1])); 43 | mAngle = 340 * percent; 44 | } 45 | 46 | @Override 47 | public void setColorSchemeColors(int[] colorSchemeColors) { 48 | mColorSchemeColors = colorSchemeColors; 49 | } 50 | 51 | @Override 52 | public void offsetTopAndBottom(int offset) { 53 | invalidateSelf(); 54 | } 55 | 56 | @Override 57 | public void start() { 58 | mLevel = 50; 59 | isRunning = true; 60 | invalidateSelf(); 61 | } 62 | 63 | private void updateLevel(int level) { 64 | int animationLevel = level == MAX_LEVEL ? 0 : level; 65 | 66 | int stateForLevel = (animationLevel / 50); 67 | 68 | float percent = level % 50 / 50f; 69 | int startColor = mColorSchemeColors[stateForLevel]; 70 | int endColor = mColorSchemeColors[(stateForLevel + 1) % mColorSchemeColors.length]; 71 | mPaint.setColor(evaluate(percent, startColor, endColor)); 72 | 73 | mDegress = 360 * percent; 74 | } 75 | 76 | @Override 77 | public void stop() { 78 | isRunning = false; 79 | mDegress = 0; 80 | } 81 | 82 | @Override 83 | public boolean isRunning() { 84 | return isRunning; 85 | } 86 | 87 | @Override 88 | protected void onBoundsChange(Rect bounds) { 89 | super.onBoundsChange(bounds); 90 | mWidth = getRefreshLayout().getFinalOffset(); 91 | mHeight = mWidth; 92 | mBounds = new RectF(bounds.width() / 2 - mWidth / 2, bounds.top, bounds.width() / 2 + mWidth / 2, bounds.top + mHeight); 93 | mBounds.inset(dp2px(15), dp2px(15)); 94 | } 95 | 96 | @Override 97 | public void draw(Canvas canvas) { 98 | canvas.save(); 99 | // canvas.translate(0, mTop); 100 | canvas.rotate(mDegress, mBounds.centerX(), mBounds.centerY()); 101 | drawRing(canvas); 102 | canvas.restore(); 103 | if (isRunning) { 104 | mLevel = mLevel >= MAX_LEVEL ? 0 : mLevel + 1; 105 | updateLevel(mLevel); 106 | invalidateSelf(); 107 | } 108 | } 109 | 110 | private void drawRing(Canvas canvas) { 111 | mPath.reset(); 112 | mPath.arcTo(mBounds, 270, mAngle, true); 113 | canvas.drawPath(mPath, mPaint); 114 | // canvas.drawArc(mBounds, 270, mAngle, true, mPaint); 115 | } 116 | 117 | private int dp2px(int dp) { 118 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); 119 | } 120 | 121 | private int evaluate(float fraction, int startValue, int endValue) { 122 | int startInt = startValue; 123 | int startA = (startInt >> 24) & 0xff; 124 | int startR = (startInt >> 16) & 0xff; 125 | int startG = (startInt >> 8) & 0xff; 126 | int startB = startInt & 0xff; 127 | 128 | int endInt = endValue; 129 | int endA = (endInt >> 24) & 0xff; 130 | int endR = (endInt >> 16) & 0xff; 131 | int endG = (endInt >> 8) & 0xff; 132 | int endB = endInt & 0xff; 133 | 134 | return ((startA + (int) (fraction * (endA - startA))) << 24) | 135 | ((startR + (int) (fraction * (endR - startR))) << 16) | 136 | ((startG + (int) (fraction * (endG - startG))) << 8) | 137 | ((startB + (int) (fraction * (endB - startB)))); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /library/src/main/java/com/baoyz/widget/SmartisanDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 baoyongzhang 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.baoyz.widget; 25 | 26 | import android.content.Context; 27 | import android.graphics.Canvas; 28 | import android.graphics.Color; 29 | import android.graphics.Paint; 30 | import android.graphics.Rect; 31 | import android.graphics.RectF; 32 | import android.util.TypedValue; 33 | 34 | /** 35 | * android-PullRefreshLayout 36 | * Created by baoyz on 15/9/20. 37 | */ 38 | public class SmartisanDrawable extends RefreshDrawable { 39 | 40 | RectF mBounds; 41 | float mWidth; 42 | float mHeight; 43 | float mCenterX; 44 | float mCenterY; 45 | float mPercent; 46 | final float mMaxAngle = (float) (180f * .85); 47 | final float mRadius = dp2px(12); 48 | final float mLineLength = (float) (Math.PI / 180 * mMaxAngle * mRadius); 49 | final float mLineWidth = dp2px(3); 50 | final float mArrowLength = (int) (mLineLength * .15); 51 | final float mArrowAngle = (float) (Math.PI / 180 * 25); 52 | final float mArrowXSpace = (int) (mArrowLength * Math.sin(mArrowAngle)); 53 | final float mArrowYSpace = (int) (mArrowLength * Math.cos(mArrowAngle)); 54 | final Paint mPaint = new Paint(); 55 | int mOffset; 56 | boolean mRunning; 57 | float mDegrees; 58 | 59 | public SmartisanDrawable(Context context, PullRefreshLayout layout) { 60 | super(context, layout); 61 | 62 | mPaint.setAntiAlias(true); 63 | mPaint.setStrokeJoin(Paint.Join.ROUND); 64 | mPaint.setStrokeCap(Paint.Cap.ROUND); 65 | mPaint.setStrokeWidth(mLineWidth); 66 | mPaint.setStyle(Paint.Style.STROKE); 67 | mPaint.setColor(Color.GRAY); 68 | } 69 | 70 | @Override 71 | protected void onBoundsChange(Rect bounds) { 72 | super.onBoundsChange(bounds); 73 | mHeight = getRefreshLayout().getFinalOffset(); 74 | mWidth = mHeight; 75 | mBounds = new RectF(bounds.width() / 2 - mWidth / 2, bounds.top - mHeight / 2, bounds.width() / 2 + mWidth / 2, bounds.top + mHeight / 2); 76 | mCenterX = mBounds.centerX(); 77 | mCenterY = mBounds.centerY(); 78 | } 79 | 80 | @Override 81 | public void setPercent(float percent) { 82 | mPercent = percent; 83 | invalidateSelf(); 84 | } 85 | 86 | @Override 87 | public void setColorSchemeColors(int[] colorSchemeColors) { 88 | if (colorSchemeColors != null && colorSchemeColors.length > 0) { 89 | mPaint.setColor(colorSchemeColors[0]); 90 | } 91 | } 92 | 93 | @Override 94 | public void offsetTopAndBottom(int offset) { 95 | mOffset += offset; 96 | invalidateSelf(); 97 | } 98 | 99 | @Override 100 | public void start() { 101 | mRunning = true; 102 | mDegrees = 0; 103 | invalidateSelf(); 104 | } 105 | 106 | @Override 107 | public void stop() { 108 | mRunning = false; 109 | } 110 | 111 | @Override 112 | public boolean isRunning() { 113 | return mRunning; 114 | } 115 | 116 | @Override 117 | public void draw(Canvas canvas) { 118 | 119 | canvas.save(); 120 | 121 | canvas.translate(0, mOffset / 2); 122 | canvas.clipRect(mBounds); 123 | 124 | if (mOffset > mHeight && !isRunning()) { 125 | canvas.rotate((mOffset - mHeight) / mHeight * 360, mCenterX, mCenterY); 126 | } 127 | 128 | if (isRunning()) { 129 | canvas.rotate(mDegrees, mCenterX, mCenterY); 130 | mDegrees = mDegrees < 360 ? mDegrees + 10 : 0; 131 | invalidateSelf(); 132 | } 133 | 134 | if (mPercent <= .5f) { 135 | 136 | float percent = mPercent / .5f; 137 | 138 | // left 139 | float leftX = mCenterX - mRadius; 140 | float leftY = mCenterY + mLineLength - mLineLength * percent; 141 | 142 | canvas.drawLine(leftX, leftY, leftX, leftY + mLineLength, mPaint); 143 | 144 | // left arrow 145 | canvas.drawLine(leftX, leftY, leftX - mArrowXSpace, leftY + mArrowYSpace, mPaint); 146 | 147 | // right 148 | float rightX = mCenterX + mRadius; 149 | float rightY = mCenterY - mLineLength + mLineLength * percent; 150 | 151 | canvas.drawLine(rightX, rightY, rightX, rightY - mLineLength, mPaint); 152 | 153 | // right arrow 154 | canvas.drawLine(rightX, rightY, rightX + mArrowXSpace, rightY - mArrowYSpace, mPaint); 155 | 156 | } else { 157 | 158 | float percent = (mPercent - .5f) / .5f; 159 | // left 160 | float leftX = mCenterX - mRadius; 161 | float leftY = mCenterY; 162 | 163 | canvas.drawLine(leftX, leftY, leftX, leftY + mLineLength - mLineLength * percent, mPaint); 164 | 165 | RectF oval = new RectF(mCenterX - mRadius, mCenterY - mRadius, mCenterX + mRadius, mCenterY + mRadius); 166 | 167 | canvas.drawArc(oval, 180, mMaxAngle * percent, false, mPaint); 168 | 169 | // right 170 | float rightX = mCenterX + mRadius; 171 | float rightY = mCenterY; 172 | 173 | canvas.drawLine(rightX, rightY, rightX, rightY - mLineLength + mLineLength * percent, mPaint); 174 | 175 | canvas.drawArc(oval, 0, mMaxAngle * percent, false, mPaint); 176 | 177 | // arrow 178 | canvas.save(); 179 | 180 | // canvas.translate(mCenterX, mCenterY); 181 | canvas.rotate(mMaxAngle * percent, mCenterX, mCenterY); 182 | // canvas.translate(-mCenterX, -mCenterY); 183 | 184 | // left arrow 185 | canvas.drawLine(leftX, leftY, leftX - mArrowXSpace, leftY + mArrowYSpace, mPaint); 186 | 187 | // right arrow 188 | canvas.drawLine(rightX, rightY, rightX + mArrowXSpace, rightY - mArrowYSpace, mPaint); 189 | 190 | canvas.restore(); 191 | } 192 | 193 | canvas.restore(); 194 | } 195 | 196 | private int dp2px(int dp) { 197 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /library/src/main/java/com/baoyz/widget/WaterDropDrawable.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.Point; 9 | import android.graphics.Rect; 10 | import android.os.Handler; 11 | 12 | import java.security.InvalidParameterException; 13 | 14 | /** 15 | * Created by baoyz on 14/10/31. 16 | */ 17 | class WaterDropDrawable extends RefreshDrawable implements Runnable { 18 | 19 | private static final float MAX_LEVEL = 10000; 20 | private static final float CIRCLE_COUNT = ProgressStates.values().length; 21 | private static final float MAX_LEVEL_PER_CIRCLE = MAX_LEVEL / CIRCLE_COUNT; 22 | 23 | private int mLevel; 24 | private Point p1, p2, p3, p4; 25 | private Paint mPaint; 26 | private Path mPath; 27 | private int mHeight; 28 | private int mWidth; 29 | private int mTop; 30 | private int[] mColorSchemeColors; 31 | private ProgressStates mCurrentState; 32 | private Handler mHandler = new Handler(); 33 | private boolean isRunning; 34 | 35 | private enum ProgressStates { 36 | ONE, 37 | TWO, 38 | TREE, 39 | FOUR 40 | } 41 | 42 | public WaterDropDrawable(Context context, PullRefreshLayout layout) { 43 | super(context, layout); 44 | mPaint = new Paint(); 45 | mPaint.setColor(Color.BLUE); 46 | mPaint.setStyle(Paint.Style.FILL); 47 | mPaint.setAntiAlias(true); 48 | mPath = new Path(); 49 | p1 = new Point(); 50 | p2 = new Point(); 51 | p3 = new Point(); 52 | p4 = new Point(); 53 | } 54 | 55 | @Override 56 | public void draw(Canvas canvas) { 57 | 58 | canvas.save(); 59 | canvas.translate(0, mTop > 0 ? mTop : 0); 60 | 61 | mPath.reset(); 62 | mPath.moveTo(p1.x, p1.y); 63 | mPath.cubicTo(p3.x, p3.y, p4.x, p4.y, p2.x, p2.y); 64 | canvas.drawPath(mPath, mPaint); 65 | 66 | canvas.restore(); 67 | } 68 | 69 | @Override 70 | protected void onBoundsChange(Rect bounds) { 71 | mWidth = bounds.width(); 72 | updateBounds(); 73 | super.onBoundsChange(bounds); 74 | } 75 | 76 | private void updateBounds() { 77 | 78 | int height = mHeight; 79 | int width = mWidth; 80 | 81 | if (height > getRefreshLayout().getFinalOffset()) { 82 | height = getRefreshLayout().getFinalOffset(); 83 | } 84 | 85 | final float percent = height / (float) getRefreshLayout().getFinalOffset(); 86 | int offsetX = (int) (width / 2 * percent); 87 | int offsetY = 0; 88 | p1.set(offsetX, offsetY); 89 | p2.set(width - offsetX, offsetY); 90 | p3.set(width / 2 - height, height); 91 | p4.set(width / 2 + height, height); 92 | } 93 | 94 | @Override 95 | public void setColorSchemeColors(int[] colorSchemeColors) { 96 | if (colorSchemeColors == null || colorSchemeColors.length < 4) 97 | throw new InvalidParameterException("The color scheme length must be 4"); 98 | mPaint.setColor(colorSchemeColors[0]); 99 | mColorSchemeColors = colorSchemeColors; 100 | } 101 | 102 | @Override 103 | public void setPercent(float percent) { 104 | mPaint.setColor(evaluate(percent, mColorSchemeColors[0], mColorSchemeColors[1])); 105 | } 106 | 107 | private void updateLevel(int level) { 108 | int animationLevel = level == MAX_LEVEL ? 0 : level; 109 | 110 | int stateForLevel = (int) (animationLevel / MAX_LEVEL_PER_CIRCLE); 111 | mCurrentState = ProgressStates.values()[stateForLevel]; 112 | 113 | float percent = level % 2500 / 2500f; 114 | int startColor = mColorSchemeColors[stateForLevel]; 115 | int endColor = mColorSchemeColors[(stateForLevel + 1) % ProgressStates.values().length]; 116 | mPaint.setColor(evaluate(percent, startColor, endColor)); 117 | } 118 | 119 | @Override 120 | public void offsetTopAndBottom(int offset) { 121 | mHeight += offset; 122 | mTop = mHeight - getRefreshLayout().getFinalOffset(); 123 | updateBounds(); 124 | invalidateSelf(); 125 | } 126 | 127 | @Override 128 | public void start() { 129 | mLevel = 2500; 130 | isRunning = true; 131 | mHandler.postDelayed(this, 20); 132 | } 133 | 134 | @Override 135 | public void stop() { 136 | mHandler.removeCallbacks(this); 137 | } 138 | 139 | @Override 140 | public boolean isRunning() { 141 | return isRunning; 142 | } 143 | 144 | @Override 145 | public void run() { 146 | mLevel += 60; 147 | if (mLevel > MAX_LEVEL) 148 | mLevel = 0; 149 | if (isRunning) { 150 | mHandler.postDelayed(this, 20); 151 | updateLevel(mLevel); 152 | invalidateSelf(); 153 | } 154 | } 155 | 156 | private int evaluate(float fraction, int startValue, int endValue) { 157 | int startInt = startValue; 158 | int startA = (startInt >> 24) & 0xff; 159 | int startR = (startInt >> 16) & 0xff; 160 | int startG = (startInt >> 8) & 0xff; 161 | int startB = startInt & 0xff; 162 | 163 | int endInt = endValue; 164 | int endA = (endInt >> 24) & 0xff; 165 | int endR = (endInt >> 16) & 0xff; 166 | int endG = (endInt >> 8) & 0xff; 167 | int endB = endInt & 0xff; 168 | 169 | return ((startA + (int) (fraction * (endA - startA))) << 24) | 170 | ((startR + (int) (fraction * (endR - startR))) << 16) | 171 | ((startG + (int) (fraction * (endG - startG))) << 8) | 172 | ((startB + (int) (fraction * (endB - startB)))); 173 | } 174 | 175 | } -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "21.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.baoyz.pullrefreshlayout.sample" 9 | minSdkVersion 8 10 | targetSdkVersion 21 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 | compile 'com.android.support:support-v4:21.0.0' 25 | compile 'com.android.support:recyclerview-v7:21.0.0' 26 | compile project(':library') 27 | } 28 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/baoyz/Developer/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/baoyz/pullrefreshlayout/sample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.pullrefreshlayout.sample; 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 | } -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sample/src/main/java/com/baoyz/pullrefreshlayout/sample/DemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.pullrefreshlayout.sample; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | 8 | import com.baoyz.widget.PullRefreshLayout; 9 | 10 | public class DemoActivity extends Activity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_demo); 17 | 18 | } 19 | 20 | public void onListViewClick(View view) { 21 | startActivity(new Intent(this, ListViewActivity.class)); 22 | } 23 | 24 | public void onRecyclerViewClick(View view) { 25 | startActivity(new Intent(this, RecyclerViewActivity.class)); 26 | } 27 | 28 | public void onScrollViewClick(View view) { 29 | startActivity(new Intent(this, ScrollViewActivity.class)); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /sample/src/main/java/com/baoyz/pullrefreshlayout/sample/ListViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.pullrefreshlayout.sample; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ListView; 12 | import android.widget.TextView; 13 | 14 | import com.baoyz.widget.PullRefreshLayout; 15 | 16 | public class ListViewActivity extends Activity { 17 | 18 | PullRefreshLayout layout; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_list_view); 25 | 26 | String[] array = new String[50]; 27 | for (int i = 0; i < array.length; i++) { 28 | array[i] = "string " + i; 29 | } 30 | 31 | final ListView listView = (ListView) findViewById(R.id.listView); 32 | listView.setAdapter(new android.widget.ArrayAdapter(this, android.R.layout.simple_list_item_1, array)); 33 | 34 | layout = (PullRefreshLayout) findViewById(R.id.swipeRefreshLayout); 35 | layout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() { 36 | @Override 37 | public void onRefresh() { 38 | layout.postDelayed(new Runnable() { 39 | @Override 40 | public void run() { 41 | layout.setRefreshing(false); 42 | } 43 | }, 3000); 44 | } 45 | }); 46 | 47 | } 48 | 49 | static class ArrayAdapter extends RecyclerView.Adapter{ 50 | 51 | private String[] mArray; 52 | private Context mContext; 53 | 54 | public ArrayAdapter(Context context, String[] array) { 55 | mContext = context; 56 | mArray = array; 57 | } 58 | 59 | @Override 60 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { 61 | return new ViewHolder(View.inflate(viewGroup.getContext(), android.R.layout.simple_list_item_1, null)); 62 | } 63 | 64 | @Override 65 | public void onBindViewHolder(ViewHolder viewHolder, int i) { 66 | viewHolder.mTextView.setText(mArray[i]); 67 | } 68 | 69 | @Override 70 | public int getItemCount() { 71 | return mArray.length; 72 | } 73 | } 74 | 75 | static class ViewHolder extends RecyclerView.ViewHolder{ 76 | 77 | public TextView mTextView; 78 | 79 | public ViewHolder(View itemView) { 80 | super(itemView); 81 | mTextView = (TextView) itemView; 82 | } 83 | } 84 | 85 | 86 | @Override 87 | public boolean onCreateOptionsMenu(Menu menu) { 88 | getMenuInflater().inflate(R.menu.demo, menu); 89 | return true; 90 | } 91 | 92 | @Override 93 | public boolean onOptionsItemSelected(MenuItem item) { 94 | int id = item.getItemId(); 95 | 96 | switch (id){ 97 | case R.id.action_material: 98 | layout.setRefreshStyle(PullRefreshLayout.STYLE_MATERIAL); 99 | return true; 100 | case R.id.action_circles: 101 | layout.setRefreshStyle(PullRefreshLayout.STYLE_CIRCLES); 102 | return true; 103 | case R.id.action_water_drop: 104 | layout.setRefreshStyle(PullRefreshLayout.STYLE_WATER_DROP); 105 | return true; 106 | case R.id.action_ring: 107 | layout.setRefreshStyle(PullRefreshLayout.STYLE_RING); 108 | return true; 109 | case R.id.action_smartisan: 110 | layout.setRefreshStyle(PullRefreshLayout.STYLE_SMARTISAN); 111 | return true; 112 | } 113 | 114 | return super.onOptionsItemSelected(item); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /sample/src/main/java/com/baoyz/pullrefreshlayout/sample/RecyclerViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.pullrefreshlayout.sample; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.TextView; 13 | 14 | import com.baoyz.widget.PullRefreshLayout; 15 | 16 | public class RecyclerViewActivity extends Activity { 17 | 18 | PullRefreshLayout layout; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_recycler_view); 25 | 26 | RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); 27 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 28 | String[] array = new String[50]; 29 | for (int i = 0; i < array.length; i++) { 30 | array[i] = "string " + i; 31 | } 32 | recyclerView.setAdapter(new ArrayAdapter(this, array)); 33 | 34 | layout = (PullRefreshLayout) findViewById(R.id.swipeRefreshLayout); 35 | layout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() { 36 | @Override 37 | public void onRefresh() { 38 | layout.postDelayed(new Runnable() { 39 | @Override 40 | public void run() { 41 | layout.setRefreshing(false); 42 | } 43 | }, 4000); 44 | } 45 | }); 46 | } 47 | 48 | static class ArrayAdapter extends RecyclerView.Adapter{ 49 | 50 | private String[] mArray; 51 | private Context mContext; 52 | 53 | public ArrayAdapter(Context context, String[] array) { 54 | mContext = context; 55 | mArray = array; 56 | } 57 | 58 | @Override 59 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { 60 | return new ViewHolder(View.inflate(viewGroup.getContext(), android.R.layout.simple_list_item_1, null)); 61 | } 62 | 63 | @Override 64 | public void onBindViewHolder(ViewHolder viewHolder, int i) { 65 | viewHolder.mTextView.setText(mArray[i]); 66 | } 67 | 68 | @Override 69 | public int getItemCount() { 70 | return mArray.length; 71 | } 72 | } 73 | 74 | static class ViewHolder extends RecyclerView.ViewHolder{ 75 | 76 | public TextView mTextView; 77 | 78 | public ViewHolder(View itemView) { 79 | super(itemView); 80 | mTextView = (TextView) itemView; 81 | } 82 | } 83 | 84 | 85 | @Override 86 | public boolean onCreateOptionsMenu(Menu menu) { 87 | getMenuInflater().inflate(R.menu.demo, menu); 88 | return true; 89 | } 90 | 91 | @Override 92 | public boolean onOptionsItemSelected(MenuItem item) { 93 | int id = item.getItemId(); 94 | 95 | switch (id){ 96 | case R.id.action_material: 97 | layout.setRefreshStyle(PullRefreshLayout.STYLE_MATERIAL); 98 | return true; 99 | case R.id.action_circles: 100 | layout.setRefreshStyle(PullRefreshLayout.STYLE_CIRCLES); 101 | return true; 102 | case R.id.action_water_drop: 103 | layout.setRefreshStyle(PullRefreshLayout.STYLE_WATER_DROP); 104 | return true; 105 | case R.id.action_ring: 106 | layout.setRefreshStyle(PullRefreshLayout.STYLE_RING); 107 | return true; 108 | case R.id.action_smartisan: 109 | layout.setRefreshStyle(PullRefreshLayout.STYLE_SMARTISAN); 110 | return true; 111 | } 112 | 113 | return super.onOptionsItemSelected(item); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /sample/src/main/java/com/baoyz/pullrefreshlayout/sample/ScrollViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.baoyz.pullrefreshlayout.sample; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | 7 | import com.baoyz.widget.PullRefreshLayout; 8 | import com.baoyz.widget.SmartisanDrawable; 9 | 10 | public class ScrollViewActivity extends Activity { 11 | 12 | PullRefreshLayout layout; 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_scroll_view); 19 | 20 | layout = (PullRefreshLayout) findViewById(R.id.swipeRefreshLayout); 21 | layout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() { 22 | @Override 23 | public void onRefresh() { 24 | layout.postDelayed(new Runnable() { 25 | @Override 26 | public void run() { 27 | layout.setRefreshing(false); 28 | } 29 | }, 3000); 30 | } 31 | }); 32 | layout.setColorSchemeColors(Color.GRAY); 33 | layout.setRefreshDrawable(new SmartisanDrawable(this, layout)); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baoyongzhang/android-PullRefreshLayout/3b439e1d6762880063eedf4b347eaa1f417d086d/sample/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baoyongzhang/android-PullRefreshLayout/3b439e1d6762880063eedf4b347eaa1f417d086d/sample/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baoyongzhang/android-PullRefreshLayout/3b439e1d6762880063eedf4b347eaa1f417d086d/sample/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baoyongzhang/android-PullRefreshLayout/3b439e1d6762880063eedf4b347eaa1f417d086d/sample/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_demo.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 |