├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── androidkun
│ │ └── pulltorefreshrecyclerview
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── androidkun
│ │ │ └── pulltorefreshrecyclerview
│ │ │ ├── MainActivity.java
│ │ │ └── adapter
│ │ │ └── ModeAdapter.java
│ └── res
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── item_mode.xml
│ │ ├── layout_empty_view.xml
│ │ ├── layout_foot_view.xml
│ │ └── layout_head_view.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
│ └── androidkun
│ └── pulltorefreshrecyclerview
│ └── ExampleUnitTest.java
├── build.gradle
├── gif
└── GIF.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pulltorefreshlibrary
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── androidkun
│ │ └── pulltorefreshlibrary
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── androidkun
│ │ │ ├── LoadMoreView.java
│ │ │ ├── PullToRefreshRecyclerView.java
│ │ │ ├── RefreshHead.java
│ │ │ ├── adapter
│ │ │ ├── BaseAdapter.java
│ │ │ └── ViewHolder.java
│ │ │ └── callback
│ │ │ ├── OnItemClickListener.java
│ │ │ └── PullToRefreshListener.java
│ └── res
│ │ ├── drawable
│ │ ├── ic_arrow.png
│ │ ├── ic_load_more.png
│ │ ├── ic_refresh_arrow.png
│ │ └── ic_refreshing.png
│ │ ├── layout
│ │ ├── layout_load_more_view.xml
│ │ └── layout_refresh_head_view.xml
│ │ ├── values-zh
│ │ └── strings.xml
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── androidkun
│ └── pulltorefreshlibrary
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.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 |
18 |
19 |
--------------------------------------------------------------------------------
/.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 | 1.8
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.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 | # PullToRefreshRecyclerView
2 | 
3 |
4 | ## 1. 在Module下的build.gradle中添加依赖
5 |
6 | compile 'com.androidkun:pulltorefreshrecyclerview:1.1.0'
7 |
8 | ## 2. 在布局文件中添加PullToRefreshRecyclerView控件
9 |
10 |
14 |
15 | ## 3. 初始化PullToRefreshRecyclerView并设置属性和回调
16 |
17 | pullToRefreshRV = (PullToRefreshRecyclerView) findViewById(R.id.pullToRefreshRV);
18 | LinearLayoutManager layoutManager = new LinearLayoutManager(this);
19 | layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
20 | pullToRefreshRV.setLayoutManager(layoutManager);
21 | adapter = new ModeAdapter(this, R.layout.item_mode,data);
22 | pullToRefreshRV.setAdapter(adapter);
23 | //是否开启下拉刷新功能
24 | pullToRefreshRV.setPullRefreshEnabled(true);
25 | //是否开启上拉加载功能
26 | pullToRefreshRV.setLoadingMoreEnabled(true);
27 | //设置是否显示上次刷新的时间
28 | pullToRefreshRV.displayLastRefreshTime(true);
29 | //设置刷新回调
30 | pullToRefreshRV.setPullToRefreshListener(this);
31 | //主动触发下拉刷新操作
32 | //pullToRefreshRV.onRefresh();
33 |
34 | ### 如果想使用网格列表,则相应设置布局管理者为网格布局管理者就行了
35 |
36 | GridLayoutManager gridLayoutManager = new GridLayoutManager(this,2);
37 | recyclerView.setLayoutManager(gridLayoutManager);
38 |
39 | ### 此外也可以通过 setRefreshingResource(int resId)和setLoadMoreResource(int resId)自定义刷新箭头和加载的图标。
40 |
41 | ## 4.处理刷新加载逻辑
42 |
43 | ```
44 | @Override
45 | public void onRefresh() {
46 | pullToRefreshRV.postDelayed(new Runnable() {
47 | @Override
48 | public void run() {
49 | pullToRefreshRV.setRefreshComplete();
50 | //模拟没有数据的情况
51 | data.clear();
52 | adapter.notifyDataSetChanged();
53 | }
54 | }, 3000);
55 | }
56 |
57 | @Override
58 | public void onLoadMore() {
59 | pullToRefreshRV.postDelayed(new Runnable() {
60 | @Override
61 | public void run() {
62 | pullToRefreshRV.setLoadMoreComplete();
63 | //模拟加载数据的情况
64 | int size = data.size();
65 | for (int i = size; i < size + 4; i++) {
66 | data.add("" + i + i + i + i);
67 | }
68 | adapter.notifyDataSetChanged();
69 | }
70 | }, 3000);
71 | }
72 | ```
73 |
74 | ### 框架中提供封装好的BaseAdapter,减少编写相同的代码,提高开发效率,Demo中的ModeAdapter就是继承了BaseAdapter,代码如下:
75 |
76 | ```
77 | public class ModeAdapter extends BaseAdapter {
78 |
79 | public ModeAdapter(Context context, int layoutId, List datas) {
80 | super(context, layoutId, datas);
81 | }
82 |
83 | @Override
84 | public void convert(ViewHolder holder, Object o) {
85 | holder.setText(R.id.textView, (String) o);
86 | }
87 |
88 | }
89 | ```
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 | defaultConfig {
7 | applicationId "com.androidkun.pulltorefreshrecyclerview"
8 | minSdkVersion 16
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
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 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:23.4.0+'
28 | compile 'com.android.support:design:23.4.0+'
29 | testCompile 'junit:junit:4.12'
30 | compile project(':pulltorefreshlibrary')
31 | }
32 |
--------------------------------------------------------------------------------
/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 D:\AndroidStudio\AndroidStudio\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/androidkun/pulltorefreshrecyclerview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.androidkun.pulltorefreshrecyclerview;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.androidkun.pulltorefreshrecyclerview", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/androidkun/pulltorefreshrecyclerview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.androidkun.pulltorefreshrecyclerview;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.support.v7.widget.LinearLayoutManager;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import com.androidkun.PullToRefreshRecyclerView;
10 | import com.androidkun.callback.PullToRefreshListener;
11 | import com.androidkun.pulltorefreshrecyclerview.adapter.ModeAdapter;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 |
17 | public class MainActivity extends AppCompatActivity implements PullToRefreshListener {
18 |
19 | private PullToRefreshRecyclerView recyclerView;
20 | private List data;
21 | private ModeAdapter adapter;
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.activity_main);
27 | data = new ArrayList<>();
28 | data.add("0000");
29 | data.add("1111");
30 | data.add("2222");
31 | data.add("3333");
32 | data.add("4444");
33 | recyclerView = (PullToRefreshRecyclerView) findViewById(R.id.recyclerView);
34 | //添加HeaderView
35 | View headView = View.inflate(this, R.layout.layout_head_view, null);
36 | recyclerView.addHeaderView(headView);
37 | //添加FooterView
38 | View footerView = View.inflate(this, R.layout.layout_foot_view, null);
39 | recyclerView.addFooterView(footerView);
40 | //设置EmptyView
41 | View emptyView = View.inflate(this, R.layout.layout_empty_view, null);
42 | emptyView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
43 | ViewGroup.LayoutParams.MATCH_PARENT));
44 | recyclerView.setEmptyView(emptyView);
45 | LinearLayoutManager layoutManager = new LinearLayoutManager(this);
46 | layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
47 | recyclerView.setLayoutManager(layoutManager);
48 | adapter = new ModeAdapter(this, data);
49 | recyclerView.setAdapter(adapter);
50 | //设置是否开启上拉加载
51 | recyclerView.setLoadingMoreEnabled(true);
52 | //设置是否开启下拉刷新
53 | recyclerView.setPullRefreshEnabled(true);
54 | //设置是否显示上次刷新的时间
55 | recyclerView.displayLastRefreshTime(true);
56 | //设置刷新回调
57 | recyclerView.setPullToRefreshListener(this);
58 | //主动触发下拉刷新操作
59 | recyclerView.onRefresh();
60 | }
61 |
62 | @Override
63 | public void onRefresh() {
64 | recyclerView.postDelayed(new Runnable() {
65 | @Override
66 | public void run() {
67 | recyclerView.setRefreshComplete();
68 | //模拟没有数据的情况
69 | data.clear();
70 | adapter.notifyDataSetChanged();
71 | }
72 | }, 3000);
73 | }
74 |
75 | int i = 0;
76 | @Override
77 | public void onLoadMore() {
78 | recyclerView.postDelayed(new Runnable() {
79 | @Override
80 | public void run() {
81 | i++;
82 | if(i>=4){
83 | recyclerView.setLoadMoreFail();
84 | return;
85 | }
86 | recyclerView.setLoadMoreComplete();
87 | //模拟加载数据的情况
88 | int size = data.size();
89 | for (int i = size; i < size + 4; i++) {
90 | data.add("" + i + i + i + i);
91 | }
92 | adapter.notifyDataSetChanged();
93 |
94 | }
95 | }, 3000);
96 | }
97 |
98 | @Override
99 | protected void onDestroy() {
100 | super.onDestroy();
101 | //解决部分机型内存泄漏问题
102 | recyclerView.setRefreshComplete();
103 | recyclerView.setLoadMoreComplete();
104 | recyclerView.loadMoreEnd();
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/androidkun/pulltorefreshrecyclerview/adapter/ModeAdapter.java:
--------------------------------------------------------------------------------
1 | package com.androidkun.pulltorefreshrecyclerview.adapter;
2 |
3 | import android.content.Context;
4 |
5 | import com.androidkun.adapter.BaseAdapter;
6 | import com.androidkun.adapter.ViewHolder;
7 | import com.androidkun.pulltorefreshrecyclerview.R;
8 |
9 | import java.util.List;
10 |
11 |
12 | /**
13 | * Created by Kun on 2016/12/14.
14 | * GitHub: https://github.com/AndroidKun
15 | * CSDN: http://blog.csdn.net/a1533588867
16 | * Description:模版
17 | */
18 |
19 | public class ModeAdapter extends BaseAdapter {
20 |
21 | public ModeAdapter(Context context, List datas) {
22 | super(context, R.layout.item_mode, datas);
23 | }
24 |
25 | @Override
26 | public void convert(ViewHolder holder, Object o) {
27 | holder.setText(R.id.textView,(String)o);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_mode.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_empty_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_foot_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_head_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/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 | PullToRefreshRecyclerView
3 |
4 | 释放立即刷新
5 | 下拉刷新
6 | 正在刷新
7 | 刷新成功
8 | 刷新失败
9 | 加载失败
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/androidkun/pulltorefreshrecyclerview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.androidkun.pulltorefreshrecyclerview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.3'
9 | classpath 'com.novoda:bintray-release:0.3.4'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
26 | allprojects {
27 | tasks.withType(Javadoc) {
28 | options{
29 | encoding "UTF-8"
30 | charSet 'UTF-8'
31 | links "http://docs.oracle.com/javase/7/docs/api"
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/gif/GIF.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/gif/GIF.gif
--------------------------------------------------------------------------------
/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 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/pulltorefreshlibrary/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/pulltorefreshlibrary/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.novoda.bintray-release'//添加
3 |
4 | android {
5 | compileSdkVersion 23
6 | buildToolsVersion "23.0.3"
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | compile fileTree(dir: 'libs', include: ['*.jar'])
27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
28 | exclude group: 'com.android.support', module: 'support-annotations'
29 | })
30 | compile 'com.android.support:appcompat-v7:23.4.0+'
31 | compile 'com.android.support:design:23.4.0+'
32 | testCompile 'junit:junit:4.12'
33 | }
34 |
35 | publish {
36 | userOrg = 'androidkun'
37 | groupId = 'com.androidkun'
38 | artifactId = 'pulltorefreshrecyclerview'
39 | publishVersion = '1.0.9'
40 | desc = 'An Pull To Refresh RecyclerView for Android, support pull down and pull up to refresh!'
41 | website = 'https://github.com/AndroidKun/PullToRefreshRecyclerView'
42 | }
--------------------------------------------------------------------------------
/pulltorefreshlibrary/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 D:\AndroidStudio\AndroidStudio\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/pulltorefreshlibrary/src/androidTest/java/com/androidkun/pulltorefreshlibrary/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.androidkun.pulltorefreshlibrary;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.androidkun.pulltorefreshlibrary.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/pulltorefreshlibrary/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/pulltorefreshlibrary/src/main/java/com/androidkun/LoadMoreView.java:
--------------------------------------------------------------------------------
1 | package com.androidkun;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.util.AttributeSet;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.animation.LinearInterpolator;
9 | import android.widget.ImageView;
10 | import android.widget.LinearLayout;
11 | import android.widget.TextView;
12 |
13 | /**
14 | * Created by Kun on 2017/2/8.
15 | * GitHub: https://github.com/AndroidKun
16 | * CSDN: http://blog.csdn.net/a1533588867
17 | * Description:加载更多
18 | */
19 |
20 | public class LoadMoreView extends LinearLayout{
21 | private ImageView imageLoadMore;
22 | private TextView textTip;
23 | private ValueAnimator animator;
24 |
25 | public LoadMoreView(Context context) {
26 | this(context,null);
27 | }
28 |
29 | public LoadMoreView(Context context, AttributeSet attrs) {
30 | this(context, attrs,0);
31 | }
32 |
33 | public LoadMoreView(Context context, AttributeSet attrs, int defStyleAttr) {
34 | super(context, attrs, defStyleAttr);
35 | View loadMoreContentView = LayoutInflater.from(getContext()).inflate(
36 | R.layout.layout_load_more_view, null);
37 | imageLoadMore = (ImageView) loadMoreContentView.findViewById(R.id.imageLoadMore);
38 | textTip = (TextView) loadMoreContentView.findViewById(R.id.textTip);
39 | textTip.setVisibility(GONE);
40 | addView(loadMoreContentView);
41 | }
42 |
43 | public void setLoadMoreResource(int resId){
44 | imageLoadMore.setImageResource(resId);
45 | }
46 |
47 | public void startAnimation() {
48 | if(animator!=null && animator.isStarted()){
49 | return;
50 | }
51 | animator = ValueAnimator.ofFloat(imageLoadMore.getRotation()
52 | ,imageLoadMore.getRotation()+359);
53 | animator.setInterpolator(new LinearInterpolator());
54 | animator.setRepeatCount(ValueAnimator.INFINITE);
55 | animator.setDuration(1000);
56 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
57 | @Override
58 | public void onAnimationUpdate(ValueAnimator animation) {
59 | imageLoadMore.setRotation((Float) animation.getAnimatedValue());
60 | }
61 | });
62 | animator.start();
63 | /* RotateAnimation animation = new RotateAnimation(0f, 359f, Animation.RELATIVE_TO_SELF,
64 | 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
65 | animation.setInterpolator(new LinearInterpolator());
66 | animation.setDuration(1000);
67 | animation.setRepeatCount(Animation.INFINITE);
68 | imageLoadMore.startAnimation(animation);*/
69 | }
70 | public void stopAnimation() {
71 | if(animator!=null) {
72 | animator.removeAllUpdateListeners();
73 | animator.cancel();
74 | animator.end();
75 | animator = null;
76 | }
77 | imageLoadMore.clearAnimation();
78 | }
79 |
80 | public void loadMoreComplete(PullToRefreshRecyclerView refreshRecyclerView,boolean isAwaysShow){
81 | if(!isAwaysShow) {
82 | setVisibility(GONE);
83 | stopAnimation();
84 | }
85 | refreshRecyclerView.scrollBy(0,-getHeight());
86 | }
87 | public void loadMoreEnd(PullToRefreshRecyclerView refreshRecyclerView){
88 | setVisibility(GONE);
89 | stopAnimation();
90 | refreshRecyclerView.scrollBy(0,-getHeight());
91 | }
92 |
93 | public void loadMoreFail(final PullToRefreshRecyclerView refreshRecyclerView){
94 | textTip.setVisibility(VISIBLE);
95 | imageLoadMore.setVisibility(INVISIBLE);
96 | postDelayed(new Runnable() {
97 | @Override
98 | public void run() {
99 | setVisibility(GONE);
100 | stopAnimation();
101 | refreshRecyclerView.scrollBy(0,-getHeight());
102 | textTip.setVisibility(INVISIBLE);
103 | imageLoadMore.setVisibility(VISIBLE);
104 | }
105 | },800);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/pulltorefreshlibrary/src/main/java/com/androidkun/PullToRefreshRecyclerView.java:
--------------------------------------------------------------------------------
1 | package com.androidkun;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.widget.GridLayoutManager;
6 | import android.support.v7.widget.LinearLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.support.v7.widget.StaggeredGridLayoutManager;
9 | import android.util.AttributeSet;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 |
14 | import com.androidkun.callback.PullToRefreshListener;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | /**
20 | * Created by Kun on 2017/2/7.
21 | * GitHub: https://github.com/AndroidKun
22 | * CSDN: http://blog.csdn.net/a1533588867
23 | * Description:PullToRefreshRecyclerView
24 | */
25 |
26 | public class PullToRefreshRecyclerView extends RecyclerView {
27 | private static final int TYPE_REFRESH_HEADER = 10000;
28 | private RefreshHead refreshHeader;
29 | private static final int TYPE_LOAD_MORE_FOOTER = 10001;
30 | private LoadMoreView loadMoreView;
31 |
32 | private static final int TYPE_EMPTY_VIEW = 10002;
33 | private View emptyView;
34 |
35 | /**
36 | * 标识HeadView的ItemType的起始值
37 | */
38 | private static final int TYPE_HEADER_VIEWS_INIT = 10003;
39 | private static List headerTypes = new ArrayList<>();
40 | private List headViews = new ArrayList<>();
41 | /**
42 | * 标识FooterView的ItemType的起始值
43 | */
44 | private static final int TYPE_FOOTER_VIEW_INIT = 11000;
45 | private static List footerTypes = new ArrayList<>();
46 | private List footerViews = new ArrayList<>();
47 | /**
48 | * 摩擦力
49 | */
50 | private static final int DRAG_RATE = 3;
51 | private final AdapterDataObserver dataObserver = new DataObserver();
52 |
53 | private boolean pullRefreshEnabled = true;
54 | private boolean loadingMoreEnabled = true;
55 |
56 | private PullToRefreshRecyclerViewAdapter pullToRefreshRecyclerViewAdapter;
57 |
58 | private PullToRefreshListener pullToRefreshListener;
59 |
60 |
61 | public PullToRefreshRecyclerView(Context context) {
62 | this(context, null);
63 | }
64 |
65 | public PullToRefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {
66 | this(context, attrs, 0);
67 | }
68 |
69 | public PullToRefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
70 | super(context, attrs, defStyle);
71 | init();
72 | }
73 |
74 | private boolean isAlwaysShow = false;
75 |
76 | //设置加载更多界面在所有数据未加载完成前是否一直显示
77 | public void setLoadMoreViewAlwaysShow(boolean alwaysShow) {
78 | this.isAlwaysShow = alwaysShow;
79 | }
80 |
81 | private void init() {
82 | refreshHeader = new RefreshHead(getContext());
83 | loadMoreView = new LoadMoreView(getContext());
84 | // if(isAlwaysShow){
85 | // loadMoreView.setVisibility(VISIBLE);
86 | // }else{
87 | loadMoreView.setVisibility(GONE);
88 | // }
89 | // loadMoreView = View.inflate(getContext(), R.layout.layout_load_more_view, null);
90 | }
91 |
92 | public void showLoadMoreView() {
93 | if (isAlwaysShow) {
94 | loadMoreView.setVisibility(VISIBLE);
95 | loadMoreView.startAnimation();
96 | }
97 | }
98 |
99 | public void setPullRefreshEnabled(boolean pullRefreshEnabled) {
100 | this.pullRefreshEnabled = pullRefreshEnabled;
101 | }
102 |
103 | public void setLoadingMoreEnabled(boolean loadingMoreEnabled) {
104 | this.loadingMoreEnabled = loadingMoreEnabled;
105 | }
106 |
107 | public void setPullToRefreshListener(PullToRefreshListener pullToRefreshListener) {
108 | this.pullToRefreshListener = pullToRefreshListener;
109 | if (refreshHeader != null) {
110 | refreshHeader.setPullToRefreshListener(pullToRefreshListener);
111 | }
112 | }
113 |
114 | /**
115 | * 设置箭头资源图标
116 | *
117 | * @param resId
118 | */
119 | public void setRefreshArrowResource(int resId) {
120 | refreshHeader.setRefreshArrowResource(resId);
121 | }
122 |
123 | /**
124 | * 设置刷新资源图标
125 | *
126 | * @param resId
127 | */
128 | public void setRefreshingResource(int resId) {
129 | refreshHeader.setRefreshingResource(resId);
130 | }
131 |
132 | /**
133 | * 设置加载更多资源图标
134 | *
135 | * @param resId
136 | */
137 | public void setLoadMoreResource(int resId) {
138 | loadMoreView.setLoadMoreResource(resId);
139 | }
140 |
141 | /**
142 | * 设置是否显示上次刷新时间
143 | */
144 | public void displayLastRefreshTime(boolean display) {
145 | refreshHeader.displayLastRefreshTime(display);
146 | }
147 |
148 | /**
149 | * 判断是否是PullToRefreshRecyclerView保留的itemViewType
150 | */
151 | private boolean isReservedItemViewType(int itemViewType) {
152 | if (itemViewType == TYPE_REFRESH_HEADER || itemViewType == TYPE_LOAD_MORE_FOOTER || headerTypes.contains(itemViewType) || footerTypes.contains(itemViewType)) {
153 | return true;
154 | } else {
155 | return false;
156 | }
157 | }
158 |
159 | public void setEmptyView(View emptyView) {
160 | this.emptyView = emptyView;
161 | }
162 |
163 | /**
164 | * 添加HeadView
165 | */
166 | public void addHeaderView(View view) {
167 | headerTypes.add(TYPE_HEADER_VIEWS_INIT + headViews.size());
168 | headViews.add(view);
169 | dataObserver.onChanged();
170 | if (pullToRefreshRecyclerViewAdapter != null) {
171 | pullToRefreshRecyclerViewAdapter.adapter.notifyDataSetChanged();
172 | }
173 | }
174 |
175 | public void removeAllHeaderViews() {
176 | headViews.clear();
177 | headerTypes.clear();
178 | dataObserver.onChanged();
179 | if (pullToRefreshRecyclerViewAdapter != null
180 | && pullToRefreshRecyclerViewAdapter.adapter != null) {
181 | pullToRefreshRecyclerViewAdapter.adapter.notifyDataSetChanged();
182 | }
183 | }
184 |
185 | public void removeHeaderViewByIndex(int index) {
186 | if (index >= headViews.size()) {
187 | throw new IndexOutOfBoundsException("Invalid index " + index + ", headerViews size is " + headViews.size());
188 | }
189 | headViews.remove(index);
190 | headerTypes.remove(headerTypes.get(index));
191 | dataObserver.onChanged();
192 | if (pullToRefreshRecyclerViewAdapter != null
193 | && pullToRefreshRecyclerViewAdapter.adapter != null) {
194 | pullToRefreshRecyclerViewAdapter.adapter.notifyDataSetChanged();
195 | }
196 | }
197 |
198 | public List getHeaderViews() {
199 | return headViews;
200 | }
201 |
202 | public View getHeaderViewByIndex(int index) {
203 | if (index >= headViews.size()) return null;
204 | return headViews.get(index);
205 | }
206 |
207 | public void addFooterView(View view) {
208 | footerTypes.add(TYPE_FOOTER_VIEW_INIT + footerViews.size());
209 | footerViews.add(view);
210 | dataObserver.onChanged();
211 | if (pullToRefreshRecyclerViewAdapter != null) {
212 | pullToRefreshRecyclerViewAdapter.adapter.notifyDataSetChanged();
213 | }
214 | }
215 |
216 | public List getFooterViews() {
217 | return footerViews;
218 | }
219 |
220 | public View getFooterViewByIndex(int index) {
221 | if (index >= footerViews.size()) return null;
222 | return footerViews.get(index);
223 | }
224 |
225 | public void removeAllFooterViews() {
226 | if (footerViews.size() == 0) return;
227 | footerTypes.clear();
228 | footerViews.clear();
229 | dataObserver.onChanged();
230 | if (pullToRefreshRecyclerViewAdapter != null
231 | && pullToRefreshRecyclerViewAdapter.adapter != null) {
232 | pullToRefreshRecyclerViewAdapter.adapter.notifyDataSetChanged();
233 | }
234 | }
235 |
236 | public void removeFooterViewByIndex(int index) {
237 | if (index >= footerViews.size()) {
238 | throw new IndexOutOfBoundsException("Invalid index " + index + ", footerView size is " + footerViews.size());
239 | }
240 | footerViews.remove(index);
241 | footerTypes.remove(headerTypes.get(index));
242 | dataObserver.onChanged();
243 | if (pullToRefreshRecyclerViewAdapter != null
244 | && pullToRefreshRecyclerViewAdapter.adapter != null) {
245 | pullToRefreshRecyclerViewAdapter.adapter.notifyDataSetChanged();
246 | }
247 | }
248 |
249 | public void setRefreshComplete() {
250 | if (refreshHeader != null) {
251 | refreshHeader.setRefreshComplete();
252 | }
253 | }
254 |
255 | public void setRefreshFail() {
256 | if (refreshHeader != null) {
257 | refreshHeader.setRefreshFail();
258 | }
259 | }
260 |
261 | /**
262 | * 设置触发刷新的高度
263 | *
264 | * @param height
265 | */
266 | public void setRefreshLimitHeight(int height) {
267 | refreshHeader.setRefreshLimitHeight(height);
268 | }
269 |
270 | @Override
271 | public void setAdapter(Adapter adapter) {
272 | pullToRefreshRecyclerViewAdapter = new PullToRefreshRecyclerViewAdapter(adapter);
273 | super.setAdapter(pullToRefreshRecyclerViewAdapter);
274 | adapter.registerAdapterDataObserver(dataObserver);
275 | dataObserver.onChanged();
276 | }
277 |
278 | @Override
279 | public Adapter getAdapter() {
280 | if (pullToRefreshRecyclerViewAdapter != null) {
281 | return pullToRefreshRecyclerViewAdapter.getAdapter();
282 | }
283 | return null;
284 | }
285 |
286 | @Override
287 | public void setLayoutManager(LayoutManager layout) {
288 | super.setLayoutManager(layout);
289 | if (pullToRefreshRecyclerViewAdapter != null) {
290 | if (layout instanceof GridLayoutManager) {
291 | final GridLayoutManager gridManager = ((GridLayoutManager) layout);
292 | gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
293 | @Override
294 | public int getSpanSize(int position) {
295 | return (pullToRefreshRecyclerViewAdapter.isHeader(position)
296 | || pullToRefreshRecyclerViewAdapter.isLoadMoreFooter(position)
297 | || pullToRefreshRecyclerViewAdapter.isRefreshHeader(position))
298 | || pullToRefreshRecyclerViewAdapter.isFooter(position)
299 | || pullToRefreshRecyclerViewAdapter.isEmptyView(position)
300 | ? gridManager.getSpanCount() : 1;
301 | }
302 | });
303 |
304 | }
305 | }
306 | }
307 |
308 | private float lastY = -1;
309 |
310 | @Override
311 | public boolean onTouchEvent(MotionEvent e) {
312 | switch (e.getAction()) {
313 | case MotionEvent.ACTION_DOWN:
314 | lastY = e.getRawY();
315 | break;
316 | case MotionEvent.ACTION_MOVE:
317 | if (lastY < 0) lastY = e.getRawY();
318 | float moveY = e.getRawY() - lastY;
319 | lastY = e.getRawY();
320 | if (refreshHeader.getVisibleHeight() == 0 && moveY < 0) {
321 | return super.onTouchEvent(e);
322 | }
323 | if (isOnTop() && pullRefreshEnabled && refreshHeader.getRefreshState() != RefreshHead.STATE_REFRESHING) {
324 | refreshHeader.onMove((int) (moveY / DRAG_RATE));
325 |
326 | LinearLayoutManager mLayoutManager =
327 | (LinearLayoutManager) getLayoutManager();
328 | mLayoutManager.scrollToPositionWithOffset(0, 0);
329 | return false;
330 | }
331 | break;
332 | case MotionEvent.ACTION_UP:
333 | refreshHeader.checkRefresh();
334 | break;
335 | }
336 | return super.onTouchEvent(e);
337 | }
338 |
339 | /**
340 | * 判断列表是否滑到顶部
341 | *
342 | * @return
343 | */
344 | private boolean isOnTop() {
345 | if (refreshHeader.getParent() != null) {
346 | return true;
347 | } else {
348 | return false;
349 | }
350 |
351 | }
352 |
353 | int lastVisibleItemPosition;
354 |
355 | @Override
356 | public void onScrollStateChanged(int state) {
357 | super.onScrollStateChanged(state);
358 | if (state == RecyclerView.SCROLL_STATE_IDLE && pullToRefreshListener != null
359 | && loadingMoreEnabled && (isAlwaysShow ? isAlwaysShow : loadMoreView.getVisibility() != View.VISIBLE)) {
360 | LayoutManager layoutManager = getLayoutManager();
361 | if (layoutManager instanceof GridLayoutManager) {
362 | lastVisibleItemPosition = ((GridLayoutManager) layoutManager)
363 | .findLastVisibleItemPosition();
364 | } else if (layoutManager instanceof StaggeredGridLayoutManager) {
365 | int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
366 | ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
367 | lastVisibleItemPosition = findMax(into);
368 | } else {
369 | lastVisibleItemPosition = ((LinearLayoutManager) layoutManager)
370 | .findLastVisibleItemPosition();
371 | }
372 | if (layoutManager.getChildCount() > 0 &&
373 | lastVisibleItemPosition >= pullToRefreshRecyclerViewAdapter.getItemCount() - 1
374 | && pullToRefreshListener != null
375 | && refreshHeader.getRefreshState() != RefreshHead.STATE_REFRESHING) {
376 | loadMoreView.setVisibility(VISIBLE);
377 | loadMoreView.startAnimation();
378 | pullToRefreshListener.onLoadMore();
379 | }
380 | }
381 | }
382 |
383 |
384 | public void setLoadMoreComplete() {
385 | loadMoreView.loadMoreComplete(this, isAlwaysShow);
386 | }
387 |
388 |
389 | public void setLoadMoreFail() {
390 | loadMoreView.loadMoreFail(this);
391 | }
392 |
393 |
394 | private int findMax(int[] lastPositions) {
395 | int max = lastPositions[0];
396 | for (int value : lastPositions) {
397 | if (value > max) {
398 | max = value;
399 | }
400 | }
401 | return max;
402 | }
403 |
404 | public void onRefresh() {
405 | refreshHeader.setRefreshing();
406 | }
407 |
408 | public void onLoadMoare() {
409 | loadMoreView.setVisibility(VISIBLE);
410 | loadMoreView.startAnimation();
411 | pullToRefreshListener.onLoadMore();
412 | }
413 |
414 | public void loadMoreEnd() {
415 | if (loadMoreView != null) {
416 | loadMoreView.loadMoreEnd(this);
417 | }
418 | }
419 |
420 | private class PullToRefreshRecyclerViewAdapter extends Adapter {
421 |
422 | private Adapter adapter;
423 |
424 | public PullToRefreshRecyclerViewAdapter(Adapter adapter) {
425 | this.adapter = adapter;
426 | }
427 |
428 | public Adapter getAdapter() {
429 | return adapter;
430 | }
431 |
432 | public boolean isHeader(int position) {
433 | return position >= 1 && position < headViews.size() + 1;
434 | }
435 |
436 | public boolean isFooter(int position) {
437 | int count = 0;
438 | if (shouldDisplayEmptyView()) count = 1;
439 | return position >= 1 && !isLoadMoreFooter(position) && position >= 1 + headViews.size() + adapter.getItemCount() + count;
440 | }
441 |
442 | public boolean isLoadMoreFooter(int position) {
443 | if (loadingMoreEnabled) {
444 | return position == getItemCount() - 1;
445 | } else {
446 | return false;
447 | }
448 | }
449 |
450 | public boolean isRefreshHeader(int position) {
451 | return position == 0;
452 | }
453 |
454 | public int getHeadersCount() {
455 | return headViews.size();
456 | }
457 |
458 | public int getFootersCount() {
459 | return footerViews.size();
460 | }
461 |
462 | /**
463 | * 根据ItemType获取对应的HeadView
464 | */
465 | private View getHeaderViewByType(int itemType) {
466 | if (!isHeaderType(itemType)) {
467 | return null;
468 | }
469 | return headViews.get(itemType - TYPE_HEADER_VIEWS_INIT);
470 | }
471 |
472 | /**
473 | * 判断ItemType是否为HeaderType
474 | */
475 | private boolean isHeaderType(int itemViewType) {
476 | return headViews.size() > 0 && headerTypes.contains(itemViewType);
477 | }
478 |
479 |
480 | /**
481 | * 根据ItemType获取对应的FooterView
482 | */
483 | private View getFooterViewByType(int itemType) {
484 | if (!isFooterType(itemType)) {
485 | return null;
486 | }
487 | return footerViews.get(itemType - TYPE_FOOTER_VIEW_INIT);
488 | }
489 |
490 | /**
491 | * 判断ItemType是否为FooterType
492 | */
493 | private boolean isFooterType(int itemViewType) {
494 | return footerTypes.size() > 0 && footerTypes.contains(itemViewType);
495 | }
496 |
497 | private boolean isEmptyView(int position) {
498 | return shouldDisplayEmptyView() && position == 1 + headViews.size();
499 | }
500 |
501 | /**
502 | * 是否需要显示EmptyView
503 | *
504 | * @return
505 | */
506 | private boolean shouldDisplayEmptyView() {
507 | return adapter.getItemCount() == 0 && emptyView != null;
508 | }
509 |
510 | @Override
511 | public int getItemCount() {
512 | int count;
513 | if (loadingMoreEnabled) {
514 | if (adapter != null) {
515 | count = getHeadersCount() + getFootersCount() + adapter.getItemCount() + 2;
516 | } else {
517 | count = getHeadersCount() + getFootersCount() + 2;
518 | }
519 | } else {
520 | if (adapter != null) {
521 | count = getHeadersCount() + getFootersCount() + adapter.getItemCount() + 1;
522 | } else {
523 | count = getHeadersCount() + getFootersCount() + 1;
524 | }
525 | }
526 | //如果Adapter中没有数据 则多加1用于显示EmptyView
527 | if (shouldDisplayEmptyView()) {
528 | count += 1;
529 | }
530 | return count;
531 | }
532 |
533 | @Override
534 | public int getItemViewType(int position) {
535 | // int adjPosition = position - (getHeadersCount() + getFootersCount() + 1);
536 | int adjPosition = position-1;
537 | if (shouldDisplayEmptyView()) {
538 | adjPosition--;
539 | }
540 | if (isRefreshHeader(position)) {
541 | return TYPE_REFRESH_HEADER;
542 | }
543 | if (isHeader(position)) {
544 | position = position - 1;
545 | return headerTypes.get(position);
546 | }
547 | if (shouldDisplayEmptyView() && position == getHeadersCount() + 1) {
548 | return TYPE_EMPTY_VIEW;
549 | }
550 | if (isFooter(position)) {
551 | position = position - 1 - headViews.size() - adapter.getItemCount();
552 | if (shouldDisplayEmptyView()) position--;
553 | return footerTypes.get(position);
554 | }
555 | if (isLoadMoreFooter(position)) {
556 | return TYPE_LOAD_MORE_FOOTER;
557 | }
558 |
559 | return adapter.getItemViewType(adjPosition);
560 | /* int adapterCount;
561 | if (adapter != null) {
562 | adapterCount = adapter.getItemCount();
563 | // adapterCount = getItemCount();
564 | Log.w("AAA", "adjPosition:" + adjPosition + ";adapterCount:" + adapterCount);
565 | if (adjPosition < adapterCount) {
566 | int type = adapter.getItemViewType(adjPosition);
567 | Log.w("AAA", "type:" + type);
568 | if (isReservedItemViewType(type)) {
569 | throw new IllegalStateException("PullToRefreshRecyclerView require itemViewType in adapter should be less than 10000 ");
570 | }
571 | return type;
572 | }
573 | }
574 | return 0;*/
575 | }
576 |
577 |
578 | @Override
579 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
580 | if (viewType == TYPE_REFRESH_HEADER) {
581 | return new SimpleViewHolder(refreshHeader);
582 | } else if (isHeaderType(viewType)) {
583 | return new SimpleViewHolder(getHeaderViewByType(viewType));
584 | } else if (viewType == TYPE_EMPTY_VIEW) {
585 | return new SimpleViewHolder(emptyView);
586 | } else if (isFooterType(viewType)) {
587 | return new SimpleViewHolder(getFooterViewByType(viewType));
588 | } else if (viewType == TYPE_LOAD_MORE_FOOTER) {
589 | return new SimpleViewHolder(loadMoreView);
590 | }
591 | return adapter.onCreateViewHolder(parent, viewType);
592 | }
593 |
594 | @Override
595 | public void onBindViewHolder(ViewHolder holder, int position) {
596 | if (isHeader(position) || isRefreshHeader(position) || isFooter(position)
597 | || isEmptyView(position) || isLoadMoreFooter(position)) {
598 | return;
599 | }
600 | int adjPosition = position - (getHeadersCount() + 1);
601 | int adapterCount;
602 | if (adapter != null) {
603 | adapterCount = adapter.getItemCount();
604 | if (adjPosition < adapterCount) {
605 | adapter.onBindViewHolder(holder, adjPosition);
606 | }
607 | }
608 | }
609 |
610 | @Override
611 | public void onBindViewHolder(ViewHolder holder, int position, List