├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pics ├── gif1.gif ├── gif2.gif ├── gif3.gif └── jd.gif ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── leelay │ │ └── refresh │ │ └── vertical │ │ ├── BaseActivity.java │ │ ├── ImageViewActivity.java │ │ ├── ListViewActivity.java │ │ ├── RecyclerViewActivity.java │ │ ├── ScrollViewActivity.java │ │ ├── SeekBarLayout.java │ │ ├── SettingDialog.java │ │ ├── TextViewActivity.java │ │ ├── config │ │ └── Config.java │ │ ├── header │ │ └── JDHeaderView.java │ │ └── utils │ │ └── Densityutils.java │ └── res │ ├── drawable-xhdpi │ ├── jd_package.png │ ├── jd_people01.png │ ├── jd_people02.png │ ├── jd_people03.png │ └── jd_people04.png │ ├── drawable-xxhdpi │ ├── android1.jpg │ └── bizhi.jpeg │ ├── drawable │ └── anim_jd_people.xml │ ├── layout │ ├── activity_imageview.xml │ ├── activity_listview.xml │ ├── activity_recyclerview.xml │ ├── activity_scrollview.xml │ ├── activity_text.xml │ ├── header_jd.xml │ ├── layout_seekbar.xml │ ├── layout_setting.xml │ └── toolbar.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml ├── settings.gradle └── verticalrefreshlayout ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── leelay │ └── freshlayout │ └── verticalre │ ├── DefaultHeaderView.java │ └── VRefreshLayout.java └── res ├── drawable-xxhdpi ├── arrow_down.png ├── arrow_up.png └── okey.png ├── layout └── defult_header_layout.xml └── values └── strings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | /swipe 10 | local.properties -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VRefreshLayout 2 | 一个竖直方向的下拉刷新控件,支持自定义Header,可配置参数,最重要的特点是**刷新时头部置顶显示,且不影响列表滑动**; 3 | 4 | ## 为什么要做头部置顶显示 5 | 现在越来越多的App下拉刷新时是置顶显示的,大家可以看`手机京东`,`天猫商城`,`小米商城`,`汽车之家`等APP,我们在下拉刷新时,头部的刷新视图是保持显示的,这样在滚动列表的时候,用户可以知道当前正在刷新状态,而传统的下拉刷新库,比如[Android-PullToRefresh](https://github.com/chrisbanes/Android-PullToRefresh)和[android-Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh),是不支持的头部置顶显示的,用户往下翻看时,刷新状态就可能被隐藏;google的SwpieRefreshLayout是置顶层显示的,但是它的内容区域的不会跟着动的,且可定制性太差。 6 | 7 | 8 | ## 功能预览 9 | 10 | 头部刷新时列表可以正常响应事件,且头部不会隐藏 11 | 12 | ![](https://github.com/ileelay/VRefreshLayout/blob/master/pics/gif1.gif) 13 | 14 | 自定义头部样式,例如模仿一个京东的下拉刷新动画 15 | 16 | ![](https://github.com/ileelay/VRefreshLayout/blob/master/pics/jd.gif) 17 | 18 | 良好的兼容性,支持ListView/RecyclerView/ScrollView/TextViw/Image等绝大多数view 19 | 20 | ![](https://github.com/ileelay/VRefreshLayout/blob/master/pics/gif2.gif) 21 | 22 | 可以配置下拉高度,阻尼度以及各部分动画时长等参数 23 | 24 | ![](https://github.com/ileelay/VRefreshLayout/blob/master/pics/gif3.gif) 25 | 26 | 27 | ## 简单使用 28 | 29 | - 在布局中,包裹一个将要刷新的View/ViewGroup 30 | ```java 31 | 36 | 37 | 43 | 44 | 45 | 46 | ``` 47 | java代码中,监听和控制刷新状态 48 | ``` 49 | mRefreshLayout = (VRefreshLayout) findViewById(R.id.refresh_layout); 50 | mRefreshLayout.addOnRefreshListener(new VRefreshLayout.OnRefreshListener() { 51 | @Override 52 | public void onRefresh() { 53 | mRefreshLayout.postDelayed(new Runnable() { 54 | @Override 55 | public void run() { 56 | mRefreshLayout.refreshComplete(); 57 | } 58 | }, 2000); 59 | } 60 | }); 61 | 62 | 63 | ``` 64 | 65 | 这样就能在下拉时触发刷新时间,2000ms后刷新完成,隐藏刷新状态; 66 | 67 | ## 方法介绍 68 | 69 | - setDragRate(float dragRate) 设置拖拽的阻尼比例,默认是0.5,一般设置在0-1之间比较合适; 70 | 71 | - setRatioOfHeaderHeightToRefresh(float ratio)  设置允许刷新的高度和HeaderView高度的比例,默认为1.0; 72 | 73 | - setRatioOfHeaderHeightToReach(float ratio) 设置能下拉的最大高度和HeaderView高度的比例,默认为1.6; 74 | 75 | - setToStartDuration(int toStartDuration) 设置回到初始状态动画执行时间,单位为ms,默认是200ms; 76 | 77 | - setToRetainDuration(int toRetainDuration) 设置手指离开屏幕后触发刷新时到刷新保持的位置需要的动画时长,单位为ms,默认为200ms; 78 | 79 | - setAutoRefreshDuration(int autoRefreshDuration) 自动刷新需要的动画时长 ,默认为800ms; 80 | 81 | - setCompleteStickDuration(int completeStickDuration) 刷新完成后状态保持的时长,默认为200ms; 82 | 83 | - setHeaderView(View view) 设置一个自定义的HeaderView; 84 | 85 | - setUpdateHandler(UpdateHandler updateHandler) 设置状态和进度更新处理,一般是Header实现; 86 | 87 | - addOnRefreshListener(OnRefreshListener onRefreshListener) 添加一个刷新回调监听,支持add方式; 88 | 89 | - refreshComplete() 调用次方法,可以接受刷新; 90 | 91 | 92 | ## 自定义HeaderView 93 | 自定义Header只需要集成一个ViewGroup/View,实现VRefreshLayout.UpdateHandler接口即可; 94 | 最基本的代码 95 | ```java 96 | public class CustomHeaderView extends ViewGroup implements VRefreshLayout.UpdateHandler{ 97 | public CustomHeaderView(Context context) { 98 | super(context); 99 | } 100 | 101 | public CustomHeaderView(Context context, AttributeSet attrs) { 102 | super(context, attrs); 103 | } 104 | 105 | @Override 106 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 107 | 108 | } 109 | 110 | @Override 111 | public void onProgressUpdate(VRefreshLayout layout, VRefreshLayout.Progress progress, int status) { 112 | 113 | } 114 | } 115 | ``` 116 | UpdateHandler只有一个方法onProgressUpdate(VRefreshLayout layout, VRefreshLayout.Progress progress, int status),重写它就能监听下拉进度和状态; 117 | ### VRefreshLayout.PProgress 118 | Progress类一个简单的Entity,其中有三个属性,分别是 119 | - totalY 可下拉的最大Y方向距离 120 | - currentY 当前Y方向距离 121 | - refreshY 触发刷新的距离这些 122 | - refreshY 触发刷新的距离 123 | 这些距离的参考点是contentView的Top点; 124 | 125 | ### status状态 126 | ``` 127 | public final static int STATUS_INIT = 0;//原始状态 128 | public final static int STATUS_DRAGGING = 1;//正在下拉 129 | public final static int STATUS_RELEASE_PREPARE = 2;//松手将要刷新 130 | public final static int STATUS_REFRESHING = 3;//正在刷新 131 | public final static int STATUS_RELEASE_CANCEL = 4;//松手取消 132 | public final static int STATUS_COMPLETE = 5;//刷新完成 133 | 134 | ``` 135 | 状态解析: 136 | - STATUS_INIT 原始状态,发生在MOVE事件之前和刷新完成headerView完全隐藏之后 137 | - STATUS_DRAGGING 手指拖拽状态,只有在触发下拉后且手指没有完全离开屏幕,发生在这一时间段内; 138 | - STATUS_RELEASE_PREPARE 拖拽的距离超过可触发刷新的距离,在手指放开到正在刷新之前的这一段时间内; 139 | - STATUS_REFRESHING 正在刷新,调用refreshComplete()结束该状态; 140 | - STATUS_COMPLETE 调用refreshComplete()方法开始,到headerView即将完全隐藏之后,这一点时间内 141 | 这些状态正好形成一个状态循环; 142 | 143 | 144 | ## 参考 145 | - [android-Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh) -liaohuqiu 146 | - [SwipeRefreshLayout](https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html) -google 147 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.2' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | COMPILE_SDK_VERSION=24 15 | BUILD_TOOLS_VERSION=24.0.3 16 | JUNIT_VERSION=4.12 17 | SUPPORT_VERSION=24.2.1 18 | #verticalrefreshlayout 19 | LIB_MINI_SDK_VERSION=11 20 | LIB_TARGET_SDK_VERSION=23 21 | #sample 22 | SAMPLE_MIN_SDK_VERSION=15 23 | SAMPLE_TARGET_SDK_VERSION=23 24 | 25 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /pics/gif1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/pics/gif1.gif -------------------------------------------------------------------------------- /pics/gif2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/pics/gif2.gif -------------------------------------------------------------------------------- /pics/gif3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/pics/gif3.gif -------------------------------------------------------------------------------- /pics/jd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/pics/jd.gif -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION) 5 | buildToolsVersion BUILD_TOOLS_VERSION 6 | defaultConfig { 7 | applicationId "com.leelay.refresh.vertical" 8 | minSdkVersion SAMPLE_MIN_SDK_VERSION 9 | targetSdkVersion SAMPLE_TARGET_SDK_VERSION 10 | versionCode 1 11 | versionName "1.0" 12 | 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 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile "com.android.support:appcompat-v7:$SUPPORT_VERSION" 28 | compile "com.android.support:recyclerview-v7:$SUPPORT_VERSION" 29 | 30 | testCompile "junit:junit:$JUNIT_VERSION" 31 | compile project(":verticalrefreshlayout") 32 | } 33 | -------------------------------------------------------------------------------- /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/leelay/Documents/Android/android-sdk-macosx/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/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical; 2 | 3 | import android.content.DialogInterface; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.ActionBar; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import com.leelay.freshlayout.verticalre.VRefreshLayout; 16 | import com.leelay.refresh.vertical.config.Config; 17 | import com.leelay.refresh.vertical.header.JDHeaderView; 18 | import com.leelay.refresh.vertical.utils.Densityutils; 19 | 20 | /** 21 | * Created by Lilei on 2016. 22 | */ 23 | 24 | public class BaseActivity extends AppCompatActivity { 25 | 26 | private static final String TAG = BaseActivity.class.getSimpleName(); 27 | protected VRefreshLayout mRefreshLayout; 28 | private Toolbar mToolBar; 29 | 30 | private View mJDHeaderView; 31 | 32 | @Override 33 | protected void onPostCreate(@Nullable Bundle savedInstanceState) { 34 | super.onPostCreate(savedInstanceState); 35 | setToolBar(); 36 | mRefreshLayout = (VRefreshLayout) findViewById(R.id.refresh_layout); 37 | if (mRefreshLayout != null) { 38 | mJDHeaderView = new JDHeaderView(this); 39 | mJDHeaderView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp2px(64))); 40 | mRefreshLayout.setBackgroundColor(Color.DKGRAY); 41 | mRefreshLayout.setAutoRefreshDuration(400); 42 | mRefreshLayout.setRatioOfHeaderHeightToReach(1.5f); 43 | mRefreshLayout.addOnRefreshListener(new VRefreshLayout.OnRefreshListener() { 44 | @Override 45 | public void onRefresh() { 46 | mRefreshLayout.postDelayed(new Runnable() { 47 | @Override 48 | public void run() { 49 | mRefreshLayout.refreshComplete(); 50 | } 51 | }, 2000); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | private void setToolBar() { 58 | mToolBar = (Toolbar) findViewById(R.id.toolbar); 59 | setSupportActionBar(mToolBar); 60 | ActionBar supportActionBar = getSupportActionBar(); 61 | supportActionBar.setDisplayHomeAsUpEnabled(!(this instanceof ListViewActivity)); 62 | mToolBar.setNavigationOnClickListener(new View.OnClickListener() { 63 | @Override 64 | public void onClick(View v) { 65 | finish(); 66 | } 67 | }); 68 | supportActionBar.setTitle(getClass().getSimpleName().replace("Activity", "")); 69 | } 70 | 71 | @Override 72 | public boolean onCreateOptionsMenu(Menu menu) { 73 | getMenuInflater().inflate(R.menu.menu_main, menu); 74 | return true; 75 | } 76 | 77 | @Override 78 | public boolean onOptionsItemSelected(MenuItem item) { 79 | int itemId = item.getItemId(); 80 | switch (itemId) { 81 | case R.id.menu_auto: 82 | if (mRefreshLayout != null) 83 | mRefreshLayout.autoRefresh(); 84 | break; 85 | case R.id.menu_setting: 86 | SettingDialog.showDialog(this, new DialogInterface.OnDismissListener() { 87 | @Override 88 | public void onDismiss(DialogInterface dialog) { 89 | refreshConfig(); 90 | } 91 | }); 92 | break; 93 | case R.id.menu_style: 94 | if (mRefreshLayout != null) { 95 | if (mRefreshLayout.getStatus() != VRefreshLayout.STATUS_INIT) { 96 | return false; 97 | } 98 | if (mRefreshLayout.isDefaultHeaderView()) { 99 | mRefreshLayout.setHeaderView(mJDHeaderView); 100 | mRefreshLayout.setBackgroundColor(Color.WHITE); 101 | item.setTitle(R.string.menu_header_default); 102 | } else { 103 | mRefreshLayout.setHeaderView(mRefreshLayout.getDefaultHeaderView()); 104 | mRefreshLayout.setBackgroundColor(Color.DKGRAY); 105 | item.setTitle(R.string.menu_header_jd); 106 | } 107 | } 108 | 109 | break; 110 | } 111 | return false; 112 | } 113 | 114 | 115 | private void refreshConfig() { 116 | if (mRefreshLayout != null) { 117 | mRefreshLayout.postDelayed(new Runnable() { 118 | @Override 119 | public void run() { 120 | Config config = Config.getInstance(); 121 | mRefreshLayout.setDragRate(config.dragRate); 122 | mRefreshLayout.setRatioOfHeaderHeightToReach(config.ratioOfHeaderHeightToReach); 123 | mRefreshLayout.setAutoRefreshDuration(config.autoRefreshDuration); 124 | mRefreshLayout.setToRetainDuration(config.toRetainDuration); 125 | mRefreshLayout.setToStartDuration(config.toStartDuration); 126 | mRefreshLayout.setCompleteStickDuration(config.completeStickDuration); 127 | } 128 | }, 200); 129 | } 130 | } 131 | 132 | protected int dp2px(float dp) { 133 | return Densityutils.dp2px(this, dp); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/ImageViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | 6 | /** 7 | * Created by Lilei on 2016. 8 | */ 9 | 10 | public class ImageViewActivity extends BaseActivity{ 11 | 12 | @Override 13 | protected void onCreate(@Nullable Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_imageview); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/ListViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v4.view.PagerAdapter; 6 | import android.support.v4.view.ViewPager; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.AbsListView; 10 | import android.widget.AdapterView; 11 | import android.widget.ArrayAdapter; 12 | import android.widget.ImageView; 13 | import android.widget.ListView; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.List; 20 | 21 | public class ListViewActivity extends BaseActivity { 22 | 23 | private List mViews; 24 | private List mList; 25 | private ListView mListView; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_listview); 31 | initData(); 32 | initView(); 33 | mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 34 | @Override 35 | public void onItemClick(AdapterView parent, View view, int position, long id) { 36 | if (view instanceof TextView){ 37 | String text = ((TextView) view).getText().toString().trim(); 38 | Class target = null; 39 | try { 40 | target = Class.forName("com.leelay.refresh.vertical." + text + "Activity"); 41 | } catch (ClassNotFoundException e) { 42 | e.printStackTrace(); 43 | } 44 | 45 | if (target != null) { 46 | Intent intent = new Intent(ListViewActivity.this, target); 47 | startActivity(intent); 48 | } else { 49 | Toast.makeText(ListViewActivity.this, ((TextView) view).getText(), Toast.LENGTH_SHORT).show(); 50 | } 51 | } 52 | } 53 | }); 54 | 55 | 56 | } 57 | 58 | private void initView() { 59 | ViewPager viewPager = new ViewPager(this); 60 | viewPager.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, dp2px(200))); 61 | mViews = new ArrayList<>(); 62 | for (int i = 0; i < 3; i++) { 63 | ImageView imageView = new ImageView(this); 64 | imageView.setImageResource(R.drawable.android1); 65 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); 66 | mViews.add(imageView); 67 | } 68 | viewPager.setAdapter(new PagerAdapter() { 69 | @Override 70 | public int getCount() { 71 | return mViews.size(); 72 | } 73 | 74 | @Override 75 | public boolean isViewFromObject(View view, Object object) { 76 | return view == object; 77 | } 78 | 79 | @Override 80 | public Object instantiateItem(ViewGroup container, int position) { 81 | ImageView imageView = mViews.get(position); 82 | container.addView(imageView); 83 | return imageView; 84 | } 85 | 86 | @Override 87 | public void destroyItem(ViewGroup container, int position, Object object) { 88 | container.removeView(mViews.get(position)); 89 | } 90 | }); 91 | mListView = (ListView) findViewById(R.id.listView); 92 | mListView.addHeaderView(viewPager); 93 | mListView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, mList)); 94 | } 95 | 96 | private void initData() { 97 | mList = new ArrayList<>(); 98 | String[] array1 = getResources().getStringArray(R.array.activity_array); 99 | mList.addAll(Arrays.asList(array1)); 100 | String[] array = getResources().getStringArray(R.array.language_array); 101 | mList.addAll(Arrays.asList(array)); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/RecyclerViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by leelay on 2016/12/6. 17 | */ 18 | 19 | public class RecyclerViewActivity extends BaseActivity { 20 | 21 | private RecyclerView mRecyclerView; 22 | private List mList; 23 | 24 | @Override 25 | protected void onCreate(@Nullable Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_recyclerview); 28 | initData(); 29 | mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview); 30 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 31 | mRecyclerView.setAdapter(new MyAdapter()); 32 | } 33 | 34 | private void initData() { 35 | String[] array = getResources().getStringArray(R.array.language_array); 36 | mList = Arrays.asList(array); 37 | } 38 | 39 | class MyAdapter extends RecyclerView.Adapter { 40 | 41 | 42 | @Override 43 | public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 44 | return new MyViewHolder(LayoutInflater.from(RecyclerViewActivity.this).inflate(android.R.layout.simple_list_item_1, parent, false)); 45 | } 46 | 47 | @Override 48 | public void onBindViewHolder(MyViewHolder holder, int position) { 49 | holder.mTextView.setText(mList.get(position)); 50 | } 51 | 52 | @Override 53 | public int getItemCount() { 54 | return mList.size(); 55 | } 56 | 57 | class MyViewHolder extends RecyclerView.ViewHolder { 58 | 59 | TextView mTextView; 60 | 61 | public MyViewHolder(View itemView) { 62 | super(itemView); 63 | mTextView = (TextView) itemView.findViewById(android.R.id.text1); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/ScrollViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.AppCompatTextView; 7 | import android.view.Gravity; 8 | import android.view.ViewGroup; 9 | import android.widget.LinearLayout; 10 | 11 | /** 12 | * Created by Lilei on 2016. 13 | */ 14 | 15 | public class ScrollViewActivity extends BaseActivity { 16 | LinearLayout mLinearLayout; 17 | @Override 18 | protected void onCreate(@Nullable Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_scrollview); 21 | mLinearLayout = (LinearLayout) findViewById(R.id.layout_content); 22 | addChilden(); 23 | } 24 | 25 | private void addChilden() { 26 | String[] array = getResources().getStringArray(R.array.language_array); 27 | for (String a : array) { 28 | AppCompatTextView textView = new AppCompatTextView(this); 29 | textView.setBackgroundColor(Color.WHITE); 30 | textView.setGravity(Gravity.CENTER); 31 | textView.setText(a); 32 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp2px(56)); 33 | layoutParams.bottomMargin = dp2px(1); 34 | mLinearLayout.addView(textView, layoutParams); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/SeekBarLayout.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.AppCompatSeekBar; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.widget.LinearLayout; 8 | import android.widget.SeekBar; 9 | import android.widget.TextView; 10 | 11 | /** 12 | * Created by leelay on 2016/12/6. 13 | */ 14 | 15 | public class SeekBarLayout extends LinearLayout { 16 | 17 | private AppCompatSeekBar mSeekBar; 18 | private TextView mTitle; 19 | private TextView mValue; 20 | 21 | private int gap; 22 | private boolean isF; 23 | 24 | public SeekBarLayout(Context context) { 25 | this(context, null); 26 | } 27 | 28 | public SeekBarLayout(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | LayoutInflater.from(context).inflate(R.layout.layout_seekbar, this, true); 31 | initView(); 32 | } 33 | 34 | public void setTitle(String title) { 35 | this.mTitle.setText(title); 36 | } 37 | 38 | public void setRange(int... range) { 39 | isF = false; 40 | doRange(range); 41 | notifyValue(mSeekBar.getProgress()); 42 | } 43 | 44 | public void setProgress(int progress) { 45 | mSeekBar.setProgress(progress - gap); 46 | } 47 | 48 | public void setProgress(float progress) { 49 | if (isF) 50 | mSeekBar.setProgress((int) (progress * 10) - gap); 51 | else 52 | mSeekBar.setProgress((int) progress - gap); 53 | } 54 | 55 | public void setRange(float... range) { 56 | int[] rangeI = new int[range.length]; 57 | for (int i = 0; i < range.length; i++) { 58 | rangeI[i] = (int) (range[i] * 10); 59 | } 60 | setRange(rangeI); 61 | isF = true; 62 | notifyValue(mSeekBar.getProgress()); 63 | } 64 | 65 | private void doRange(int... range) { 66 | if (range.length < 2) { 67 | throw new RuntimeException("range length must > 2"); 68 | } 69 | int[] numbers = range.clone(); 70 | int start = numbers[0]; 71 | int end = numbers[1]; 72 | 73 | if (end <= start) { 74 | throw new RuntimeException("range end must > start"); 75 | } 76 | gap = start; 77 | mSeekBar.setMax(end - start); 78 | } 79 | 80 | private void initView() { 81 | mSeekBar = (AppCompatSeekBar) findViewById(R.id.seekbar); 82 | mTitle = (TextView) findViewById(R.id.title); 83 | mValue = (TextView) findViewById(R.id.value); 84 | mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 85 | @Override 86 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 87 | notifyValue(progress); 88 | } 89 | 90 | @Override 91 | public void onStartTrackingTouch(SeekBar seekBar) { 92 | 93 | } 94 | 95 | @Override 96 | public void onStopTrackingTouch(SeekBar seekBar) { 97 | 98 | } 99 | }); 100 | 101 | } 102 | 103 | private void notifyValue(int progress) { 104 | if (isF) { 105 | float p = (float) (progress + gap) / 10f; 106 | mValue.setText(String.valueOf(p)); 107 | } else { 108 | mValue.setText(String.valueOf(progress + gap)); 109 | } 110 | } 111 | 112 | public String getProcess() { 113 | return mValue.getText().toString(); 114 | } 115 | 116 | 117 | } 118 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/SettingDialog.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical; 2 | 3 | import android.content.Context; 4 | import android.content.DialogInterface; 5 | import android.support.v7.app.AlertDialog; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | 9 | import com.leelay.refresh.vertical.config.Config; 10 | 11 | /** 12 | * Created by leelay on 2016/12/6. 13 | */ 14 | 15 | public class SettingDialog { 16 | 17 | public static void showDialog(Context context, DialogInterface.OnDismissListener onDismissListener) { 18 | final View view = LayoutInflater.from(context).inflate(R.layout.layout_setting, null, false); 19 | setupView(view); 20 | AlertDialog.Builder builder = new AlertDialog.Builder(context).setView(view); 21 | builder.setTitle("Setting"); 22 | builder.setPositiveButton("YES", new DialogInterface.OnClickListener() { 23 | @Override 24 | public void onClick(DialogInterface dialog, int which) { 25 | ok(view); 26 | } 27 | }); 28 | builder.setNegativeButton("NO", new DialogInterface.OnClickListener() { 29 | @Override 30 | public void onClick(DialogInterface dialog, int which) { 31 | 32 | } 33 | }); 34 | builder.setNeutralButton("reset", new DialogInterface.OnClickListener() { 35 | @Override 36 | public void onClick(DialogInterface dialog, int which) { 37 | Config.getInstance().reset(); 38 | } 39 | }); 40 | AlertDialog alertDialog = builder.create(); 41 | alertDialog.show(); 42 | if (onDismissListener != null) 43 | alertDialog.setOnDismissListener(onDismissListener); 44 | 45 | } 46 | 47 | private static void setupView(View alertDialog) { 48 | Config config = Config.getInstance(); 49 | SeekBarLayout seekBarLayout01 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout01); 50 | seekBarLayout01.setRange(0.2f, 0.8f); 51 | seekBarLayout01.setProgress(config.dragRate); 52 | seekBarLayout01.setTitle("阻力系数"); 53 | 54 | SeekBarLayout seekBarLayout02 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout02); 55 | seekBarLayout02.setRange(1.0f, 3.0f); 56 | seekBarLayout02.setProgress(config.ratioOfHeaderHeightToReach); 57 | seekBarLayout02.setTitle("最大高度比例"); 58 | 59 | SeekBarLayout seekBarLayout03 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout03); 60 | seekBarLayout03.setRange(100, 5000); 61 | seekBarLayout03.setProgress(config.completeStickDuration); 62 | seekBarLayout03.setTitle("头部滞留时长"); 63 | 64 | SeekBarLayout seekBarLayout04 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout04); 65 | seekBarLayout04.setRange(100, 5000); 66 | seekBarLayout04.setProgress(config.autoRefreshDuration); 67 | seekBarLayout04.setTitle("自动下拉时长"); 68 | 69 | SeekBarLayout seekBarLayout05 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout05); 70 | seekBarLayout05.setRange(100, 5000); 71 | seekBarLayout05.setProgress(config.toRetainDuration); 72 | seekBarLayout05.setTitle("回到刷新时长"); 73 | 74 | SeekBarLayout seekBarLayout06 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout06); 75 | seekBarLayout06.setRange(100, 5000); 76 | seekBarLayout06.setProgress(config.toStartDuration); 77 | seekBarLayout06.setTitle("回到顶部时长"); 78 | 79 | } 80 | 81 | private static void ok(View alertDialog) { 82 | Config config = Config.getInstance(); 83 | SeekBarLayout seekBarLayout01 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout01); 84 | config.dragRate = Float.parseFloat(seekBarLayout01.getProcess()); 85 | 86 | SeekBarLayout seekBarLayout02 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout02); 87 | config.ratioOfHeaderHeightToReach = Float.parseFloat(seekBarLayout02.getProcess()); 88 | 89 | SeekBarLayout seekBarLayout03 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout03); 90 | config.completeStickDuration = Integer.parseInt(seekBarLayout03.getProcess()); 91 | SeekBarLayout seekBarLayout04 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout04); 92 | config.autoRefreshDuration = Integer.parseInt(seekBarLayout04.getProcess()); 93 | 94 | SeekBarLayout seekBarLayout05 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout05); 95 | config.toRetainDuration = Integer.parseInt(seekBarLayout05.getProcess()); 96 | 97 | SeekBarLayout seekBarLayout06 = (SeekBarLayout) alertDialog.findViewById(R.id.seekbar_layout06); 98 | config.toStartDuration = Integer.parseInt(seekBarLayout06.getProcess()); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/TextViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | 6 | /** 7 | * Created by Lilei on 2016. 8 | */ 9 | 10 | public class TextViewActivity extends BaseActivity{ 11 | 12 | @Override 13 | protected void onCreate(@Nullable Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_text); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical.config; 2 | 3 | /** 4 | * Created by leelay on 2016/12/6. 5 | */ 6 | 7 | public class Config { 8 | 9 | private static Config config = new Config(); 10 | public float dragRate; 11 | public float ratioOfHeaderHeightToReach; 12 | public int completeStickDuration; 13 | public int autoRefreshDuration; 14 | public int toStartDuration; 15 | public int toRetainDuration; 16 | 17 | private Config() { 18 | reset(); 19 | } 20 | 21 | public static Config getInstance() { 22 | return config; 23 | } 24 | 25 | public void reset() { 26 | dragRate = 0.5f; 27 | ratioOfHeaderHeightToReach = 1.6f; 28 | completeStickDuration = 400; 29 | autoRefreshDuration = 800; 30 | toStartDuration = 200; 31 | toRetainDuration = 200; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/header/JDHeaderView.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical.header; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.AnimationDrawable; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.widget.ImageView; 8 | import android.widget.RelativeLayout; 9 | import android.widget.TextView; 10 | 11 | import com.leelay.freshlayout.verticalre.VRefreshLayout; 12 | import com.leelay.refresh.vertical.R; 13 | import com.leelay.refresh.vertical.utils.Densityutils; 14 | 15 | /** 16 | * Created by Lilei on 2016. 17 | */ 18 | 19 | public class JDHeaderView extends RelativeLayout implements VRefreshLayout.UpdateHandler { 20 | 21 | private ImageView mPeopleIv, mPackageIv; 22 | private TextView mStatusTv; 23 | private AnimationDrawable mAnimationDrawable; 24 | 25 | public JDHeaderView(Context context) { 26 | this(context, null); 27 | } 28 | 29 | public JDHeaderView(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | LayoutInflater.from(context).inflate(R.layout.header_jd, this, true); 32 | mPeopleIv = (ImageView) findViewById(R.id.iv_people); 33 | mPackageIv = (ImageView) findViewById(R.id.iv_package); 34 | mStatusTv = (TextView) findViewById(R.id.tv_status); 35 | } 36 | 37 | @Override 38 | public void onProgressUpdate(VRefreshLayout layout, VRefreshLayout.Progress progress, int status) { 39 | switch (status) { 40 | case VRefreshLayout.STATUS_INIT: 41 | mPeopleIv.setScaleX(0.1f); 42 | mPeopleIv.setScaleY(0.1f); 43 | mPeopleIv.setTranslationX(0); 44 | mPackageIv.setScaleX(0.1f); 45 | mPackageIv.setScaleY(0.1f); 46 | mPeopleIv.setImageResource(R.drawable.jd_people01); 47 | mStatusTv.setText(R.string.pull_to_refresh); 48 | if (mAnimationDrawable != null) { 49 | mAnimationDrawable.stop(); 50 | mAnimationDrawable = null; 51 | } 52 | 53 | break; 54 | case VRefreshLayout.STATUS_DRAGGING: 55 | float currentY = progress.getCurrentY(); 56 | float refreshY = progress.getRefreshY(); 57 | float percent = Math.min(1.0f, currentY / refreshY); 58 | mPackageIv.setScaleX(percent); 59 | mPackageIv.setScaleY(percent); 60 | mPeopleIv.setScaleX(percent); 61 | mPeopleIv.setScaleY(percent); 62 | mPeopleIv.setTranslationX(percent * Densityutils.dp2px(getContext(), 18f)); 63 | mPackageIv.setVisibility(VISIBLE); 64 | if (percent >= 1.0f) { 65 | mStatusTv.setText(R.string.release_to_refresh); 66 | } else { 67 | mStatusTv.setText(R.string.pull_to_refresh); 68 | } 69 | break; 70 | case VRefreshLayout.STATUS_RELEASE_PREPARE: 71 | case VRefreshLayout.STATUS_REFRESHING: 72 | if (mAnimationDrawable == null) { 73 | mStatusTv.setText(R.string.refreshing); 74 | mPackageIv.setVisibility(INVISIBLE); 75 | mPeopleIv.setImageResource(R.drawable.anim_jd_people); 76 | mAnimationDrawable = (AnimationDrawable) mPeopleIv.getDrawable(); 77 | mAnimationDrawable.start(); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /sample/src/main/java/com/leelay/refresh/vertical/utils/Densityutils.java: -------------------------------------------------------------------------------- 1 | package com.leelay.refresh.vertical.utils; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Created by Lilei on 2016. 7 | */ 8 | 9 | public class Densityutils { 10 | 11 | public static int dp2px(Context context, float dp) { 12 | return (int) (context.getResources().getDisplayMetrics().density * dp); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/jd_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/drawable-xhdpi/jd_package.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/jd_people01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/drawable-xhdpi/jd_people01.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/jd_people02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/drawable-xhdpi/jd_people02.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/jd_people03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/drawable-xhdpi/jd_people03.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/jd_people04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/drawable-xhdpi/jd_people04.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/android1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/drawable-xxhdpi/android1.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/bizhi.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/drawable-xxhdpi/bizhi.jpeg -------------------------------------------------------------------------------- /sample/src/main/res/drawable/anim_jd_people.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_imageview.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 12 | 13 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_listview.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 16 | 17 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_recyclerview.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_scrollview.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 15 | 16 | 20 | 21 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 14 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/header_jd.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 18 | 19 | 25 | 26 | 27 | 35 | 36 | 44 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/layout_seekbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/layout_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 26 | 27 | 32 | 33 | 38 | 39 | 44 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #444444 7 | 8 | -------------------------------------------------------------------------------- /sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /sample/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | VRefreshLayout 3 | 京东Style 4 | 默认Style 5 | 6 | Java 7 | C 8 | C++ 9 | C# 10 | Python 11 | JavaScript 12 | PHP 13 | Perl 14 | Objective-C 15 | Swift 16 | Ruby 17 | Visual Basic 18 | Delphi 19 | Go 20 | Groovy 21 | R 22 | Matlab 23 | PL/SQL 24 | 25 | 26 | 27 | ScrollView 28 | TextView 29 | ImageView 30 | RecyclerView 31 | 32 | 33 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample', ':verticalrefreshlayout' 2 | -------------------------------------------------------------------------------- /verticalrefreshlayout/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /verticalrefreshlayout/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION) 5 | buildToolsVersion BUILD_TOOLS_VERSION 6 | 7 | defaultConfig { 8 | minSdkVersion LIB_MINI_SDK_VERSION 9 | targetSdkVersion LIB_TARGET_SDK_VERSION 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | compile "com.android.support:appcompat-v7:$SUPPORT_VERSION" 24 | testCompile "junit:junit:$JUNIT_VERSION" 25 | } 26 | -------------------------------------------------------------------------------- /verticalrefreshlayout/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/leelay/Documents/Android/android-sdk-macosx/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 | -------------------------------------------------------------------------------- /verticalrefreshlayout/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /verticalrefreshlayout/src/main/java/com/leelay/freshlayout/verticalre/DefaultHeaderView.java: -------------------------------------------------------------------------------- 1 | package com.leelay.freshlayout.verticalre; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.support.annotation.RequiresApi; 6 | import android.util.AttributeSet; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.widget.ImageView; 10 | import android.widget.RelativeLayout; 11 | import android.widget.TextView; 12 | 13 | 14 | /** 15 | * Created by Lilei on 2016. 16 | */ 17 | 18 | public class DefaultHeaderView extends RelativeLayout implements VRefreshLayout.UpdateHandler { 19 | 20 | private final ImageView mImageView; 21 | private final View mProgress; 22 | private TextView mTextView; 23 | private static final String TAG = "DefultHeaderView"; 24 | 25 | public DefaultHeaderView(Context context) { 26 | this(context, null); 27 | } 28 | 29 | public DefaultHeaderView(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | LayoutInflater.from(context).inflate(R.layout.defult_header_layout, this, true); 32 | mTextView = (TextView) findViewById(R.id.text); 33 | mImageView = (ImageView) findViewById(R.id.image); 34 | mProgress = findViewById(R.id.progress); 35 | } 36 | 37 | //箭头是否朝上 38 | private boolean arrowUp; 39 | 40 | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) 41 | @Override 42 | public void onProgressUpdate(VRefreshLayout layout, VRefreshLayout.Progress progress, int status) { 43 | switch (status) { 44 | case VRefreshLayout.STATUS_INIT: 45 | mImageView.setVisibility(VISIBLE); 46 | mImageView.setImageResource(R.drawable.arrow_down); 47 | mProgress.setVisibility(GONE); 48 | break; 49 | 50 | case VRefreshLayout.STATUS_DRAGGING: 51 | if (progress.getCurrentY() >= progress.getRefreshY()) { 52 | if (arrowUp) { 53 | arrowUp = false; 54 | mTextView.setText(R.string.release_to_refresh); 55 | mImageView.setImageResource(R.drawable.arrow_up); 56 | } 57 | 58 | } else { 59 | if (!arrowUp) { 60 | arrowUp = true; 61 | mTextView.setText(R.string.pull_to_refresh); 62 | mImageView.setImageResource(R.drawable.arrow_down); 63 | } 64 | } 65 | break; 66 | case VRefreshLayout.STATUS_RELEASE_PREPARE: 67 | mTextView.setText(R.string.begin_refresh); 68 | mImageView.setVisibility(GONE); 69 | break; 70 | case VRefreshLayout.STATUS_REFRESHING: 71 | mTextView.setText(R.string.refreshing); 72 | mImageView.setImageResource(android.R.drawable.btn_dropdown); 73 | mProgress.setVisibility(VISIBLE); 74 | break; 75 | case VRefreshLayout.STATUS_COMPLETE: 76 | mTextView.setText(R.string.refresh_complete); 77 | mProgress.setVisibility(GONE); 78 | mImageView.setVisibility(VISIBLE); 79 | mImageView.setImageResource(R.drawable.okey); 80 | break; 81 | case VRefreshLayout.STATUS_RELEASE_CANCEL: 82 | mTextView.setText(R.string.cancel_refresh); 83 | break; 84 | } 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /verticalrefreshlayout/src/main/java/com/leelay/freshlayout/verticalre/VRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.leelay.freshlayout.verticalre; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.support.annotation.LayoutRes; 8 | import android.support.v4.view.MotionEventCompat; 9 | import android.support.v4.view.ViewCompat; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import android.util.TypedValue; 13 | import android.view.LayoutInflater; 14 | import android.view.MotionEvent; 15 | import android.view.View; 16 | import android.view.ViewConfiguration; 17 | import android.view.ViewGroup; 18 | import android.widget.AbsListView; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * 支持垂直下拉刷新的Layout 25 | * Created by leelay on 2016/12/3. 26 | */ 27 | 28 | public class VRefreshLayout extends ViewGroup { 29 | 30 | public final static int STATUS_INIT = 0;//原始状态 31 | public final static int STATUS_DRAGGING = 1;//正在下拉 32 | public final static int STATUS_RELEASE_PREPARE = 2;//松手将要刷新 33 | public final static int STATUS_REFRESHING = 3;//正在刷新 34 | public final static int STATUS_RELEASE_CANCEL = 4;//松手取消 35 | public final static int STATUS_COMPLETE = 5;//刷新完成 36 | private static final String TAG = "VRefreshLayout"; 37 | private int mStatus; 38 | 39 | private View mHeaderView; 40 | 41 | private View mContentView; 42 | 43 | private int mHeaderOrginTop; 44 | 45 | private int mMaxDragDistance = -1; 46 | private float mDragRate = .5f; 47 | private int mHeaderCurrentTop; 48 | private int mHeaderLayoutIndex = -1; 49 | private boolean mIsInitMesure = true; 50 | private boolean mIsBeingDragged; 51 | private float mInitDownY; 52 | private float mInitMotionY; 53 | private boolean mIsRefreshing; 54 | private int mActivePointerId = -1; 55 | private float mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 56 | private Progress mProgress = new Progress(); 57 | private int mRefreshDistance; 58 | private int mToStartDuration = 200; 59 | private int mToRetainDuration = 200; 60 | private int mAutoRefreshDuration = 800; 61 | private int mCompleteStickDuration = 400; 62 | private float ratioOfHeaderHeightToRefresh = 1.0f; 63 | private float ratioOfHeaderHeightToReach = 1.6f; 64 | private List mOnRefreshListeners; 65 | private UpdateHandler mUpdateHandler; 66 | private DefaultHeaderView mDefaultHeaderView; 67 | 68 | public VRefreshLayout(Context context) { 69 | this(context, null); 70 | } 71 | 72 | public VRefreshLayout(Context context, AttributeSet attrs) { 73 | super(context, attrs); 74 | setDefaultHeaderView(); 75 | setChildrenDrawingOrderEnabled(true); 76 | } 77 | 78 | public void setDragRate(float dragRate) { 79 | mDragRate = dragRate; 80 | } 81 | 82 | 83 | public int getStatus() { 84 | return mStatus; 85 | } 86 | 87 | public void setRatioOfHeaderHeightToRefresh(float ratio) { 88 | ratio = Math.max(ratio, 1.0f); 89 | this.ratioOfHeaderHeightToRefresh = ratio; 90 | this.ratioOfHeaderHeightToReach = Math.max(ratioOfHeaderHeightToRefresh, ratioOfHeaderHeightToReach); 91 | if (mHeaderView.getMeasuredHeight() > 0) { 92 | setRefreshDistance((int) (mHeaderView.getMeasuredHeight() * ratioOfHeaderHeightToRefresh)); 93 | setMaxDragDistance((int) (mHeaderView.getMeasuredHeight() * ratioOfHeaderHeightToReach)); 94 | } 95 | } 96 | 97 | public void setRatioOfHeaderHeightToReach(float ratio) { 98 | ratio = Math.max(Math.max(ratio, 1.0f), ratioOfHeaderHeightToRefresh); 99 | this.ratioOfHeaderHeightToReach = ratio; 100 | if (mHeaderView.getMeasuredHeight() > 0) { 101 | setMaxDragDistance((int) (mHeaderView.getMeasuredHeight() * ratioOfHeaderHeightToReach)); 102 | } 103 | } 104 | 105 | public void setToStartDuration(int toStartDuration) { 106 | mToStartDuration = toStartDuration; 107 | } 108 | 109 | public void setToRetainDuration(int toRetainDuration) { 110 | mToRetainDuration = toRetainDuration; 111 | } 112 | 113 | public void setAutoRefreshDuration(int autoRefreshDuration) { 114 | mAutoRefreshDuration = autoRefreshDuration; 115 | } 116 | 117 | public void setCompleteStickDuration(int completeStickDuration) { 118 | mCompleteStickDuration = completeStickDuration; 119 | } 120 | 121 | private void setMaxDragDistance(int distance) { 122 | mMaxDragDistance = distance; 123 | mProgress.totalY = distance; 124 | } 125 | 126 | private void setRefreshDistance(int distance) { 127 | mRefreshDistance = distance; 128 | mProgress.refreshY = mRefreshDistance; 129 | } 130 | 131 | public boolean isDefaultHeaderView() { 132 | return mHeaderView == mDefaultHeaderView; 133 | } 134 | 135 | public View getDefaultHeaderView() { 136 | if (mDefaultHeaderView == null) { 137 | setDefaultHeaderView(); 138 | } 139 | return mDefaultHeaderView; 140 | } 141 | 142 | private void setDefaultHeaderView() { 143 | mDefaultHeaderView = new DefaultHeaderView(getContext()); 144 | mDefaultHeaderView.setPadding(0, dp2px(10), 0, dp2px(10)); 145 | mDefaultHeaderView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, dp2px(64))); 146 | setHeaderView(mDefaultHeaderView); 147 | } 148 | 149 | @Override 150 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 151 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 152 | Log.e(TAG, "onMeasure: "); 153 | ensureContent(); 154 | //measure contentView 155 | if (mContentView != null) { 156 | int contentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); 157 | int contentHeight = getMeasuredHeight() - getPaddingTop() + getPaddingBottom(); 158 | mContentView.measure(MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY)); 159 | } 160 | 161 | //measure headerView 162 | if (mHeaderView != null) { 163 | measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); 164 | if (mIsInitMesure) { 165 | int measuredHeight = mHeaderView.getMeasuredHeight(); 166 | mHeaderOrginTop = mHeaderCurrentTop = -measuredHeight; 167 | setMaxDragDistance((int) (measuredHeight * ratioOfHeaderHeightToReach)); 168 | setRefreshDistance((int) (measuredHeight * ratioOfHeaderHeightToRefresh)); 169 | mIsInitMesure = false; 170 | } 171 | } 172 | mHeaderLayoutIndex = -1; 173 | for (int i = 0; i < getChildCount(); i++) { 174 | if (mHeaderView == getChildAt(i)) { 175 | mHeaderLayoutIndex = i; 176 | } 177 | } 178 | } 179 | 180 | private void ensureContent() { 181 | if (mContentView == null) { 182 | for (int i = 0; i < getChildCount(); i++) { 183 | View childAt = getChildAt(i); 184 | if (childAt != mHeaderView) { 185 | mContentView = childAt; 186 | break; 187 | } 188 | } 189 | } 190 | } 191 | 192 | @Override 193 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 194 | 195 | if (mContentView == null) { 196 | ensureContent(); 197 | } 198 | 199 | int paddingLeft = getPaddingLeft(); 200 | int paddingTop = getPaddingTop(); 201 | Log.e(TAG, "onLayout: mHeaderCurrentTop" + mHeaderCurrentTop); 202 | //layout headerView 203 | if (mHeaderView != null) { 204 | mHeaderView.layout(paddingLeft, mHeaderCurrentTop, paddingLeft + mHeaderView.getMeasuredWidth(), mHeaderCurrentTop + mHeaderView.getMeasuredHeight()); 205 | } 206 | 207 | //layout contentView 208 | if (mContentView != null) { 209 | int distance = mHeaderCurrentTop - mHeaderOrginTop; 210 | int contentHeight = mContentView.getMeasuredHeight(); 211 | int contentWidth = mContentView.getMeasuredWidth(); 212 | int left = paddingLeft; 213 | int top = paddingTop + distance; 214 | int right = left + contentWidth; 215 | int bottom = top + contentHeight; 216 | mContentView.layout(left, top, right, bottom); 217 | } 218 | Log.e(TAG, "onLayout: "); 219 | } 220 | 221 | @Override 222 | protected int getChildDrawingOrder(int childCount, int i) { 223 | if (mHeaderLayoutIndex < 0) { 224 | return i; 225 | } else if (i == childCount - 1) { 226 | return mHeaderLayoutIndex; 227 | } else if (i >= mHeaderLayoutIndex) { 228 | return i + 1; 229 | } else { 230 | return i; 231 | } 232 | } 233 | 234 | @Override 235 | public boolean onInterceptTouchEvent(MotionEvent ev) { 236 | ensureContent(); 237 | int action = MotionEventCompat.getActionMasked(ev); 238 | if (!isEnabled() || canChildScrollUp() || mIsRefreshing) { 239 | return false; 240 | } 241 | int pointerIndex; 242 | switch (action) { 243 | case MotionEvent.ACTION_DOWN: 244 | Log.e(TAG, "onInterceptTouchEvent: ACTION_DOWN"); 245 | mIsBeingDragged = false; 246 | notifyStatus(STATUS_INIT); 247 | mActivePointerId = ev.getPointerId(0); 248 | pointerIndex = ev.findPointerIndex(mActivePointerId); 249 | if (pointerIndex < 0) { 250 | return false; 251 | } 252 | mInitDownY = ev.getY(pointerIndex); 253 | break; 254 | 255 | case MotionEvent.ACTION_MOVE: 256 | Log.e(TAG, "onInterceptTouchEvent: ACTION_MOVE"); 257 | pointerIndex = ev.findPointerIndex(mActivePointerId); 258 | if (pointerIndex < 0) { 259 | return false; 260 | } 261 | float evY = ev.getY(pointerIndex); 262 | checkDragging(evY); 263 | break; 264 | 265 | case MotionEventCompat.ACTION_POINTER_UP: 266 | Log.e(TAG, "onInterceptTouchEvent: ACTION_POINTER_UP"); 267 | checkOtherPointerUp(ev); 268 | break; 269 | 270 | case MotionEvent.ACTION_UP: 271 | case MotionEvent.ACTION_CANCEL: 272 | Log.e(TAG, "onInterceptTouchEvent: ACTION_DOWN"); 273 | mIsBeingDragged = false; 274 | mActivePointerId = -1; 275 | break; 276 | } 277 | 278 | 279 | return mIsBeingDragged; 280 | } 281 | 282 | private void checkDragging(float y) { 283 | float dy = y - mInitDownY; 284 | mInitMotionY = mInitDownY + mTouchSlop; 285 | if (dy > mTouchSlop && !mIsBeingDragged) { 286 | mIsBeingDragged = true; 287 | } 288 | } 289 | 290 | private void checkOtherPointerUp(MotionEvent ev) { 291 | int pointIndex = MotionEventCompat.getActionIndex(ev); 292 | int pointerId = ev.getPointerId(pointIndex); 293 | if (pointerId == mActivePointerId) { 294 | int newPointIndex = pointIndex == 0 ? 1 : 0; 295 | mActivePointerId = ev.getPointerId(newPointIndex); 296 | } 297 | } 298 | 299 | @Override 300 | public boolean onTouchEvent(MotionEvent ev) { 301 | if (!isEnabled() || canChildScrollUp() || mIsRefreshing) { 302 | return false; 303 | } 304 | int action = MotionEventCompat.getActionMasked(ev); 305 | int pointerIndex; 306 | switch (action) { 307 | case MotionEvent.ACTION_DOWN: 308 | Log.e(TAG, "onTouchEvent: ACTION_DOWN"); 309 | mIsBeingDragged = false; 310 | notifyStatus(STATUS_INIT); 311 | mActivePointerId = ev.getPointerId(0); 312 | pointerIndex = ev.findPointerIndex(mActivePointerId); 313 | if (pointerIndex < 0) { 314 | return false; 315 | } 316 | mInitDownY = ev.getY(pointerIndex); 317 | break; 318 | 319 | case MotionEvent.ACTION_MOVE: 320 | Log.e(TAG, "onTouchEvent: ACTION_MOVE"); 321 | pointerIndex = ev.findPointerIndex(mActivePointerId); 322 | if (pointerIndex < 0) { 323 | return false; 324 | } 325 | float evY = ev.getY(pointerIndex); 326 | checkDragging(evY); 327 | if (mIsBeingDragged) { 328 | float dy = (evY - mInitMotionY) * mDragRate; 329 | if (dy > 0) { 330 | actionMoving(dy); 331 | } 332 | notifyStatus(STATUS_DRAGGING); 333 | } 334 | break; 335 | 336 | case MotionEvent.ACTION_POINTER_DOWN: 337 | Log.e(TAG, "onTouchEvent: ACTION_POINTER_DOWN"); 338 | pointerIndex = MotionEventCompat.getActionIndex(ev); 339 | if (pointerIndex < 0) { 340 | return false; 341 | } 342 | mActivePointerId = ev.getPointerId(pointerIndex); 343 | break; 344 | 345 | case MotionEventCompat.ACTION_POINTER_UP: 346 | Log.e(TAG, "onTouchEvent: ACTION_POINTER_UP"); 347 | checkOtherPointerUp(ev); 348 | break; 349 | 350 | case MotionEvent.ACTION_UP: 351 | Log.e(TAG, "onTouchEvent: ACTION_UP"); 352 | pointerIndex = ev.findPointerIndex(mActivePointerId); 353 | if (pointerIndex < 0) { 354 | return false; 355 | } 356 | float upY = ev.getY(pointerIndex); 357 | if (mIsBeingDragged) { 358 | float upDy = (upY - mInitMotionY) * mDragRate; 359 | actionUp(upDy); 360 | mIsBeingDragged = false; 361 | } 362 | mActivePointerId = -1; 363 | 364 | break; 365 | case MotionEvent.ACTION_CANCEL: 366 | Log.e(TAG, "onTouchEvent: ACTION_CANCEL"); 367 | return false; 368 | } 369 | return true; 370 | 371 | } 372 | 373 | private void actionUp(float dy) { 374 | Log.e(TAG, "actionUp: " + dy); 375 | if (dy < mRefreshDistance) { 376 | //cancel 377 | animOffsetToStartPos(); 378 | mIsRefreshing = false; 379 | notifyStatus(STATUS_RELEASE_CANCEL); 380 | } else { 381 | animOffsetToRetainPos(); 382 | mIsRefreshing = true; 383 | notifyStatus(STATUS_RELEASE_PREPARE); 384 | } 385 | } 386 | 387 | private void moveAnimation(int star, int end, int duration, Animator.AnimatorListener animatorListener) { 388 | ValueAnimator valueAnimator = ValueAnimator.ofInt(star, end); 389 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 390 | @Override 391 | public void onAnimationUpdate(ValueAnimator animation) { 392 | int value = (int) animation.getAnimatedValue(); 393 | moveTo(value); 394 | } 395 | }); 396 | if (animatorListener != null) { 397 | valueAnimator.addListener(animatorListener); 398 | } 399 | valueAnimator.setDuration(duration); 400 | valueAnimator.start(); 401 | } 402 | 403 | private void animOffsetToRetainPos() { 404 | final int from = mHeaderCurrentTop = mHeaderView.getTop(); 405 | int to = getPaddingTop() + mRefreshDistance - mHeaderView.getMeasuredHeight(); 406 | moveAnimation(from, to, mToRetainDuration, new AnimatorListenerAdapter() { 407 | @Override 408 | public void onAnimationEnd(Animator animation) { 409 | super.onAnimationEnd(animation); 410 | notifyRefreshListeners(); 411 | notifyStatus(STATUS_REFRESHING); 412 | } 413 | }); 414 | } 415 | 416 | private void animOffsetAutoRefresh() { 417 | mHeaderCurrentTop = mHeaderView.getTop(); 418 | final int from = mHeaderCurrentTop; 419 | int to = mMaxDragDistance + mHeaderOrginTop; 420 | moveAnimation(from, to, mAutoRefreshDuration, new AnimatorListenerAdapter() { 421 | @Override 422 | public void onAnimationEnd(Animator animation) { 423 | super.onAnimationEnd(animation); 424 | notifyStatus(STATUS_RELEASE_PREPARE); 425 | animOffsetToRetainPos(); 426 | } 427 | 428 | @Override 429 | public void onAnimationStart(Animator animation) { 430 | super.onAnimationStart(animation); 431 | notifyStatus(STATUS_DRAGGING); 432 | } 433 | }); 434 | } 435 | 436 | private void animOffsetToStartPos() { 437 | final int from = mHeaderCurrentTop = mHeaderView.getTop(); 438 | int to = mHeaderOrginTop; 439 | moveAnimation(from, to, mToStartDuration, new AnimatorListenerAdapter() { 440 | @Override 441 | public void onAnimationEnd(Animator animation) { 442 | super.onAnimationEnd(animation); 443 | mIsRefreshing = false; 444 | notifyStatus(STATUS_INIT); 445 | } 446 | }); 447 | } 448 | 449 | private void actionMoving(float y) { 450 | y = Math.min(y, mMaxDragDistance); 451 | if (y <= mMaxDragDistance) { 452 | int targetY = (int) (mHeaderOrginTop + y); 453 | moveTo(targetY); 454 | } 455 | } 456 | 457 | public void setHeaderView(View view) { 458 | if (view == null || view == mHeaderView) { 459 | return; 460 | } 461 | LayoutParams layoutParams = view.getLayoutParams(); 462 | if (layoutParams == null) { 463 | layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 464 | view.setLayoutParams(layoutParams); 465 | } 466 | removeView(mHeaderView); 467 | mHeaderView = view; 468 | mIsInitMesure = true; 469 | this.addView(mHeaderView); 470 | if (view instanceof UpdateHandler) { 471 | setUpdateHandler((UpdateHandler) view); 472 | } 473 | } 474 | 475 | public void setHeaderView(@LayoutRes int redId) { 476 | View view = LayoutInflater.from(getContext()).inflate(redId, this, false); 477 | setHeaderView(view); 478 | } 479 | 480 | private int dp2px(int dp) { 481 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); 482 | } 483 | 484 | public boolean canChildScrollUp() { 485 | if (android.os.Build.VERSION.SDK_INT < 14) { 486 | if (mContentView instanceof AbsListView) { 487 | final AbsListView absListView = (AbsListView) mContentView; 488 | return absListView.getChildCount() > 0 489 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 490 | .getTop() < absListView.getPaddingTop()); 491 | } else { 492 | return ViewCompat.canScrollVertically(mContentView, -1) || mContentView.getScrollY() > 0; 493 | } 494 | } else { 495 | return ViewCompat.canScrollVertically(mContentView, -1); 496 | } 497 | } 498 | 499 | public void refreshComplete() { 500 | if (mIsRefreshing) { 501 | notifyStatus(STATUS_COMPLETE); 502 | postDelayed(new Runnable() { 503 | @Override 504 | public void run() { 505 | animOffsetToStartPos(); 506 | } 507 | }, mCompleteStickDuration); 508 | } 509 | } 510 | 511 | public void autoRefresh() { 512 | if (!mIsRefreshing) { 513 | mIsRefreshing = true; 514 | animOffsetAutoRefresh(); 515 | } 516 | } 517 | 518 | private void moveTo(int y) { 519 | int dy = y - mHeaderCurrentTop; 520 | ViewCompat.offsetTopAndBottom(mHeaderView, dy); 521 | ViewCompat.offsetTopAndBottom(mContentView, dy); 522 | mHeaderCurrentTop = mHeaderView.getTop(); 523 | mProgress.currentY = mHeaderCurrentTop - mHeaderOrginTop; 524 | notifyProgress(); 525 | } 526 | 527 | private void notifyProgress() { 528 | if (mUpdateHandler != null) { 529 | mUpdateHandler.onProgressUpdate(this, mProgress, mStatus); 530 | } 531 | } 532 | 533 | private void notifyStatus(int status) { 534 | mStatus = status; 535 | if (mUpdateHandler != null) { 536 | mUpdateHandler.onProgressUpdate(this, mProgress, mStatus); 537 | } 538 | } 539 | 540 | public void addOnRefreshListener(OnRefreshListener onRefreshListener) { 541 | if (mOnRefreshListeners == null) { 542 | mOnRefreshListeners = new ArrayList<>(); 543 | } 544 | mOnRefreshListeners.add(onRefreshListener); 545 | } 546 | 547 | private void notifyRefreshListeners() { 548 | if (mOnRefreshListeners == null || mOnRefreshListeners.isEmpty()) { 549 | return; 550 | } 551 | for (OnRefreshListener onRefreshListener : mOnRefreshListeners) { 552 | onRefreshListener.onRefresh(); 553 | } 554 | } 555 | 556 | public void setUpdateHandler(UpdateHandler updateHandler) { 557 | mUpdateHandler = updateHandler; 558 | } 559 | 560 | public interface OnRefreshListener { 561 | void onRefresh(); 562 | } 563 | 564 | 565 | public interface UpdateHandler { 566 | void onProgressUpdate(VRefreshLayout layout, Progress progress, int status); 567 | } 568 | 569 | public static class Progress { 570 | private int totalY; 571 | private int currentY; 572 | private int refreshY; 573 | 574 | public int getRefreshY() { 575 | return refreshY; 576 | } 577 | 578 | public int getTotalY() { 579 | return totalY; 580 | } 581 | 582 | public int getCurrentY() { 583 | return currentY; 584 | } 585 | } 586 | 587 | 588 | } 589 | -------------------------------------------------------------------------------- /verticalrefreshlayout/src/main/res/drawable-xxhdpi/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/verticalrefreshlayout/src/main/res/drawable-xxhdpi/arrow_down.png -------------------------------------------------------------------------------- /verticalrefreshlayout/src/main/res/drawable-xxhdpi/arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/verticalrefreshlayout/src/main/res/drawable-xxhdpi/arrow_up.png -------------------------------------------------------------------------------- /verticalrefreshlayout/src/main/res/drawable-xxhdpi/okey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HitenDev/VRefreshLayout/17468b0652df543a59f72882f8f21cdd77aea3d9/verticalrefreshlayout/src/main/res/drawable-xxhdpi/okey.png -------------------------------------------------------------------------------- /verticalrefreshlayout/src/main/res/layout/defult_header_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 14 | 15 | 23 | 24 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /verticalrefreshlayout/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | VerticalRefreshLayout 3 | 松手刷新 4 | 下拉刷新 5 | 即将刷新 6 | 刷新中 7 | 刷新完成 8 | 取消刷新 9 | 10 | --------------------------------------------------------------------------------