├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app.gif ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yazhi │ │ └── swipelayout │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yazhi │ │ │ └── swipelayout │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── item.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── yazhi │ └── swipelayout │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── superswiperefreshlayout ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── project.properties └── src ├── androidTest └── java │ └── com │ └── yazhi │ └── superswiperefreshlayout │ └── ApplicationTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── yazhi │ │ └── superswiperefreshlayout │ │ ├── CircleImageView.java │ │ ├── MaterialProgressDrawable.java │ │ └── SuperSwipeRefreshLayout.java └── res │ ├── layout │ └── item_null.xml │ └── values │ ├── attr.xml │ └── strings.xml └── test └── java └── com └── yazhi └── superswiperefreshlayout └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | SwipeLayout -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SuperSwipeRefreshLayout 2 | 3 | ## 简介 4 | 5 | - 基于Google官网出品[SwipeRrefreshLayout](http://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html)优化 6 | - 支持下拉刷新、上拉刷新和一进入页面就加载刷新,并保持加载动画的一致性 7 | 8 | ![image](https://github.com/yazhi1992/SuperSwipeRefreshLayout/blob/master/app.gif) 9 | 10 | ## 使用Gradle构建 11 | 12 | 在你moudle项目的build.gradle**(Moudle:你的项目)**里添加(注意:不是project的build.gradle) 13 | 14 | dependencies { 15 | compile 'com.yazhi:superswiperefreshlayout:1.1.2 16 | } 17 | 18 | ## 使用方法 19 | 20 | ###XML布局中 21 | 22 | **如果使用自定义属性请记住要加上xmlns:app="http://schemas.android.com/apk/res-auto"** 23 | 24 | SuperSwipeRefreshLayout布局内只能有一个直接子View,这个子view要求必须是可以滑动的,例如scrollview,listview,如果子view直接放relativelayout等不能滑动的控件时下拉刷新会出现问题。 25 | 26 | 如果设置了允许上拉刷新,因为考虑到只有在列表的情况下才会使用分页功能,因此只有在子view是listview时上拉刷新才有效。 27 | 28 | 29 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | ###根据回调的参数区分是源自哪类刷新操作 49 | **关闭上拉刷新动画与其他两类不同,是setUpRefreshing(false)** 50 | 51 | 52 | SuperSwipeRefreshLayout layout = (SuperSwipeRefreshLayout) findViewById(R.id.layout); 53 | 54 | layout.setOnRefreshListener(new SuperSwipeRefreshLayout.OnRefreshListener() { 55 | @Override 56 | public void onRefresh(int type) { 57 | switch (type) { 58 | case 0: //type = 0:进入界面时加载的操作 refresh when activity was created 59 | 60 | //TODO 61 | 62 | //业务执行完毕要关闭启动加载刷新动画 finish animation 63 | layout.setRefreshing(false); 64 | break; 65 | 66 | case 1: //type = 1:下拉刷新 pull down to refresh 67 | 68 | //TODO 69 | 70 | //业务执行完毕要关闭下拉拉刷新动画 finish animation 71 | layout.setRefreshing(false); 72 | break; 73 | 74 | case 2: //type =2: 上拉刷新 pull up to refresh 75 | 76 | //TODO 77 | 78 | //业务执行完毕要关闭上拉刷新动画 finish animation 79 | layout.setUpRefreshing(false); 80 | break; 81 | 82 | default: 83 | break; 84 | } 85 | } 86 | }); 87 | 88 | ###另外提一下两个常用的方法: 89 | 90 | //设置下拉刷新时加载动画的位置 91 | layout.setProgressViewOffset(false, 0, 48); 92 | //设置加载动画的颜色,最多可设置四种 93 | layout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light); 94 | 95 | 因为这个控件是基于官方swiperefreshlayout改动的,所以除了新增的功能和属性、方法,其他与原版一致,更多具体的使用方法请参考 96 | [SwipeRefreshLayout官方文档](http://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html)。 97 | 98 | 99 | ##最后 100 | 101 | 我虽热爱分享,但毕竟才疏学浅难免会有错误之处,敬请谅解。欢迎提issues反馈意见,我会尽我所能地去解决问题。谢谢。 102 | 103 | 互联网精神万岁。 104 | -------------------------------------------------------------------------------- /app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yazhidev/SuperSwipeRefreshLayout/44b57dcfc4960fc8553d946c49fc44438b78fbe0/app.gif -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .gif -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.yazhi.swipelayout" 9 | minSdkVersion 15 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.3.0' 26 | compile project(':superswiperefreshlayout') 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\android-sdk-windows_r24.3.4/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/yazhi/swipelayout/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.yazhi.swipelayout; 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 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/yazhi/swipelayout/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yazhi.swipelayout; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.BaseAdapter; 9 | import android.widget.ListView; 10 | import android.widget.TextView; 11 | 12 | import com.yazhi.superswiperefreshlayout.SuperSwipeRefreshLayout; 13 | 14 | import java.util.ArrayList; 15 | 16 | /** 17 | * Created by zyz on 2016/4/19 18 | * QQ: 344100167 19 | * Github: https://github.com/yazhi1992 20 | */ 21 | public class MainActivity extends AppCompatActivity { 22 | 23 | private ListView mLv; 24 | ArrayList list = new ArrayList<>(); 25 | private SuperSwipeRefreshLayout mView; 26 | public static Handler sHandler = new Handler(); 27 | ; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_main); 33 | 34 | //测试数据 35 | //data for test 36 | for (int i = 0; i < 15; i++) { 37 | list.add("Number " + (i + 1) + ", test data"); 38 | } 39 | mView = (SuperSwipeRefreshLayout) findViewById(R.id.myview); 40 | mLv = (ListView) findViewById(R.id.listview); 41 | final Adapter adapter = new Adapter(); 42 | mLv.setAdapter(adapter); 43 | 44 | //设置加载动画的位置 45 | // mView.setProgressViewOffset(false, 100, 198); 46 | // mView.setProgressViewEndTarget(false, 198); 47 | //设置小圆圈颜色 48 | mView.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light); 49 | mView.setOnRefreshListener(new SuperSwipeRefreshLayout.OnRefreshListener() { 50 | @Override 51 | public void onRefresh(final int type) { 52 | sHandler.postDelayed(new Runnable() { 53 | @Override 54 | public void run() { 55 | switch (type) { 56 | case 0: //进入界面时加载的操作 refresh when activity was created 57 | list.add(0, "Refresh when activity was created"); 58 | //关闭启动加载刷新动画 59 | //finish animation 60 | mView.setRefreshing(false); 61 | break; 62 | case 1: //下拉刷新 pull down to refresh 63 | list.add(0, "Pull down to refresh"); 64 | //关闭下拉拉刷新动画 65 | //finish animation 66 | mView.setRefreshing(false); 67 | break; 68 | case 2: //上拉刷新 pull up to refresh 69 | list.add("Pull up to refresh, number 1"); 70 | list.add("Pull up to refresh, number 2"); 71 | list.add("Pull up to refresh, number 3"); 72 | list.add("Pull up to refresh, number 4"); 73 | //关闭上拉刷新动画 74 | //finish animation 75 | mView.setUpRefreshing(false); 76 | break; 77 | default: 78 | break; 79 | } 80 | adapter.notifyDataSetChanged(); 81 | } 82 | }, 3000); 83 | } 84 | }); 85 | 86 | } 87 | 88 | 89 | class Adapter extends BaseAdapter { 90 | 91 | @Override 92 | public int getCount() { 93 | return list.size(); 94 | } 95 | 96 | @Override 97 | public Object getItem(int position) { 98 | return null; 99 | } 100 | 101 | @Override 102 | public long getItemId(int position) { 103 | return 0; 104 | } 105 | 106 | @Override 107 | public View getView(int position, View convertView, ViewGroup parent) { 108 | View inflate = getLayoutInflater().inflate(R.layout.item, null); 109 | TextView text = (TextView) inflate.findViewById(R.id.textview); 110 | text.setText(list.get(position)); 111 | return inflate; 112 | } 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yazhidev/SuperSwipeRefreshLayout/44b57dcfc4960fc8553d946c49fc44438b78fbe0/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yazhidev/SuperSwipeRefreshLayout/44b57dcfc4960fc8553d946c49fc44438b78fbe0/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yazhidev/SuperSwipeRefreshLayout/44b57dcfc4960fc8553d946c49fc44438b78fbe0/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yazhidev/SuperSwipeRefreshLayout/44b57dcfc4960fc8553d946c49fc44438b78fbe0/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yazhidev/SuperSwipeRefreshLayout/44b57dcfc4960fc8553d946c49fc44438b78fbe0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SuperSwipeRefreshLayout 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/yazhi/swipelayout/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yazhi.swipelayout; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /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.0.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yazhidev/SuperSwipeRefreshLayout/44b57dcfc4960fc8553d946c49fc44438b78fbe0/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.10-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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':superswiperefreshlayout' 2 | -------------------------------------------------------------------------------- /superswiperefreshlayout/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .gif -------------------------------------------------------------------------------- /superswiperefreshlayout/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.1.2" 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 | testCompile 'junit:junit:4.12' 24 | compile 'com.android.support:appcompat-v7:23.3.0' 25 | } 26 | 27 | apply from: "https://raw.githubusercontent.com/xiaopansky/android-library-publish-to-jcenter/master/bintrayUpload.gradle" -------------------------------------------------------------------------------- /superswiperefreshlayout/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 E:\android-sdk-windows_r24.3.4/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 | -------------------------------------------------------------------------------- /superswiperefreshlayout/project.properties: -------------------------------------------------------------------------------- 1 | #project 2 | project.name=SuperSwipeRefreshLayout 3 | project.groupId=com.yazhi 4 | project.artifactId=superswiperefreshlayout 5 | project.packaging=aar 6 | project.siteUrl=https://github.com/yazhi1992/SuperSwipeRefreshLayout 7 | project.gitUrl=https://github.com/yazhi1992/SuperSwipeRefreshLayout.git 8 | 9 | #javadoc 10 | javadoc.name=SuperSwipeRefreshLayout 11 | -------------------------------------------------------------------------------- /superswiperefreshlayout/src/androidTest/java/com/yazhi/superswiperefreshlayout/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.yazhi.superswiperefreshlayout; 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 | } -------------------------------------------------------------------------------- /superswiperefreshlayout/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /superswiperefreshlayout/src/main/java/com/yazhi/superswiperefreshlayout/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package com.yazhi.superswiperefreshlayout; 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.RadialGradient; 8 | import android.graphics.Shader; 9 | import android.graphics.drawable.ShapeDrawable; 10 | import android.graphics.drawable.shapes.OvalShape; 11 | import android.support.v4.view.ViewCompat; 12 | import android.view.animation.Animation; 13 | import android.widget.ImageView; 14 | 15 | /** 16 | * Created by zyz on 2016/4/19 0019. 17 | * QQ: 344100167 18 | * Github: https://github.com/yazhi1992 19 | */ 20 | class CircleImageView extends ImageView { 21 | 22 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 23 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 24 | // PX 25 | private static final float X_OFFSET = 0f; 26 | private static final float Y_OFFSET = 1.75f; 27 | private static final float SHADOW_RADIUS = 3.5f; 28 | private static final int SHADOW_ELEVATION = 4; 29 | 30 | private Animation.AnimationListener mListener; 31 | private int mShadowRadius; 32 | 33 | public CircleImageView(Context context, int color, final float radius) { 34 | super(context); 35 | final float density = getContext().getResources().getDisplayMetrics().density; 36 | final int diameter = (int) (radius * density * 2); 37 | final int shadowYOffset = (int) (density * Y_OFFSET); 38 | final int shadowXOffset = (int) (density * X_OFFSET); 39 | 40 | mShadowRadius = (int) (density * SHADOW_RADIUS); 41 | 42 | ShapeDrawable circle; 43 | if (elevationSupported()) { 44 | circle = new ShapeDrawable(new OvalShape()); 45 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 46 | } else { 47 | OvalShape oval = new OvalShadow(mShadowRadius, diameter); 48 | circle = new ShapeDrawable(oval); 49 | ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); 50 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 51 | KEY_SHADOW_COLOR); 52 | final int padding = mShadowRadius; 53 | // set padding so the inner image sits correctly within the shadow. 54 | setPadding(padding, padding, padding, padding); 55 | } 56 | circle.getPaint().setColor(color); 57 | setBackgroundDrawable(circle); 58 | } 59 | 60 | private boolean elevationSupported() { 61 | return android.os.Build.VERSION.SDK_INT >= 21; 62 | } 63 | 64 | @Override 65 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 66 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 67 | if (!elevationSupported()) { 68 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight() 69 | + mShadowRadius * 2); 70 | } 71 | } 72 | 73 | public void setAnimationListener(Animation.AnimationListener listener) { 74 | mListener = listener; 75 | } 76 | 77 | @Override 78 | public void onAnimationStart() { 79 | super.onAnimationStart(); 80 | if (mListener != null) { 81 | mListener.onAnimationStart(getAnimation()); 82 | } 83 | } 84 | 85 | @Override 86 | public void onAnimationEnd() { 87 | super.onAnimationEnd(); 88 | if (mListener != null) { 89 | mListener.onAnimationEnd(getAnimation()); 90 | } 91 | } 92 | 93 | /** 94 | * Update the background color of the circle image view. 95 | * 96 | * @param colorRes Id of a color resource. 97 | */ 98 | public void setBackgroundColorRes(int colorRes) { 99 | setBackgroundColor(getContext().getResources().getColor(colorRes)); 100 | } 101 | 102 | @Override 103 | public void setBackgroundColor(int color) { 104 | if (getBackground() instanceof ShapeDrawable) { 105 | ((ShapeDrawable) getBackground()).getPaint().setColor(color); 106 | } 107 | } 108 | 109 | private class OvalShadow extends OvalShape { 110 | private RadialGradient mRadialGradient; 111 | private Paint mShadowPaint; 112 | private int mCircleDiameter; 113 | 114 | public OvalShadow(int shadowRadius, int circleDiameter) { 115 | super(); 116 | mShadowPaint = new Paint(); 117 | mShadowRadius = shadowRadius; 118 | mCircleDiameter = circleDiameter; 119 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, 120 | mShadowRadius, new int[]{ 121 | FILL_SHADOW_COLOR, Color.TRANSPARENT 122 | }, null, Shader.TileMode.CLAMP); 123 | mShadowPaint.setShader(mRadialGradient); 124 | } 125 | 126 | @Override 127 | public void draw(Canvas canvas, Paint paint) { 128 | final int viewWidth = CircleImageView.this.getWidth(); 129 | final int viewHeight = CircleImageView.this.getHeight(); 130 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius), 131 | mShadowPaint); 132 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint); 133 | } 134 | } 135 | } 136 | 137 | -------------------------------------------------------------------------------- /superswiperefreshlayout/src/main/java/com/yazhi/superswiperefreshlayout/MaterialProgressDrawable.java: -------------------------------------------------------------------------------- 1 | package com.yazhi.superswiperefreshlayout; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.ColorFilter; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.PixelFormat; 11 | import android.graphics.Rect; 12 | import android.graphics.RectF; 13 | import android.graphics.drawable.Animatable; 14 | import android.graphics.drawable.Drawable; 15 | import android.support.annotation.IntDef; 16 | import android.support.annotation.NonNull; 17 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 18 | import android.util.DisplayMetrics; 19 | import android.view.View; 20 | import android.view.animation.Animation; 21 | import android.view.animation.Interpolator; 22 | import android.view.animation.LinearInterpolator; 23 | import android.view.animation.Transformation; 24 | 25 | import java.lang.annotation.Retention; 26 | import java.lang.annotation.RetentionPolicy; 27 | import java.util.ArrayList; 28 | 29 | /** 30 | * Created by zyz on 2016/4/19 31 | * QQ: 344100167 32 | * Github: https://github.com/yazhi1992 33 | */ 34 | class MaterialProgressDrawable extends Drawable implements Animatable { 35 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 36 | private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 37 | 38 | private static final float FULL_ROTATION = 1080.0f; 39 | 40 | @Retention(RetentionPolicy.CLASS) 41 | @IntDef({LARGE, DEFAULT}) 42 | public @interface ProgressDrawableSize { 43 | } 44 | 45 | // Maps to ProgressBar.Large style 46 | static final int LARGE = 0; 47 | // Maps to ProgressBar default style 48 | static final int DEFAULT = 1; 49 | 50 | // Maps to ProgressBar default style 51 | private static final int CIRCLE_DIAMETER = 40; 52 | private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 53 | private static final float STROKE_WIDTH = 2.5f; 54 | 55 | // Maps to ProgressBar.Large style 56 | private static final int CIRCLE_DIAMETER_LARGE = 56; 57 | private static final float CENTER_RADIUS_LARGE = 12.5f; 58 | private static final float STROKE_WIDTH_LARGE = 3f; 59 | 60 | private final int[] COLORS = new int[]{ 61 | Color.BLACK 62 | }; 63 | 64 | /** 65 | * The value in the linear interpolator for animating the drawable at which 66 | * the color transition should start 67 | */ 68 | private static final float COLOR_START_DELAY_OFFSET = 0.75f; 69 | private static final float END_TRIM_START_DELAY_OFFSET = 0.5f; 70 | private static final float START_TRIM_DURATION_OFFSET = 0.5f; 71 | 72 | /** 73 | * The duration of a single progress spin in milliseconds. 74 | */ 75 | private static final int ANIMATION_DURATION = 1332; 76 | 77 | /** 78 | * The number of points in the progress "star". 79 | */ 80 | private static final float NUM_POINTS = 5f; 81 | /** 82 | * The list of animators operating on this drawable. 83 | */ 84 | private final ArrayList mAnimators = new ArrayList(); 85 | 86 | /** 87 | * The indicator ring, used to manage animation state. 88 | */ 89 | private final Ring mRing; 90 | 91 | /** 92 | * Canvas rotation in degrees. 93 | */ 94 | private float mRotation; 95 | 96 | /** 97 | * Layout info for the arrowhead in dp 98 | */ 99 | private static final int ARROW_WIDTH = 10; 100 | private static final int ARROW_HEIGHT = 5; 101 | private static final float ARROW_OFFSET_ANGLE = 5; 102 | 103 | /** 104 | * Layout info for the arrowhead for the large spinner in dp 105 | */ 106 | private static final int ARROW_WIDTH_LARGE = 12; 107 | private static final int ARROW_HEIGHT_LARGE = 6; 108 | private static final float MAX_PROGRESS_ARC = .8f; 109 | 110 | private Resources mResources; 111 | private View mParent; 112 | private Animation mAnimation; 113 | private float mRotationCount; 114 | private double mWidth; 115 | private double mHeight; 116 | boolean mFinishing; 117 | 118 | public MaterialProgressDrawable(Context context, View parent) { 119 | mParent = parent; 120 | mResources = context.getResources(); 121 | 122 | mRing = new Ring(mCallback); 123 | mRing.setColors(COLORS); 124 | 125 | updateSizes(DEFAULT); 126 | setupAnimators(); 127 | } 128 | 129 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 130 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 131 | final Ring ring = mRing; 132 | final DisplayMetrics metrics = mResources.getDisplayMetrics(); 133 | final float screenDensity = metrics.density; 134 | 135 | mWidth = progressCircleWidth * screenDensity; 136 | mHeight = progressCircleHeight * screenDensity; 137 | ring.setStrokeWidth((float) strokeWidth * screenDensity); 138 | ring.setCenterRadius(centerRadius * screenDensity); 139 | ring.setColorIndex(0); 140 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 141 | ring.setInsets((int) mWidth, (int) mHeight); 142 | } 143 | 144 | public void updateSizes(@ProgressDrawableSize int size) { 145 | if (size == LARGE) { 146 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 147 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 148 | } else { 149 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 150 | ARROW_WIDTH, ARROW_HEIGHT); 151 | } 152 | } 153 | 154 | /** 155 | * @param show Set to true to display the arrowhead on the progress spinner. 156 | */ 157 | public void showArrow(boolean show) { 158 | mRing.setShowArrow(show); 159 | } 160 | 161 | /** 162 | * @param scale Set the scale of the arrowhead for the spinner. 163 | */ 164 | public void setArrowScale(float scale) { 165 | mRing.setArrowScale(scale); 166 | } 167 | 168 | /** 169 | * Set the start and end trim for the progress spinner arc. 170 | * 171 | * @param startAngle start angle 172 | * @param endAngle end angle 173 | */ 174 | public void setStartEndTrim(float startAngle, float endAngle) { 175 | mRing.setStartTrim(startAngle); 176 | mRing.setEndTrim(endAngle); 177 | } 178 | 179 | /** 180 | * Set the amount of rotation to apply to the progress spinner. 181 | * 182 | * @param rotation Rotation is from [0..1] 183 | */ 184 | public void setProgressRotation(float rotation) { 185 | mRing.setRotation(rotation); 186 | } 187 | 188 | /** 189 | * Update the background color of the circle image view. 190 | */ 191 | public void setBackgroundColor(int color) { 192 | mRing.setBackgroundColor(color); 193 | } 194 | 195 | /** 196 | * Set the colors used in the progress animation from color resources. 197 | * The first color will also be the color of the bar that grows in response 198 | * to a user swipe gesture. 199 | * 200 | * @param colors 201 | */ 202 | public void setColorSchemeColors(int... colors) { 203 | mRing.setColors(colors); 204 | mRing.setColorIndex(0); 205 | } 206 | 207 | @Override 208 | public int getIntrinsicHeight() { 209 | return (int) mHeight; 210 | } 211 | 212 | @Override 213 | public int getIntrinsicWidth() { 214 | return (int) mWidth; 215 | } 216 | 217 | @Override 218 | public void draw(Canvas c) { 219 | final Rect bounds = getBounds(); 220 | final int saveCount = c.save(); 221 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 222 | mRing.draw(c, bounds); 223 | c.restoreToCount(saveCount); 224 | } 225 | 226 | @Override 227 | public void setAlpha(int alpha) { 228 | mRing.setAlpha(alpha); 229 | } 230 | 231 | public int getAlpha() { 232 | return mRing.getAlpha(); 233 | } 234 | 235 | @Override 236 | public void setColorFilter(ColorFilter colorFilter) { 237 | mRing.setColorFilter(colorFilter); 238 | } 239 | 240 | @SuppressWarnings("unused") 241 | void setRotation(float rotation) { 242 | mRotation = rotation; 243 | invalidateSelf(); 244 | } 245 | 246 | @SuppressWarnings("unused") 247 | private float getRotation() { 248 | return mRotation; 249 | } 250 | 251 | @Override 252 | public int getOpacity() { 253 | return PixelFormat.TRANSLUCENT; 254 | } 255 | 256 | @Override 257 | public boolean isRunning() { 258 | final ArrayList animators = mAnimators; 259 | final int N = animators.size(); 260 | for (int i = 0; i < N; i++) { 261 | final Animation animator = animators.get(i); 262 | if (animator.hasStarted() && !animator.hasEnded()) { 263 | return true; 264 | } 265 | } 266 | return false; 267 | } 268 | 269 | @Override 270 | public void start() { 271 | mAnimation.reset(); 272 | mRing.storeOriginals(); 273 | // Already showing some part of the ring 274 | if (mRing.getEndTrim() != mRing.getStartTrim()) { 275 | mFinishing = true; 276 | mAnimation.setDuration(ANIMATION_DURATION / 2); 277 | mParent.startAnimation(mAnimation); 278 | } else { 279 | mRing.setColorIndex(0); 280 | mRing.resetOriginals(); 281 | mAnimation.setDuration(ANIMATION_DURATION); 282 | mParent.startAnimation(mAnimation); 283 | } 284 | } 285 | 286 | @Override 287 | public void stop() { 288 | mParent.clearAnimation(); 289 | setRotation(0); 290 | mRing.setShowArrow(false); 291 | mRing.setColorIndex(0); 292 | mRing.resetOriginals(); 293 | } 294 | 295 | private float getMinProgressArc(Ring ring) { 296 | return (float) Math.toRadians( 297 | ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius())); 298 | } 299 | 300 | // Adapted from ArgbEvaluator.java 301 | private int evaluateColorChange(float fraction, int startValue, int endValue) { 302 | int startInt = (Integer) startValue; 303 | int startA = (startInt >> 24) & 0xff; 304 | int startR = (startInt >> 16) & 0xff; 305 | int startG = (startInt >> 8) & 0xff; 306 | int startB = startInt & 0xff; 307 | 308 | int endInt = (Integer) endValue; 309 | int endA = (endInt >> 24) & 0xff; 310 | int endR = (endInt >> 16) & 0xff; 311 | int endG = (endInt >> 8) & 0xff; 312 | int endB = endInt & 0xff; 313 | 314 | return (int) ((startA + (int) (fraction * (endA - startA))) << 24) | 315 | (int) ((startR + (int) (fraction * (endR - startR))) << 16) | 316 | (int) ((startG + (int) (fraction * (endG - startG))) << 8) | 317 | (int) ((startB + (int) (fraction * (endB - startB)))); 318 | } 319 | 320 | /** 321 | * Update the ring color if this is within the last 25% of the animation. 322 | * The new ring color will be a translation from the starting ring color to 323 | * the next color. 324 | */ 325 | private void updateRingColor(float interpolatedTime, Ring ring) { 326 | if (interpolatedTime > COLOR_START_DELAY_OFFSET) { 327 | // scale the interpolatedTime so that the full 328 | // transformation from 0 - 1 takes place in the 329 | // remaining time 330 | ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET) 331 | / (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(), 332 | ring.getNextColor())); 333 | } 334 | } 335 | 336 | private void applyFinishTranslation(float interpolatedTime, Ring ring) { 337 | // shrink back down and complete a full rotation before 338 | // starting other circles 339 | // Rotation goes between [0..1]. 340 | updateRingColor(interpolatedTime, ring); 341 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC) 342 | + 1f); 343 | final float minProgressArc = getMinProgressArc(ring); 344 | final float startTrim = ring.getStartingStartTrim() 345 | + (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim()) 346 | * interpolatedTime; 347 | ring.setStartTrim(startTrim); 348 | ring.setEndTrim(ring.getStartingEndTrim()); 349 | final float rotation = ring.getStartingRotation() 350 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 351 | ring.setRotation(rotation); 352 | } 353 | 354 | private void setupAnimators() { 355 | final Ring ring = mRing; 356 | final Animation animation = new Animation() { 357 | @Override 358 | public void applyTransformation(float interpolatedTime, Transformation t) { 359 | if (mFinishing) { 360 | applyFinishTranslation(interpolatedTime, ring); 361 | } else { 362 | // The minProgressArc is calculated from 0 to create an 363 | // angle that matches the stroke width. 364 | final float minProgressArc = getMinProgressArc(ring); 365 | final float startingEndTrim = ring.getStartingEndTrim(); 366 | final float startingTrim = ring.getStartingStartTrim(); 367 | final float startingRotation = ring.getStartingRotation(); 368 | 369 | updateRingColor(interpolatedTime, ring); 370 | 371 | // Moving the start trim only occurs in the first 50% of a 372 | // single ring animation 373 | if (interpolatedTime <= START_TRIM_DURATION_OFFSET) { 374 | // scale the interpolatedTime so that the full 375 | // transformation from 0 - 1 takes place in the 376 | // remaining time 377 | final float scaledTime = (interpolatedTime) 378 | / (1.0f - START_TRIM_DURATION_OFFSET); 379 | final float startTrim = startingTrim 380 | + ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR 381 | .getInterpolation(scaledTime)); 382 | ring.setStartTrim(startTrim); 383 | } 384 | 385 | // Moving the end trim starts after 50% of a single ring 386 | // animation completes 387 | if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) { 388 | // scale the interpolatedTime so that the full 389 | // transformation from 0 - 1 takes place in the 390 | // remaining time 391 | final float minArc = MAX_PROGRESS_ARC - minProgressArc; 392 | float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET) 393 | / (1.0f - START_TRIM_DURATION_OFFSET); 394 | final float endTrim = startingEndTrim 395 | + (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime)); 396 | ring.setEndTrim(endTrim); 397 | } 398 | 399 | final float rotation = startingRotation + (0.25f * interpolatedTime); 400 | ring.setRotation(rotation); 401 | 402 | float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime) 403 | + (FULL_ROTATION * (mRotationCount / NUM_POINTS)); 404 | setRotation(groupRotation); 405 | } 406 | } 407 | }; 408 | animation.setRepeatCount(Animation.INFINITE); 409 | animation.setRepeatMode(Animation.RESTART); 410 | animation.setInterpolator(LINEAR_INTERPOLATOR); 411 | animation.setAnimationListener(new Animation.AnimationListener() { 412 | 413 | @Override 414 | public void onAnimationStart(Animation animation) { 415 | mRotationCount = 0; 416 | } 417 | 418 | @Override 419 | public void onAnimationEnd(Animation animation) { 420 | // do nothing 421 | } 422 | 423 | @Override 424 | public void onAnimationRepeat(Animation animation) { 425 | ring.storeOriginals(); 426 | ring.goToNextColor(); 427 | ring.setStartTrim(ring.getEndTrim()); 428 | if (mFinishing) { 429 | // finished closing the last ring from the swipe gesture; go 430 | // into progress mode 431 | mFinishing = false; 432 | animation.setDuration(ANIMATION_DURATION); 433 | ring.setShowArrow(false); 434 | } else { 435 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 436 | } 437 | } 438 | }); 439 | mAnimation = animation; 440 | } 441 | 442 | private final Callback mCallback = new Callback() { 443 | @Override 444 | public void invalidateDrawable(Drawable d) { 445 | invalidateSelf(); 446 | } 447 | 448 | @Override 449 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 450 | scheduleSelf(what, when); 451 | } 452 | 453 | @Override 454 | public void unscheduleDrawable(Drawable d, Runnable what) { 455 | unscheduleSelf(what); 456 | } 457 | }; 458 | 459 | private static class Ring { 460 | private final RectF mTempBounds = new RectF(); 461 | private final Paint mPaint = new Paint(); 462 | private final Paint mArrowPaint = new Paint(); 463 | 464 | private final Callback mCallback; 465 | 466 | private float mStartTrim = 0.0f; 467 | private float mEndTrim = 0.0f; 468 | private float mRotation = 0.0f; 469 | private float mStrokeWidth = 5.0f; 470 | private float mStrokeInset = 2.5f; 471 | 472 | private int[] mColors; 473 | // mColorIndex represents the offset into the available mColors that the 474 | // progress circle should currently display. As the progress circle is 475 | // animating, the mColorIndex moves by one to the next available color. 476 | private int mColorIndex; 477 | private float mStartingStartTrim; 478 | private float mStartingEndTrim; 479 | private float mStartingRotation; 480 | private boolean mShowArrow; 481 | private Path mArrow; 482 | private float mArrowScale; 483 | private double mRingCenterRadius; 484 | private int mArrowWidth; 485 | private int mArrowHeight; 486 | private int mAlpha; 487 | private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 488 | private int mBackgroundColor; 489 | private int mCurrentColor; 490 | 491 | public Ring(Callback callback) { 492 | mCallback = callback; 493 | 494 | mPaint.setStrokeCap(Paint.Cap.SQUARE); 495 | mPaint.setAntiAlias(true); 496 | mPaint.setStyle(Paint.Style.STROKE); 497 | 498 | mArrowPaint.setStyle(Paint.Style.FILL); 499 | mArrowPaint.setAntiAlias(true); 500 | } 501 | 502 | public void setBackgroundColor(int color) { 503 | mBackgroundColor = color; 504 | } 505 | 506 | /** 507 | * Set the dimensions of the arrowhead. 508 | * 509 | * @param width Width of the hypotenuse of the arrow head 510 | * @param height Height of the arrow point 511 | */ 512 | public void setArrowDimensions(float width, float height) { 513 | mArrowWidth = (int) width; 514 | mArrowHeight = (int) height; 515 | } 516 | 517 | /** 518 | * Draw the progress spinner 519 | */ 520 | public void draw(Canvas c, Rect bounds) { 521 | final RectF arcBounds = mTempBounds; 522 | arcBounds.set(bounds); 523 | arcBounds.inset(mStrokeInset, mStrokeInset); 524 | 525 | final float startAngle = (mStartTrim + mRotation) * 360; 526 | final float endAngle = (mEndTrim + mRotation) * 360; 527 | float sweepAngle = endAngle - startAngle; 528 | 529 | mPaint.setColor(mCurrentColor); 530 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); 531 | 532 | drawTriangle(c, startAngle, sweepAngle, bounds); 533 | 534 | if (mAlpha < 255) { 535 | mCirclePaint.setColor(mBackgroundColor); 536 | mCirclePaint.setAlpha(255 - mAlpha); 537 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, 538 | mCirclePaint); 539 | } 540 | } 541 | 542 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 543 | if (mShowArrow) { 544 | if (mArrow == null) { 545 | mArrow = new Path(); 546 | mArrow.setFillType(Path.FillType.EVEN_ODD); 547 | } else { 548 | mArrow.reset(); 549 | } 550 | 551 | // Adjust the position of the triangle so that it is inset as 552 | // much as the arc, but also centered on the arc. 553 | float inset = (int) mStrokeInset / 2 * mArrowScale; 554 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 555 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 556 | 557 | // Update the path each time. This works around an issue in SKIA 558 | // where concatenating a rotation matrix to a scale matrix 559 | // ignored a starting negative rotation. This appears to have 560 | // been fixed as of API 21. 561 | mArrow.moveTo(0, 0); 562 | mArrow.lineTo(mArrowWidth * mArrowScale, 0); 563 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 564 | * mArrowScale)); 565 | mArrow.offset(x - inset, y); 566 | mArrow.close(); 567 | // draw a triangle 568 | mArrowPaint.setColor(mCurrentColor); 569 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 570 | bounds.exactCenterY()); 571 | c.drawPath(mArrow, mArrowPaint); 572 | } 573 | } 574 | 575 | /** 576 | * Set the colors the progress spinner alternates between. 577 | * 578 | * @param colors Array of integers describing the colors. Must be non-null. 579 | */ 580 | public void setColors(@NonNull int[] colors) { 581 | mColors = colors; 582 | // if colors are reset, make sure to reset the color index as well 583 | setColorIndex(0); 584 | } 585 | 586 | /** 587 | * Set the absolute color of the progress spinner. This is should only 588 | * be used when animating between current and next color when the 589 | * spinner is rotating. 590 | * 591 | * @param color int describing the color. 592 | */ 593 | public void setColor(int color) { 594 | mCurrentColor = color; 595 | } 596 | 597 | /** 598 | * @param index Index into the color array of the color to display in 599 | * the progress spinner. 600 | */ 601 | public void setColorIndex(int index) { 602 | mColorIndex = index; 603 | mCurrentColor = mColors[mColorIndex]; 604 | } 605 | 606 | /** 607 | * @return int describing the next color the progress spinner should use when drawing. 608 | */ 609 | public int getNextColor() { 610 | return mColors[getNextColorIndex()]; 611 | } 612 | 613 | private int getNextColorIndex() { 614 | return (mColorIndex + 1) % (mColors.length); 615 | } 616 | 617 | /** 618 | * Proceed to the next available ring color. This will automatically 619 | * wrap back to the beginning of colors. 620 | */ 621 | public void goToNextColor() { 622 | setColorIndex(getNextColorIndex()); 623 | } 624 | 625 | public void setColorFilter(ColorFilter filter) { 626 | mPaint.setColorFilter(filter); 627 | invalidateSelf(); 628 | } 629 | 630 | /** 631 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 632 | */ 633 | public void setAlpha(int alpha) { 634 | mAlpha = alpha; 635 | } 636 | 637 | /** 638 | * @return Current alpha of the progress spinner and arrowhead. 639 | */ 640 | public int getAlpha() { 641 | return mAlpha; 642 | } 643 | 644 | /** 645 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 646 | */ 647 | public void setStrokeWidth(float strokeWidth) { 648 | mStrokeWidth = strokeWidth; 649 | mPaint.setStrokeWidth(strokeWidth); 650 | invalidateSelf(); 651 | } 652 | 653 | @SuppressWarnings("unused") 654 | public float getStrokeWidth() { 655 | return mStrokeWidth; 656 | } 657 | 658 | @SuppressWarnings("unused") 659 | public void setStartTrim(float startTrim) { 660 | mStartTrim = startTrim; 661 | invalidateSelf(); 662 | } 663 | 664 | @SuppressWarnings("unused") 665 | public float getStartTrim() { 666 | return mStartTrim; 667 | } 668 | 669 | public float getStartingStartTrim() { 670 | return mStartingStartTrim; 671 | } 672 | 673 | public float getStartingEndTrim() { 674 | return mStartingEndTrim; 675 | } 676 | 677 | public int getStartingColor() { 678 | return mColors[mColorIndex]; 679 | } 680 | 681 | @SuppressWarnings("unused") 682 | public void setEndTrim(float endTrim) { 683 | mEndTrim = endTrim; 684 | invalidateSelf(); 685 | } 686 | 687 | @SuppressWarnings("unused") 688 | public float getEndTrim() { 689 | return mEndTrim; 690 | } 691 | 692 | @SuppressWarnings("unused") 693 | public void setRotation(float rotation) { 694 | mRotation = rotation; 695 | invalidateSelf(); 696 | } 697 | 698 | @SuppressWarnings("unused") 699 | public float getRotation() { 700 | return mRotation; 701 | } 702 | 703 | public void setInsets(int width, int height) { 704 | final float minEdge = (float) Math.min(width, height); 705 | float insets; 706 | if (mRingCenterRadius <= 0 || minEdge < 0) { 707 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 708 | } else { 709 | insets = (float) (minEdge / 2.0f - mRingCenterRadius); 710 | } 711 | mStrokeInset = insets; 712 | } 713 | 714 | @SuppressWarnings("unused") 715 | public float getInsets() { 716 | return mStrokeInset; 717 | } 718 | 719 | /** 720 | * @param centerRadius Inner radius in px of the circle the progress 721 | * spinner arc traces. 722 | */ 723 | public void setCenterRadius(double centerRadius) { 724 | mRingCenterRadius = centerRadius; 725 | } 726 | 727 | public double getCenterRadius() { 728 | return mRingCenterRadius; 729 | } 730 | 731 | /** 732 | * @param show Set to true to show the arrow head on the progress spinner. 733 | */ 734 | public void setShowArrow(boolean show) { 735 | if (mShowArrow != show) { 736 | mShowArrow = show; 737 | invalidateSelf(); 738 | } 739 | } 740 | 741 | /** 742 | * @param scale Set the scale of the arrowhead for the spinner. 743 | */ 744 | public void setArrowScale(float scale) { 745 | if (scale != mArrowScale) { 746 | mArrowScale = scale; 747 | invalidateSelf(); 748 | } 749 | } 750 | 751 | /** 752 | * @return The amount the progress spinner is currently rotated, between [0..1]. 753 | */ 754 | public float getStartingRotation() { 755 | return mStartingRotation; 756 | } 757 | 758 | /** 759 | * If the start / end trim are offset to begin with, store them so that 760 | * animation starts from that offset. 761 | */ 762 | public void storeOriginals() { 763 | mStartingStartTrim = mStartTrim; 764 | mStartingEndTrim = mEndTrim; 765 | mStartingRotation = mRotation; 766 | } 767 | 768 | /** 769 | * Reset the progress spinner to default rotation, start and end angles. 770 | */ 771 | public void resetOriginals() { 772 | mStartingStartTrim = 0; 773 | mStartingEndTrim = 0; 774 | mStartingRotation = 0; 775 | setStartTrim(0); 776 | setEndTrim(0); 777 | setRotation(0); 778 | } 779 | 780 | private void invalidateSelf() { 781 | mCallback.invalidateDrawable(null); 782 | } 783 | } 784 | } 785 | -------------------------------------------------------------------------------- /superswiperefreshlayout/src/main/java/com/yazhi/superswiperefreshlayout/SuperSwipeRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.yazhi.superswiperefreshlayout; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.content.res.TypedArray; 6 | import android.os.Build; 7 | import android.support.annotation.ColorInt; 8 | import android.support.annotation.ColorRes; 9 | import android.support.v4.view.MotionEventCompat; 10 | import android.support.v4.view.NestedScrollingChild; 11 | import android.support.v4.view.NestedScrollingChildHelper; 12 | import android.support.v4.view.NestedScrollingParent; 13 | import android.support.v4.view.NestedScrollingParentHelper; 14 | import android.support.v4.view.ViewCompat; 15 | import android.support.v4.widget.SwipeRefreshLayout; 16 | import android.util.AttributeSet; 17 | import android.util.DisplayMetrics; 18 | import android.util.Log; 19 | import android.view.LayoutInflater; 20 | import android.view.MotionEvent; 21 | import android.view.View; 22 | import android.view.ViewConfiguration; 23 | import android.view.ViewGroup; 24 | import android.view.animation.Animation; 25 | import android.view.animation.DecelerateInterpolator; 26 | import android.view.animation.Transformation; 27 | import android.widget.AbsListView; 28 | import android.widget.ListView; 29 | 30 | /** 31 | * Created by zyz on 2016/4/19 32 | * QQ: 344100167 33 | * Github: https://github.com/yazhi1992 34 | */ 35 | public class SuperSwipeRefreshLayout extends ViewGroup implements NestedScrollingParent, 36 | NestedScrollingChild, AbsListView.OnScrollListener { 37 | 38 | private Context mContext; 39 | private ListView mListView; 40 | boolean isLoading; //是否正在加载中 41 | private int mYPress; //按下时的y坐标 42 | private int mYRelease; //抬起时的y坐标 43 | private int mLoadDistance = 300; //上拉刷新的最小触发距离 44 | boolean isUp = false; 45 | int UpRefreshingDistance; //上拉刷新的位置 46 | boolean refreshWhenCreate; //是否允许进入布局时就刷新 47 | boolean allowUpTorefresh; 48 | float mOldSpinnerFinalOffset; 49 | boolean mOldmUsingCustomStart; 50 | private View mListViewFooter; //底部加载时的布局 51 | private int mMeasuredHeight; 52 | private float mAFloat; 53 | boolean childIsListview = false; 54 | 55 | //判断是否是上拉操作 56 | private boolean isPullUp() { 57 | return (mYPress - mYRelease) >= mLoadDistance; 58 | } 59 | 60 | // Maps to ProgressBar.Large style 61 | public static final int LARGE = MaterialProgressDrawable.LARGE; 62 | // Maps to ProgressBar default style 63 | public static final int DEFAULT = MaterialProgressDrawable.DEFAULT; 64 | 65 | private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName(); 66 | 67 | private static final int MAX_ALPHA = 255; 68 | private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA); 69 | 70 | private static final int CIRCLE_DIAMETER = 40; 71 | private static final int CIRCLE_DIAMETER_LARGE = 56; 72 | 73 | private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; 74 | private static final int INVALID_POINTER = -1; 75 | private static final float DRAG_RATE = .5f; 76 | 77 | // Max amount of circle that can be filled by progress during swipe gesture, 78 | // where 1.0 is a full circle 79 | private static final float MAX_PROGRESS_ANGLE = .8f; 80 | 81 | private static final int SCALE_DOWN_DURATION = 150; 82 | 83 | private static final int ALPHA_ANIMATION_DURATION = 300; 84 | 85 | private static final int ANIMATE_TO_TRIGGER_DURATION = 200; 86 | 87 | private static final int ANIMATE_TO_START_DURATION = 200; 88 | 89 | // Default background for the progress spinner 90 | private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; 91 | // Default offset in dips from the top of the view to where the progress spinner should stop 92 | private static final int DEFAULT_CIRCLE_TARGET = 64; 93 | 94 | private View mTarget; // the target of the gesture 95 | private OnRefreshListener mListener; 96 | private boolean mRefreshing = false; 97 | private int mTouchSlop; 98 | private float mTotalDragDistance = -1; 99 | 100 | // If nested scrolling is enabled, the total amount that needed to be 101 | // consumed by this as the nested scrolling parent is used in place of the 102 | // overscroll determined by MOVE events in the onTouch handler 103 | private float mTotalUnconsumed; 104 | private final NestedScrollingParentHelper mNestedScrollingParentHelper; 105 | private final NestedScrollingChildHelper mNestedScrollingChildHelper; 106 | private final int[] mParentScrollConsumed = new int[2]; 107 | private final int[] mParentOffsetInWindow = new int[2]; 108 | private boolean mNestedScrollInProgress; 109 | 110 | private int mMediumAnimationDuration; 111 | private int mCurrentTargetOffsetTop; //-70 112 | // Whether or not the starting offset has been determined. 113 | private boolean mOriginalOffsetCalculated = false; 114 | 115 | private float mInitialMotionY; //下拉的初始Y坐标 116 | private float mInitialDownY; 117 | private boolean mIsBeingDragged; 118 | private int mActivePointerId = INVALID_POINTER; 119 | // Whether this item is scaled up rather than clipped 120 | private boolean mScale; 121 | 122 | // Target is returning to its start offset because it was cancelled or a 123 | // refresh was triggered. 124 | private boolean mReturningToStart; 125 | private final DecelerateInterpolator mDecelerateInterpolator; 126 | private static final int[] LAYOUT_ATTRS = new int[]{ 127 | android.R.attr.enabled 128 | }; 129 | 130 | private CircleImageView mCircleView; 131 | private int mCircleViewIndex = -1; 132 | 133 | protected int mFrom; 134 | 135 | private float mStartingScale; 136 | 137 | protected int mOriginalOffsetTop; //下拉时圆出现的初始位置 138 | 139 | private float mSpinnerFinalOffset; //下拉刷新的位置,这两个的差就是可以滑动的距离 140 | 141 | private MaterialProgressDrawable mProgress; 142 | 143 | private Animation mScaleAnimation; 144 | 145 | private Animation mScaleDownAnimation; 146 | 147 | private Animation mAlphaStartAnimation; 148 | 149 | private Animation mAlphaMaxAnimation; 150 | 151 | private Animation mScaleDownToStartAnimation; 152 | 153 | private boolean mNotify; 154 | 155 | private int mCircleWidth; 156 | 157 | private int mCircleHeight; 158 | 159 | // Whether the client has set a custom starting position; 160 | private boolean mUsingCustomStart; //设置了位置,为true 161 | 162 | /** 163 | * Simple constructor to use when creating a SwipeRefreshLayout from code. 164 | * 165 | * @param context 166 | */ 167 | public SuperSwipeRefreshLayout(Context context) { 168 | this(context, null); 169 | mContext = context; 170 | } 171 | 172 | /** 173 | * Constructor that is called when inflating SwipeRefreshLayout from XML. 174 | * 175 | * @param context 176 | * @param attrs 177 | */ 178 | public SuperSwipeRefreshLayout(Context context, AttributeSet attrs) { 179 | super(context, attrs); 180 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SuperSwipeRefreshLayout); 181 | refreshWhenCreate = typedArray.getBoolean(R.styleable.SuperSwipeRefreshLayout_RefreshWhenCreate, false); //是否在进入布局时就刷新,默认不允许 182 | allowUpTorefresh = typedArray.getBoolean(R.styleable.SuperSwipeRefreshLayout_AllowUpToRefresh, false); //是要允许上拉刷新,默认不允许 183 | mAFloat = typedArray.getFloat(R.styleable.SuperSwipeRefreshLayout_BottomLocationPercent, 0.88f); 184 | if (mAFloat > 1) { 185 | mAFloat = 0.88f; 186 | } 187 | typedArray.recycle(); 188 | 189 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 190 | 191 | mMediumAnimationDuration = getResources().getInteger( 192 | android.R.integer.config_mediumAnimTime); 193 | 194 | setWillNotDraw(false); 195 | mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); 196 | 197 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 198 | setEnabled(a.getBoolean(0, true)); 199 | a.recycle(); 200 | 201 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 202 | mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); 203 | mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density); 204 | 205 | createProgressView(); 206 | ViewCompat.setChildrenDrawingOrderEnabled(this, true); 207 | // the absolute offset has to take into account that the circle starts at an offset 208 | mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density; 209 | mTotalDragDistance = mSpinnerFinalOffset; 210 | mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 211 | 212 | mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); 213 | setNestedScrollingEnabled(true); 214 | 215 | mContext = context; 216 | 217 | mListViewFooter = LayoutInflater.from(context).inflate(R.layout.item_null, null); 218 | } 219 | 220 | @Override 221 | public boolean dispatchTouchEvent(MotionEvent ev) { 222 | int action = ev.getAction(); 223 | switch (action) { 224 | case MotionEvent.ACTION_DOWN: //按下操作 225 | if (!isRefreshing() && allowUpTorefresh && isBottom()) { 226 | mYPress = (int) ev.getRawY(); 227 | } 228 | break; 229 | case MotionEvent.ACTION_MOVE: //松手时的Y坐标 230 | if (allowUpTorefresh && isBottom()) { 231 | mYRelease = (int) ev.getRawY(); 232 | } 233 | break; 234 | case MotionEvent.ACTION_UP: //松手 235 | if (canLoad() && !isRefreshing() && allowUpTorefresh && isBottom()) { 236 | setUpRefreshing(true); //显示加载动画 237 | } 238 | break; 239 | default: 240 | break; 241 | } 242 | return super.dispatchTouchEvent(ev); 243 | } 244 | 245 | public void setUpRefreshing(boolean loading) { 246 | if (allowUpTorefresh) { 247 | isLoading = loading; 248 | isUp = loading; 249 | if (isLoading) { 250 | 251 | if (childIsListview) { 252 | mListView.addFooterView(mListViewFooter); 253 | mListView.smoothScrollBy(100, 500); 254 | } 255 | mOldmUsingCustomStart = mUsingCustomStart; 256 | mUsingCustomStart = true; 257 | 258 | mOriginalOffsetTop = UpRefreshingDistance; 259 | 260 | mCircleView.setVisibility(View.VISIBLE); 261 | ViewCompat.setScaleX(mCircleView, 1f); //1f1倍,2f2倍大小 262 | ViewCompat.setScaleY(mCircleView, 1f); 263 | 264 | mNotify = true; 265 | ensureTarget(); 266 | mRefreshing = true; 267 | mOldSpinnerFinalOffset = mSpinnerFinalOffset; 268 | mSpinnerFinalOffset = UpRefreshingDistance; 269 | mFrom = UpRefreshingDistance; //从该位置移动到转圈处(mCurrentTargetOffsetTop) 270 | mAnimateToCorrectPosition.reset(); 271 | mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION); 272 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator); 273 | mCircleView.setAnimationListener(mRefreshListener); 274 | mCircleView.clearAnimation(); 275 | mCircleView.startAnimation(mAnimateToCorrectPosition); 276 | } else { 277 | mYRelease = 0; 278 | mYPress = 0; 279 | //还原设置,否则上拉位置会错乱 280 | mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); 281 | mCurrentTargetOffsetTop = mCircleView.getTop(); 282 | mSpinnerFinalOffset = mOldSpinnerFinalOffset; 283 | mUsingCustomStart = mOldmUsingCustomStart; 284 | setRefreshing(false); 285 | 286 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && childIsListview) { 287 | mListView.removeFooterView(mListViewFooter); 288 | } 289 | } 290 | } 291 | } 292 | 293 | @Override 294 | public void onScrollStateChanged(AbsListView view, int scrollState) { 295 | if (canLoad()) { 296 | if (allowUpTorefresh) { 297 | setUpRefreshing(true); //显示加载动画 298 | } 299 | } 300 | } 301 | 302 | @Override 303 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 304 | 305 | } 306 | 307 | //判断是否加载 308 | private boolean canLoad() { 309 | return isBottom() && !isLoading && isPullUp(); 310 | } 311 | 312 | //判断是否到达了底部 313 | private boolean isBottom() { 314 | if (mListView == null) { 315 | int childs = getChildCount(); 316 | if (childs > 0) { 317 | View childView = getChildAt(0); 318 | if (childView instanceof ListView) { 319 | childIsListview = true; 320 | mListView = (ListView) childView; 321 | //添加空白布局,让用户知道下面还有列表 322 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 323 | mListView.addFooterView(mListViewFooter); 324 | mListView.removeFooterView(mListViewFooter); 325 | } 326 | mListView.setOnScrollListener(this); 327 | } 328 | } 329 | } 330 | if (childIsListview && mListView != null && mListView.getAdapter() != null) { 331 | //可见的最后一个列表是否是最后一个listview的列表 332 | return mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() - 1); 333 | } 334 | return false; 335 | } 336 | 337 | 338 | private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() { 339 | @Override 340 | public void onAnimationStart(Animation animation) { 341 | } 342 | 343 | @Override 344 | public void onAnimationRepeat(Animation animation) { 345 | } 346 | 347 | @Override 348 | public void onAnimationEnd(Animation animation) { 349 | if (mRefreshing) { 350 | // Make sure the progress view is fully visible 351 | mProgress.setAlpha(MAX_ALPHA); 352 | mProgress.start(); 353 | if (mNotify) { 354 | if (mListener != null) { 355 | if (isUp) { //上拉刷新则返回2 356 | mListener.onRefresh(2); 357 | } else { //下拉刷新返回1 358 | mListener.onRefresh(1); 359 | } 360 | } 361 | } 362 | if (isUp) { 363 | mCurrentTargetOffsetTop = UpRefreshingDistance; 364 | } else { 365 | mCurrentTargetOffsetTop = mCircleView.getTop(); 366 | } 367 | } else { 368 | reset(); 369 | } 370 | } 371 | }; 372 | 373 | private void reset() { 374 | mCircleView.clearAnimation(); 375 | mProgress.stop(); 376 | mCircleView.setVisibility(View.GONE); 377 | setColorViewAlpha(MAX_ALPHA); 378 | // Return the circle to its start position 379 | if (mScale) { 380 | setAnimationProgress(0 /* animation complete and view is hidden */); 381 | } else { 382 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop, 383 | true /* requires update */); 384 | } 385 | if (isUp) { 386 | mCurrentTargetOffsetTop = UpRefreshingDistance; 387 | } else { 388 | mCurrentTargetOffsetTop = mCircleView.getTop(); 389 | } 390 | } 391 | 392 | @Override 393 | protected void onDetachedFromWindow() { 394 | super.onDetachedFromWindow(); 395 | reset(); 396 | } 397 | 398 | private void setColorViewAlpha(int targetAlpha) { 399 | mCircleView.getBackground().setAlpha(targetAlpha); 400 | mProgress.setAlpha(targetAlpha); 401 | } 402 | 403 | /** 404 | * The refresh indicator starting and resting position is always positioned 405 | * near the top of the refreshing content. This position is a consistent 406 | * location, but can be adjusted in either direction based on whether or not 407 | * there is a toolbar or actionbar present. 408 | * 409 | * @param scale Set to true if there is no view at a higher z-order than 410 | * where the progress spinner is set to appear. 411 | * @param start The offset in pixels from the top of this view at which the 412 | * progress spinner should appear. 413 | * @param end The offset in pixels from the top of this view at which the 414 | * progress spinner should come to rest after a successful swipe 415 | * gesture. 416 | */ 417 | public void setProgressViewOffset(boolean scale, int start, int end) { 418 | mScale = scale; 419 | mCircleView.setVisibility(View.GONE); 420 | mOriginalOffsetTop = mCurrentTargetOffsetTop = start; 421 | mSpinnerFinalOffset = end; 422 | mUsingCustomStart = true; 423 | mCircleView.invalidate(); 424 | } 425 | 426 | /** 427 | * The refresh indicator resting position is always positioned near the top 428 | * of the refreshing content. This position is a consistent location, but 429 | * can be adjusted in either direction based on whether or not there is a 430 | * toolbar or actionbar present. 431 | * 432 | * @param scale Set to true if there is no view at a higher z-order than 433 | * where the progress spinner is set to appear. 434 | * @param end The offset in pixels from the top of this view at which the 435 | * progress spinner should come to rest after a successful swipe 436 | * gesture. 437 | */ 438 | public void setProgressViewEndTarget(boolean scale, int end) { 439 | mSpinnerFinalOffset = end; 440 | mScale = scale; 441 | mCircleView.invalidate(); 442 | } 443 | 444 | /** 445 | * One of DEFAULT, or LARGE. 446 | */ 447 | public void setSize(int size) { 448 | if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) { 449 | return; 450 | } 451 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 452 | if (size == MaterialProgressDrawable.LARGE) { 453 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); 454 | } else { 455 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); 456 | } 457 | // force the bounds of the progress circle inside the circle view to 458 | // update by setting it to null before updating its size and then 459 | // re-setting it 460 | mCircleView.setImageDrawable(null); 461 | mProgress.updateSizes(size); 462 | mCircleView.setImageDrawable(mProgress); 463 | } 464 | 465 | protected int getChildDrawingOrder(int childCount, int i) { 466 | if (mCircleViewIndex < 0) { 467 | return i; 468 | } else if (i == childCount - 1) { 469 | // Draw the selected child last 470 | return mCircleViewIndex; 471 | } else if (i >= mCircleViewIndex) { 472 | // Move the children after the selected child earlier one 473 | return i + 1; 474 | } else { 475 | // Keep the children before the selected child the same 476 | return i; 477 | } 478 | } 479 | 480 | private void createProgressView() { 481 | mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER / 2); 482 | mProgress = new MaterialProgressDrawable(getContext(), this); 483 | mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); 484 | mCircleView.setImageDrawable(mProgress); 485 | mCircleView.setVisibility(View.GONE); 486 | addView(mCircleView); 487 | } 488 | 489 | /** 490 | * Set the listener to be notified when a refresh is triggered via the swipe 491 | * gesture. 492 | */ 493 | public void setOnRefreshListener(OnRefreshListener listener) { 494 | mListener = listener; 495 | if (refreshWhenCreate) { //允许在布局进入时就刷新,则回调 496 | mListener.onRefresh(0); 497 | } 498 | } 499 | 500 | /** 501 | * Pre API 11, alpha is used to make the progress circle appear instead of scale. 502 | */ 503 | private boolean isAlphaUsedForScale() { 504 | return android.os.Build.VERSION.SDK_INT < 11; 505 | } 506 | 507 | /** 508 | * Notify the widget that refresh state has changed. Do not call this when 509 | * refresh is triggered by a swipe gesture. 510 | * 511 | * @param refreshing Whether or not the view should show refresh progress. 512 | */ 513 | public void setRefreshing(boolean refreshing) { 514 | if (refreshing && mRefreshing != refreshing) { 515 | // scale and show 516 | mRefreshing = refreshing; 517 | int endTarget = 0; 518 | if (!mUsingCustomStart) { 519 | endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop); 520 | } else { 521 | endTarget = (int) mSpinnerFinalOffset; 522 | } 523 | setTargetOffsetTopAndBottom(endTarget, 524 | true /* requires update */); 525 | 526 | // setTargetOffsetTopAndBottom((int) mSpinnerFinalOffset + mOriginalOffsetTop, 527 | // true /* requires update */); 528 | mNotify = false; 529 | startScaleUpAnimation(mRefreshListener); 530 | } else { 531 | setRefreshing(refreshing, false /* notify */); 532 | } 533 | } 534 | 535 | private void startScaleUpAnimation(Animation.AnimationListener listener) { 536 | mCircleView.setVisibility(View.VISIBLE); 537 | if (android.os.Build.VERSION.SDK_INT >= 11) { 538 | // Pre API 11, alpha is used in place of scale up to show the 539 | // progress circle appearing. 540 | // Don't adjust the alpha during appearance otherwise. 541 | mProgress.setAlpha(MAX_ALPHA); 542 | } 543 | mScaleAnimation = new Animation() { 544 | @Override 545 | public void applyTransformation(float interpolatedTime, Transformation t) { 546 | setAnimationProgress(interpolatedTime); 547 | } 548 | }; 549 | mScaleAnimation.setDuration(mMediumAnimationDuration); 550 | if (listener != null) { 551 | mCircleView.setAnimationListener(listener); 552 | } 553 | mCircleView.clearAnimation(); 554 | mCircleView.startAnimation(mScaleAnimation); 555 | } 556 | 557 | /** 558 | * Pre API 11, this does an alpha animation. 559 | * 560 | * @param progress 561 | */ 562 | private void setAnimationProgress(float progress) { 563 | if (isAlphaUsedForScale()) { 564 | setColorViewAlpha((int) (progress * MAX_ALPHA)); 565 | } else { 566 | ViewCompat.setScaleX(mCircleView, progress); 567 | ViewCompat.setScaleY(mCircleView, progress); 568 | } 569 | } 570 | 571 | private void setRefreshing(boolean refreshing, final boolean notify) { 572 | if (mRefreshing != refreshing) { 573 | mNotify = notify; 574 | ensureTarget(); 575 | mRefreshing = refreshing; 576 | if (mRefreshing) { 577 | animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener); 578 | } else { 579 | startScaleDownAnimation(mRefreshListener); 580 | } 581 | } 582 | } 583 | 584 | private void startScaleDownAnimation(Animation.AnimationListener listener) { 585 | mScaleDownAnimation = new Animation() { 586 | @Override 587 | public void applyTransformation(float interpolatedTime, Transformation t) { 588 | setAnimationProgress(1 - interpolatedTime); 589 | } 590 | }; 591 | mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION); 592 | mCircleView.setAnimationListener(listener); 593 | mCircleView.clearAnimation(); 594 | mCircleView.startAnimation(mScaleDownAnimation); 595 | } 596 | 597 | private void startProgressAlphaStartAnimation() { 598 | mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); 599 | } 600 | 601 | private void startProgressAlphaMaxAnimation() { 602 | mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA); 603 | } 604 | 605 | private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) { 606 | // Pre API 11, alpha is used in place of scale. Don't also use it to 607 | // show the trigger point. 608 | if (mScale && isAlphaUsedForScale()) { 609 | return null; 610 | } 611 | Animation alpha = new Animation() { 612 | @Override 613 | public void applyTransformation(float interpolatedTime, Transformation t) { 614 | mProgress 615 | .setAlpha((int) (startingAlpha + ((endingAlpha - startingAlpha) 616 | * interpolatedTime))); 617 | } 618 | }; 619 | alpha.setDuration(ALPHA_ANIMATION_DURATION); 620 | // Clear out the previous animation listeners. 621 | mCircleView.setAnimationListener(null); 622 | mCircleView.clearAnimation(); 623 | mCircleView.startAnimation(alpha); 624 | return alpha; 625 | } 626 | 627 | /** 628 | * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)} 629 | */ 630 | @Deprecated 631 | public void setProgressBackgroundColor(int colorRes) { 632 | setProgressBackgroundColorSchemeResource(colorRes); 633 | } 634 | 635 | /** 636 | * Set the background color of the progress spinner disc. 637 | * 638 | * @param colorRes Resource id of the color. 639 | */ 640 | public void setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) { 641 | setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes)); 642 | } 643 | 644 | /** 645 | * Set the background color of the progress spinner disc. 646 | * 647 | * @param color 648 | */ 649 | public void setProgressBackgroundColorSchemeColor(@ColorInt int color) { 650 | mCircleView.setBackgroundColor(color); 651 | mProgress.setBackgroundColor(color); 652 | } 653 | 654 | // /** 655 | // * @deprecated Use {@link #setColorSchemeResources(int...)} 656 | // */ 657 | // @Deprecated 658 | // public void setColorScheme(@ColorInt int... colors) { 659 | // setColorSchemeResources(colors); 660 | // } 661 | 662 | /** 663 | * Set the color resources used in the progress animation from color resources. 664 | * The first color will also be the color of the bar that grows in response 665 | * to a user swipe gesture. 666 | * 667 | * @param colorResIds 668 | */ 669 | public void setColorSchemeResources(@ColorRes int... colorResIds) { 670 | final Resources res = getResources(); 671 | int[] colorRes = new int[colorResIds.length]; 672 | for (int i = 0; i < colorResIds.length; i++) { 673 | colorRes[i] = res.getColor(colorResIds[i]); 674 | } 675 | setColorSchemeColors(colorRes); 676 | } 677 | 678 | /** 679 | * Set the colors used in the progress animation. The first 680 | * color will also be the color of the bar that grows in response to a user 681 | * swipe gesture. 682 | * 683 | * @param colors 684 | */ 685 | @ColorInt 686 | public void setColorSchemeColors(int... colors) { 687 | ensureTarget(); 688 | mProgress.setColorSchemeColors(colors); 689 | } 690 | 691 | /** 692 | * @return Whether the SwipeRefreshWidget is actively showing refresh 693 | * progress. 694 | */ 695 | public boolean isRefreshing() { 696 | return mRefreshing; 697 | } 698 | 699 | private void ensureTarget() { 700 | // Don't bother getting the parent height if the parent hasn't been laid 701 | // out yet. 702 | if (mTarget == null) { 703 | for (int i = 0; i < getChildCount(); i++) { 704 | View child = getChildAt(i); 705 | if (!child.equals(mCircleView)) { 706 | mTarget = child; 707 | break; 708 | } 709 | } 710 | } 711 | } 712 | 713 | /** 714 | * Set the distance to trigger a sync in dips 715 | * 716 | * @param distance 717 | */ 718 | public void setDistanceToTriggerSync(int distance) { 719 | mTotalDragDistance = distance; 720 | } 721 | 722 | @Override 723 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 724 | final int width = getMeasuredWidth(); 725 | final int height = getMeasuredHeight(); 726 | if (getChildCount() == 0) { 727 | return; 728 | } 729 | if (mTarget == null) { 730 | ensureTarget(); 731 | } 732 | if (mTarget == null) { 733 | return; 734 | } 735 | final View child = mTarget; 736 | final int childLeft = getPaddingLeft(); 737 | final int childTop = getPaddingTop(); 738 | final int childWidth = width - getPaddingLeft() - getPaddingRight(); 739 | final int childHeight = height - getPaddingTop() - getPaddingBottom(); 740 | child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); 741 | int circleWidth = mCircleView.getMeasuredWidth(); 742 | int circleHeight = mCircleView.getMeasuredHeight(); 743 | mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop, 744 | (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight); 745 | 746 | } 747 | 748 | 749 | @Override 750 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 751 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 752 | if (mTarget == null) { 753 | ensureTarget(); 754 | } 755 | if (mTarget == null) { 756 | return; 757 | } 758 | mMeasuredHeight = getMeasuredHeight(); 759 | float v = mAFloat * mMeasuredHeight; 760 | UpRefreshingDistance = (int) v; 761 | mTarget.measure(MeasureSpec.makeMeasureSpec( 762 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 763 | MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( 764 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); 765 | mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY), 766 | MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY)); 767 | if (!mUsingCustomStart && !mOriginalOffsetCalculated) { 768 | mOriginalOffsetCalculated = true; 769 | mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); 770 | } 771 | mCircleViewIndex = -1; 772 | // Get the index of the circleview. 773 | for (int index = 0; index < getChildCount(); index++) { 774 | if (getChildAt(index) == mCircleView) { 775 | mCircleViewIndex = index; 776 | break; 777 | } 778 | } 779 | 780 | //一进入页面就自动刷新 781 | if (refreshWhenCreate) { 782 | setRefreshing(true); 783 | refreshWhenCreate = false; 784 | } 785 | } 786 | 787 | /** 788 | * Get the diameter of the progress circle that is displayed as part of the 789 | * swipe to refresh layout. This is not valid until a measure pass has 790 | * completed. 791 | * 792 | * @return Diameter in pixels of the progress circle view. 793 | */ 794 | public int getProgressCircleDiameter() { 795 | return mCircleView != null ? mCircleView.getMeasuredHeight() : 0; 796 | } 797 | 798 | /** 799 | * @return Whether it is possible for the child view of this layout to 800 | * scroll up. Override this if the child view is a custom view. 801 | */ 802 | public boolean canChildScrollUp() { 803 | if (android.os.Build.VERSION.SDK_INT < 14) { 804 | if (mTarget instanceof AbsListView) { 805 | final AbsListView absListView = (AbsListView) mTarget; 806 | return absListView.getChildCount() > 0 807 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 808 | .getTop() < absListView.getPaddingTop()); 809 | } else { 810 | return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; 811 | } 812 | } else { 813 | return ViewCompat.canScrollVertically(mTarget, -1); 814 | } 815 | } 816 | 817 | @Override 818 | public boolean onInterceptTouchEvent(MotionEvent ev) { 819 | ensureTarget(); 820 | 821 | final int action = MotionEventCompat.getActionMasked(ev); 822 | 823 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 824 | mReturningToStart = false; 825 | } 826 | 827 | if (!isEnabled() || mReturningToStart || canChildScrollUp() 828 | || mRefreshing || mNestedScrollInProgress) { 829 | // Fail fast if we're not in a state where a swipe is possible 830 | return false; 831 | } 832 | 833 | switch (action) { 834 | case MotionEvent.ACTION_DOWN: 835 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true); 836 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 837 | mIsBeingDragged = false; 838 | final float initialDownY = getMotionEventY(ev, mActivePointerId); 839 | if (initialDownY == -1) { 840 | return false; 841 | } 842 | mInitialDownY = initialDownY; 843 | break; 844 | 845 | case MotionEvent.ACTION_MOVE: //拦截手势,如果return true会执行onTouchEvent 846 | if (mActivePointerId == INVALID_POINTER) { 847 | Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id."); 848 | return false; 849 | } 850 | 851 | final float y = getMotionEventY(ev, mActivePointerId); 852 | if (y == -1) { 853 | return false; 854 | } 855 | final float yDiff = y - mInitialDownY; 856 | if (yDiff > mTouchSlop && !mIsBeingDragged) { 857 | mInitialMotionY = mInitialDownY + mTouchSlop; 858 | mIsBeingDragged = true; 859 | mProgress.setAlpha(STARTING_PROGRESS_ALPHA); 860 | } 861 | break; 862 | 863 | case MotionEventCompat.ACTION_POINTER_UP: 864 | onSecondaryPointerUp(ev); 865 | break; 866 | 867 | case MotionEvent.ACTION_UP: 868 | case MotionEvent.ACTION_CANCEL: 869 | mIsBeingDragged = false; 870 | mActivePointerId = INVALID_POINTER; 871 | break; 872 | } 873 | 874 | return mIsBeingDragged; 875 | } 876 | 877 | private float getMotionEventY(MotionEvent ev, int activePointerId) { 878 | final int index = MotionEventCompat.findPointerIndex(ev, activePointerId); 879 | if (index < 0) { 880 | return -1; 881 | } 882 | return MotionEventCompat.getY(ev, index); 883 | } 884 | 885 | @Override 886 | public void requestDisallowInterceptTouchEvent(boolean b) { 887 | // if this is a List < L or another view that doesn't support nested 888 | // scrolling, ignore this request so that the vertical scroll event 889 | // isn't stolen 890 | if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView) 891 | || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) { 892 | // Nope. 893 | } else { 894 | super.requestDisallowInterceptTouchEvent(b); 895 | } 896 | } 897 | 898 | // NestedScrollingParent 899 | 900 | @Override 901 | public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 902 | return isEnabled() && canChildScrollUp() && !mReturningToStart && !mRefreshing 903 | && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 904 | } 905 | 906 | @Override 907 | public void onNestedScrollAccepted(View child, View target, int axes) { 908 | // Reset the counter of how much leftover scroll needs to be consumed. 909 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); 910 | // Dispatch up to the nested parent 911 | startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL); 912 | mTotalUnconsumed = 0; 913 | mNestedScrollInProgress = true; 914 | } 915 | 916 | @Override 917 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 918 | // If we are in the middle of consuming, a scroll, then we want to move the spinner back up 919 | // before allowing the list to scroll 920 | if (dy > 0 && mTotalUnconsumed > 0) { 921 | if (dy > mTotalUnconsumed) { 922 | consumed[1] = dy - (int) mTotalUnconsumed; 923 | mTotalUnconsumed = 0; 924 | } else { 925 | mTotalUnconsumed -= dy; 926 | consumed[1] = dy; 927 | 928 | } 929 | moveSpinner(mTotalUnconsumed); 930 | } 931 | 932 | // If a client layout is using a custom start position for the circle 933 | // view, they mean to hide it again before scrolling the child view 934 | // If we get back to mTotalUnconsumed == 0 and there is more to go, hide 935 | // the circle so it isn't exposed if its blocking content is moved 936 | if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0 937 | && Math.abs(dy - consumed[1]) > 0) { 938 | mCircleView.setVisibility(View.GONE); 939 | } 940 | 941 | // Now let our nested parent consume the leftovers 942 | final int[] parentConsumed = mParentScrollConsumed; 943 | if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) { 944 | consumed[0] += parentConsumed[0]; 945 | consumed[1] += parentConsumed[1]; 946 | } 947 | } 948 | 949 | @Override 950 | public int getNestedScrollAxes() { 951 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 952 | } 953 | 954 | @Override 955 | public void onStopNestedScroll(View target) { 956 | mNestedScrollingParentHelper.onStopNestedScroll(target); 957 | mNestedScrollInProgress = false; 958 | // Finish the spinner for nested scrolling if we ever consumed any 959 | // unconsumed nested scroll 960 | if (mTotalUnconsumed > 0) { 961 | finishSpinner(mTotalUnconsumed); 962 | mTotalUnconsumed = 0; 963 | } 964 | // Dispatch up our nested parent 965 | stopNestedScroll(); 966 | } 967 | 968 | @Override 969 | public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed, 970 | final int dxUnconsumed, final int dyUnconsumed) { 971 | // Dispatch up to the nested parent first 972 | dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 973 | mParentOffsetInWindow); 974 | 975 | // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are 976 | // sometimes between two nested scrolling views, we need a way to be able to know when any 977 | // nested scrolling parent has stopped handling events. We do that by using the 978 | // 'offset in window 'functionality to see if we have been moved from the event. 979 | // This is a decent indication of whether we should take over the event stream or not. 980 | final int dy = dyUnconsumed + mParentOffsetInWindow[1]; 981 | if (dy < 0) { 982 | mTotalUnconsumed += Math.abs(dy); 983 | moveSpinner(mTotalUnconsumed); 984 | } 985 | } 986 | 987 | // NestedScrollingChild 988 | 989 | @Override 990 | public void setNestedScrollingEnabled(boolean enabled) { 991 | mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled); 992 | } 993 | 994 | @Override 995 | public boolean isNestedScrollingEnabled() { 996 | return mNestedScrollingChildHelper.isNestedScrollingEnabled(); 997 | } 998 | 999 | @Override 1000 | public boolean startNestedScroll(int axes) { 1001 | return mNestedScrollingChildHelper.startNestedScroll(axes); 1002 | } 1003 | 1004 | @Override 1005 | public void stopNestedScroll() { 1006 | mNestedScrollingChildHelper.stopNestedScroll(); 1007 | } 1008 | 1009 | @Override 1010 | public boolean hasNestedScrollingParent() { 1011 | return mNestedScrollingChildHelper.hasNestedScrollingParent(); 1012 | } 1013 | 1014 | @Override 1015 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 1016 | int dyUnconsumed, int[] offsetInWindow) { 1017 | return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, 1018 | dxUnconsumed, dyUnconsumed, offsetInWindow); 1019 | } 1020 | 1021 | @Override 1022 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 1023 | return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 1024 | } 1025 | 1026 | @Override 1027 | public boolean onNestedPreFling(View target, float velocityX, 1028 | float velocityY) { 1029 | return dispatchNestedPreFling(velocityX, velocityY); 1030 | } 1031 | 1032 | @Override 1033 | public boolean onNestedFling(View target, float velocityX, float velocityY, 1034 | boolean consumed) { 1035 | return dispatchNestedFling(velocityX, velocityY, consumed); 1036 | } 1037 | 1038 | @Override 1039 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 1040 | return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 1041 | } 1042 | 1043 | @Override 1044 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 1045 | return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); 1046 | } 1047 | 1048 | private boolean isAnimationRunning(Animation animation) { 1049 | return animation != null && animation.hasStarted() && !animation.hasEnded(); 1050 | } 1051 | 1052 | private void moveSpinner(float overscrollTop) { //移动转盘 1053 | mProgress.showArrow(true); 1054 | float originalDragPercent = overscrollTop / mTotalDragDistance; 1055 | float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); 1056 | float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; 1057 | float extraOS = Math.abs(overscrollTop) - mTotalDragDistance; 1058 | float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset - mOriginalOffsetTop 1059 | : mSpinnerFinalOffset; 1060 | float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) 1061 | / slingshotDist); 1062 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( 1063 | (tensionSlingshotPercent / 4), 2)) * 2f; 1064 | float extraMove = (slingshotDist) * tensionPercent * 2; 1065 | int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove); 1066 | // where 1.0f is a full circle 1067 | if (mCircleView.getVisibility() != View.VISIBLE) { 1068 | mCircleView.setVisibility(View.VISIBLE); 1069 | } 1070 | if (!mScale) { 1071 | ViewCompat.setScaleX(mCircleView, 1f); 1072 | ViewCompat.setScaleY(mCircleView, 1f); 1073 | } 1074 | 1075 | if (mScale) { 1076 | setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance)); 1077 | } 1078 | if (overscrollTop < mTotalDragDistance) { 1079 | if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA 1080 | && !isAnimationRunning(mAlphaStartAnimation)) { 1081 | // Animate the alpha 1082 | startProgressAlphaStartAnimation(); 1083 | } 1084 | } else { 1085 | if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) { 1086 | // Animate the alpha 1087 | startProgressAlphaMaxAnimation(); 1088 | } 1089 | } 1090 | float strokeStart = adjustedPercent * .8f; 1091 | mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); 1092 | mProgress.setArrowScale(Math.min(1f, adjustedPercent)); 1093 | 1094 | float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; 1095 | mProgress.setProgressRotation(rotation); 1096 | setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */); 1097 | } 1098 | 1099 | private void finishSpinner(float overscrollTop) { 1100 | if (overscrollTop > mTotalDragDistance) { 1101 | setRefreshing(true, true /* notify */); 1102 | } else { 1103 | // cancel refresh 1104 | mRefreshing = false; 1105 | mProgress.setStartEndTrim(0f, 0f); 1106 | Animation.AnimationListener listener = null; 1107 | if (!mScale) { 1108 | listener = new Animation.AnimationListener() { 1109 | 1110 | @Override 1111 | public void onAnimationStart(Animation animation) { 1112 | } 1113 | 1114 | @Override 1115 | public void onAnimationEnd(Animation animation) { 1116 | if (!mScale) { 1117 | startScaleDownAnimation(null); 1118 | } 1119 | } 1120 | 1121 | @Override 1122 | public void onAnimationRepeat(Animation animation) { 1123 | } 1124 | 1125 | }; 1126 | } 1127 | animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener); 1128 | mProgress.showArrow(false); 1129 | } 1130 | } 1131 | 1132 | @Override 1133 | public boolean onTouchEvent(MotionEvent ev) { 1134 | final int action = MotionEventCompat.getActionMasked(ev); 1135 | int pointerIndex = -1; 1136 | 1137 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 1138 | mReturningToStart = false; 1139 | } 1140 | 1141 | if (!isEnabled() || mReturningToStart || canChildScrollUp() || mNestedScrollInProgress) { 1142 | // Fail fast if we're not in a state where a swipe is possible 1143 | return false; 1144 | } 1145 | 1146 | switch (action) { 1147 | case MotionEvent.ACTION_DOWN: 1148 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 1149 | mIsBeingDragged = false; 1150 | break; 1151 | 1152 | case MotionEvent.ACTION_MOVE: { 1153 | pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1154 | if (pointerIndex < 0) { 1155 | Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id."); 1156 | return false; 1157 | } 1158 | 1159 | final float y = MotionEventCompat.getY(ev, pointerIndex); 1160 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 1161 | if (mIsBeingDragged) { 1162 | if (overscrollTop > 0) { 1163 | moveSpinner(overscrollTop); 1164 | } else { 1165 | return false; 1166 | } 1167 | } 1168 | break; 1169 | } 1170 | case MotionEventCompat.ACTION_POINTER_DOWN: { 1171 | pointerIndex = MotionEventCompat.getActionIndex(ev); 1172 | if (pointerIndex < 0) { 1173 | Log.e(LOG_TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index."); 1174 | return false; 1175 | } 1176 | mActivePointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 1177 | break; 1178 | } 1179 | 1180 | case MotionEventCompat.ACTION_POINTER_UP: 1181 | onSecondaryPointerUp(ev); 1182 | break; 1183 | case MotionEvent.ACTION_UP: { 1184 | pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1185 | if (pointerIndex < 0) { 1186 | Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id."); 1187 | return false; 1188 | } 1189 | 1190 | final float y = MotionEventCompat.getY(ev, pointerIndex); 1191 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 1192 | mIsBeingDragged = false; 1193 | finishSpinner(overscrollTop); 1194 | mActivePointerId = INVALID_POINTER; 1195 | return false; 1196 | } 1197 | case MotionEvent.ACTION_CANCEL: 1198 | return false; 1199 | } 1200 | 1201 | return true; 1202 | } 1203 | 1204 | private void animateOffsetToCorrectPosition(int from, Animation.AnimationListener listener) { 1205 | mFrom = from; 1206 | mAnimateToCorrectPosition.reset(); 1207 | mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION); 1208 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator); 1209 | if (listener != null) { 1210 | mCircleView.setAnimationListener(listener); 1211 | } 1212 | mCircleView.clearAnimation(); 1213 | mCircleView.startAnimation(mAnimateToCorrectPosition); 1214 | } 1215 | 1216 | private void animateOffsetToStartPosition(int from, Animation.AnimationListener listener) { 1217 | if (mScale) { 1218 | // Scale the item back down 1219 | startScaleDownReturnToStartAnimation(from, listener); 1220 | } else { 1221 | mFrom = from; 1222 | mAnimateToStartPosition.reset(); 1223 | mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION); 1224 | mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); 1225 | if (listener != null) { 1226 | mCircleView.setAnimationListener(listener); 1227 | } 1228 | mCircleView.clearAnimation(); 1229 | mCircleView.startAnimation(mAnimateToStartPosition); 1230 | } 1231 | } 1232 | 1233 | private final Animation mAnimateToCorrectPosition = new Animation() { 1234 | @Override 1235 | public void applyTransformation(float interpolatedTime, Transformation t) { 1236 | int targetTop = 0; 1237 | int endTarget = 0; 1238 | if (!mUsingCustomStart) { 1239 | endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop)); 1240 | } else { 1241 | endTarget = (int) mSpinnerFinalOffset; 1242 | } 1243 | targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime)); 1244 | int offset = targetTop - mCircleView.getTop(); 1245 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 1246 | mProgress.setArrowScale(1 - interpolatedTime); 1247 | } 1248 | }; 1249 | 1250 | private void moveToStart(float interpolatedTime) { 1251 | int targetTop = 0; 1252 | targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime)); 1253 | int offset = targetTop - mCircleView.getTop(); 1254 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 1255 | } 1256 | 1257 | private final Animation mAnimateToStartPosition = new Animation() { 1258 | @Override 1259 | public void applyTransformation(float interpolatedTime, Transformation t) { 1260 | moveToStart(interpolatedTime); 1261 | } 1262 | }; 1263 | 1264 | private void startScaleDownReturnToStartAnimation(int from, 1265 | Animation.AnimationListener listener) { 1266 | mFrom = from; 1267 | if (isAlphaUsedForScale()) { 1268 | mStartingScale = mProgress.getAlpha(); 1269 | } else { 1270 | mStartingScale = ViewCompat.getScaleX(mCircleView); 1271 | } 1272 | mScaleDownToStartAnimation = new Animation() { 1273 | @Override 1274 | public void applyTransformation(float interpolatedTime, Transformation t) { 1275 | float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime)); 1276 | setAnimationProgress(targetScale); 1277 | moveToStart(interpolatedTime); 1278 | } 1279 | }; 1280 | mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION); 1281 | if (listener != null) { 1282 | mCircleView.setAnimationListener(listener); 1283 | } 1284 | mCircleView.clearAnimation(); 1285 | mCircleView.startAnimation(mScaleDownToStartAnimation); 1286 | } 1287 | 1288 | private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) { 1289 | mCircleView.bringToFront(); 1290 | mCircleView.offsetTopAndBottom(offset); 1291 | if (isUp) { 1292 | mCurrentTargetOffsetTop = UpRefreshingDistance; 1293 | } else { 1294 | mCurrentTargetOffsetTop = mCircleView.getTop(); 1295 | } 1296 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { 1297 | invalidate(); 1298 | } 1299 | } 1300 | 1301 | private void onSecondaryPointerUp(MotionEvent ev) { 1302 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 1303 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 1304 | if (pointerId == mActivePointerId) { 1305 | // This was our active pointer going up. Choose a new 1306 | // active pointer and adjust accordingly. 1307 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1308 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 1309 | } 1310 | } 1311 | 1312 | /** 1313 | * Classes that wish to be notified when the swipe gesture correctly 1314 | * triggers a refresh should implement this interface. 1315 | */ 1316 | public interface OnRefreshListener { 1317 | /** 1318 | * type = 0 :刚进入界面时加载的操作,type = 1:下拉刷新的操作,type = 2:上拉刷新的操作 1319 | * type = 0 : refresh when activity was created, type = 1 : pull down to refresh, type = 2 : pull up to refresh 1320 | * 1321 | * @param type 1322 | */ 1323 | public void onRefresh(int type); 1324 | } 1325 | 1326 | } 1327 | -------------------------------------------------------------------------------- /superswiperefreshlayout/src/main/res/layout/item_null.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /superswiperefreshlayout/src/main/res/values/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | //是否在刚进入布局时就刷新 5 | //是否允许上拉刷新 6 | //底部刷新动画的位置 7 | 8 | 9 | -------------------------------------------------------------------------------- /superswiperefreshlayout/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SuperSwipeRefreshLayout 3 | 4 | -------------------------------------------------------------------------------- /superswiperefreshlayout/src/test/java/com/yazhi/superswiperefreshlayout/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yazhi.superswiperefreshlayout; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } --------------------------------------------------------------------------------