├── .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 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 1.8
51 |
52 |
53 |
54 |
55 |
56 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
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 | 
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 | }
--------------------------------------------------------------------------------