├── .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 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 1.8 41 | 42 | 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 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PullToRefreshRecyclerView 2 | ![image](https://github.com/AndroidKun/PullToRefreshRecyclerView/blob/master/gif/GIF.gif) 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 payloads) { 612 | if (isHeader(position) || isRefreshHeader(position) || isFooter(position) 613 | || isEmptyView(position) || isLoadMoreFooter(position)) { 614 | return; 615 | } 616 | int adjPosition = position - (getHeadersCount() + 1); 617 | int adapterCount; 618 | if (adapter != null) { 619 | adapterCount = adapter.getItemCount(); 620 | if (adjPosition < adapterCount) { 621 | if (payloads.isEmpty()) { 622 | adapter.onBindViewHolder(holder, adjPosition); 623 | } else { 624 | adapter.onBindViewHolder(holder, adjPosition, payloads); 625 | } 626 | } 627 | } 628 | } 629 | 630 | @Override 631 | public long getItemId(int position) { 632 | if (adapter != null && position >= getHeadersCount() + 1) { 633 | int adjPosition = position - (getHeadersCount() + 1); 634 | if (adjPosition < adapter.getItemCount()) { 635 | return adapter.getItemId(adjPosition); 636 | } 637 | } 638 | return -1; 639 | } 640 | 641 | @Override 642 | public void onAttachedToRecyclerView(RecyclerView recyclerView) { 643 | super.onAttachedToRecyclerView(recyclerView); 644 | LayoutManager manager = recyclerView.getLayoutManager(); 645 | if (manager instanceof GridLayoutManager) { 646 | final GridLayoutManager gridManager = ((GridLayoutManager) manager); 647 | gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 648 | @Override 649 | public int getSpanSize(int position) { 650 | return (isHeader(position) || isLoadMoreFooter(position) 651 | || isRefreshHeader(position) || isFooter(position) 652 | || isEmptyView(position)) 653 | ? gridManager.getSpanCount() : 1; 654 | } 655 | }); 656 | } 657 | adapter.onAttachedToRecyclerView(recyclerView); 658 | } 659 | 660 | @Override 661 | public void onDetachedFromRecyclerView(RecyclerView recyclerView) { 662 | adapter.onDetachedFromRecyclerView(recyclerView); 663 | } 664 | 665 | @Override 666 | public void onViewAttachedToWindow(ViewHolder holder) { 667 | super.onViewAttachedToWindow(holder); 668 | ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 669 | if (lp != null 670 | && lp instanceof StaggeredGridLayoutManager.LayoutParams 671 | && (isHeader(holder.getLayoutPosition()) || isRefreshHeader(holder.getLayoutPosition()) || isLoadMoreFooter(holder.getLayoutPosition()))) { 672 | StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; 673 | p.setFullSpan(true); 674 | } 675 | adapter.onViewAttachedToWindow(holder); 676 | } 677 | 678 | @Override 679 | public void onViewDetachedFromWindow(ViewHolder holder) { 680 | adapter.onViewDetachedFromWindow(holder); 681 | } 682 | 683 | @Override 684 | public void onViewRecycled(ViewHolder holder) { 685 | adapter.onViewRecycled(holder); 686 | } 687 | 688 | @Override 689 | public boolean onFailedToRecycleView(ViewHolder holder) { 690 | return adapter.onFailedToRecycleView(holder); 691 | } 692 | 693 | @Override 694 | public void unregisterAdapterDataObserver(AdapterDataObserver observer) { 695 | adapter.unregisterAdapterDataObserver(observer); 696 | } 697 | 698 | @Override 699 | public void registerAdapterDataObserver(AdapterDataObserver observer) { 700 | adapter.registerAdapterDataObserver(observer); 701 | } 702 | 703 | private class SimpleViewHolder extends ViewHolder { 704 | public SimpleViewHolder(View itemView) { 705 | super(itemView); 706 | } 707 | } 708 | 709 | } 710 | 711 | private class DataObserver extends AdapterDataObserver { 712 | 713 | @Override 714 | public void onChanged() { 715 | if (pullToRefreshRecyclerViewAdapter != null) { 716 | pullToRefreshRecyclerViewAdapter.notifyDataSetChanged(); 717 | } 718 | } 719 | 720 | @Override 721 | public void onItemRangeInserted(int positionStart, int itemCount) { 722 | pullToRefreshRecyclerViewAdapter.notifyItemRangeInserted(positionStart, itemCount); 723 | } 724 | 725 | @Override 726 | public void onItemRangeChanged(int positionStart, int itemCount) { 727 | pullToRefreshRecyclerViewAdapter.notifyItemRangeChanged(positionStart, itemCount); 728 | } 729 | 730 | @Override 731 | public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 732 | pullToRefreshRecyclerViewAdapter.notifyItemRangeChanged(positionStart, itemCount, payload); 733 | } 734 | 735 | @Override 736 | public void onItemRangeRemoved(int positionStart, int itemCount) { 737 | pullToRefreshRecyclerViewAdapter.notifyItemRangeRemoved(positionStart, itemCount); 738 | } 739 | 740 | @Override 741 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 742 | pullToRefreshRecyclerViewAdapter.notifyItemMoved(fromPosition, toPosition); 743 | } 744 | } 745 | 746 | } 747 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/java/com/androidkun/RefreshHead.java: -------------------------------------------------------------------------------- 1 | package com.androidkun; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | import android.util.Log; 7 | import android.view.Gravity; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.view.WindowManager; 12 | import android.view.animation.LinearInterpolator; 13 | import android.widget.ImageView; 14 | import android.widget.LinearLayout; 15 | import android.widget.TextView; 16 | 17 | import com.androidkun.callback.PullToRefreshListener; 18 | 19 | import java.util.Date; 20 | /** 21 | * Created by Kun on 2017/2/7. 22 | * GitHub: https://github.com/AndroidKun 23 | * CSDN: http://blog.csdn.net/a1533588867 24 | * Description:下拉刷新 25 | */ 26 | 27 | public class RefreshHead extends LinearLayout { 28 | 29 | public static final int STATE_NORMAL = 0; 30 | public static final int STATE_RELEASE_TO_REFRESH = 1; 31 | public static final int STATE_REFRESHING = 2; 32 | public static final int STATE_DONE = 3; 33 | public static final int STATE_FAIL = 4; 34 | 35 | private int refreshState = 0; 36 | /** 37 | * 触发刷新操作最小的高度 38 | */ 39 | private int refreshLimitHeight; 40 | private View refreshContentView; 41 | private TextView textTip; 42 | private TextView textLastRefreshTime; 43 | private ImageView imageArrow; 44 | private ImageView imageRefreshing; 45 | 46 | private PullToRefreshListener pullToRefreshListener; 47 | /** 48 | * 上次刷新的时间 49 | */ 50 | private Date lastRefreshDate; 51 | private boolean showLastRefreshTime; 52 | private ValueAnimator animator; 53 | 54 | public RefreshHead(Context context) { 55 | this(context, null); 56 | } 57 | 58 | public RefreshHead(Context context, AttributeSet attrs) { 59 | this(context, attrs, 0); 60 | } 61 | 62 | public RefreshHead(Context context, AttributeSet attrs, int defStyleAttr) { 63 | super(context, attrs, defStyleAttr); 64 | refreshLimitHeight = getScreenHeight() / 6; 65 | 66 | refreshContentView = LayoutInflater.from(getContext()).inflate( 67 | R.layout.layout_refresh_head_view, null); 68 | textTip = (TextView) refreshContentView.findViewById(R.id.textTip); 69 | textLastRefreshTime = (TextView) refreshContentView.findViewById(R.id.textLastRefreshTime); 70 | imageArrow = (ImageView) refreshContentView.findViewById(R.id.imageArrow); 71 | imageRefreshing = (ImageView) refreshContentView.findViewById(R.id.imageRefreshing); 72 | //一开始设置高度为0 不显示刷新布局 73 | addView(refreshContentView, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0)); 74 | setGravity(Gravity.BOTTOM); 75 | 76 | 77 | } 78 | 79 | /** 80 | * 设置触发刷新的高度 81 | * 82 | * @param height 83 | */ 84 | public void setRefreshLimitHeight(int height){ 85 | refreshLimitHeight = height; 86 | } 87 | 88 | public void setRefreshArrowResource(int resId){ 89 | imageArrow.setImageResource(resId); 90 | } 91 | 92 | public void setRefreshingResource(int resId){ 93 | imageRefreshing.setImageResource(resId); 94 | } 95 | public void displayLastRefreshTime(boolean display){ 96 | showLastRefreshTime = display; 97 | } 98 | 99 | public void onMove(int move) { 100 | int newVisibleHeight = getVisibleHeight() + move; 101 | if (newVisibleHeight >= refreshLimitHeight && refreshState != STATE_RELEASE_TO_REFRESH) { 102 | if(imageArrow.getVisibility()!=VISIBLE ) imageArrow.setVisibility(VISIBLE); 103 | refreshState = STATE_RELEASE_TO_REFRESH; 104 | textTip.setText(R.string.release_refresh); 105 | rotationAnimator(180f); 106 | } 107 | if (newVisibleHeight < refreshLimitHeight && refreshState != STATE_NORMAL) { 108 | if(imageArrow.getVisibility()!=VISIBLE ) imageArrow.setVisibility(VISIBLE); 109 | refreshState = STATE_NORMAL; 110 | textTip.setText(R.string.pull_to_refresh); 111 | if(lastRefreshDate==null){ 112 | textLastRefreshTime.setVisibility(GONE); 113 | }else{ 114 | if(showLastRefreshTime) { 115 | textLastRefreshTime.setVisibility(VISIBLE); 116 | textLastRefreshTime.setText(friendlyTime(lastRefreshDate)); 117 | } 118 | } 119 | rotationAnimator(0); 120 | } 121 | setVisibleHeight(getVisibleHeight() + move); 122 | 123 | } 124 | 125 | /** 126 | * 触摸事件结束后检查是否需要刷新 127 | */ 128 | public void checkRefresh() { 129 | if (getVisibleHeight() <= 0) return; 130 | if (refreshState == STATE_NORMAL) { 131 | smoothScrollTo(0); 132 | refreshState = STATE_DONE; 133 | } else if (refreshState == STATE_RELEASE_TO_REFRESH) { 134 | setState(STATE_REFRESHING); 135 | } 136 | } 137 | 138 | public void setRefreshing() { 139 | refreshState = STATE_REFRESHING; 140 | imageArrow.setVisibility(GONE); 141 | textLastRefreshTime.setVisibility(GONE); 142 | imageRefreshing.setVisibility(VISIBLE); 143 | startRefreshingAnimation(); 144 | textTip.setText(R.string.refreshing); 145 | smoothScrollTo(getScreenHeight() / 9); 146 | if (pullToRefreshListener != null) { 147 | pullToRefreshListener.onRefresh(); 148 | } 149 | } 150 | 151 | public void startRefreshingAnimation() { 152 | animator = ValueAnimator.ofFloat(imageRefreshing.getRotation(), imageRefreshing.getRotation()+359); 153 | animator.setDuration(1000).start(); 154 | animator.setInterpolator(new LinearInterpolator()); 155 | animator.setRepeatCount(ValueAnimator.INFINITE); 156 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 157 | @Override 158 | public void onAnimationUpdate(ValueAnimator animation) { 159 | imageRefreshing.setRotation((Float) animation.getAnimatedValue()); 160 | } 161 | }); 162 | animator.start(); 163 | /* 164 | RotateAnimation animation = new RotateAnimation(0f, 359f, Animation.RELATIVE_TO_SELF, 165 | 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 166 | animation.setInterpolator(new LinearInterpolator()); 167 | animation.setDuration(1000); 168 | animation.setRepeatCount(Animation.INFINITE); 169 | imageRefreshing.startAnimation(animation);*/ 170 | } 171 | 172 | public void setState(int state) { 173 | if (refreshState == state) return; 174 | switch (state) { 175 | case STATE_REFRESHING://切换到刷新状态 176 | refreshState = state; 177 | imageArrow.setVisibility(GONE); 178 | textLastRefreshTime.setVisibility(GONE); 179 | imageRefreshing.setVisibility(VISIBLE); 180 | startRefreshingAnimation(); 181 | textTip.setText(R.string.refreshing); 182 | smoothScrollTo(getScreenHeight() / 9); 183 | 184 | if (pullToRefreshListener != null) { 185 | pullToRefreshListener.onRefresh(); 186 | } 187 | break; 188 | case STATE_DONE://切换到刷新完成或者加载成功的状态 189 | if (refreshState == STATE_REFRESHING) { 190 | refreshState = state; 191 | animator.end(); 192 | imageRefreshing.setVisibility(GONE); 193 | textTip.setText(R.string.refresh_success); 194 | lastRefreshDate = new Date(); 195 | // imageRefreshing.clearAnimation(); 196 | smoothScrollTo(0); 197 | } 198 | break; 199 | case STATE_FAIL://切换到刷新失败或者加载失败的状态 200 | if (refreshState == STATE_REFRESHING) { 201 | refreshState = state; 202 | imageRefreshing.setVisibility(GONE); 203 | imageArrow.setVisibility(VISIBLE); 204 | textTip.setText(R.string.refresh_fail); 205 | animator.end(); 206 | // imageRefreshing.clearAnimation(); 207 | smoothScrollTo(0); 208 | } 209 | break; 210 | } 211 | } 212 | 213 | public int getRefreshState() { 214 | return refreshState; 215 | } 216 | 217 | public void setRefreshComplete() { 218 | setState(STATE_DONE); 219 | } 220 | 221 | public void setRefreshFail() { 222 | setState(STATE_FAIL); 223 | } 224 | 225 | public int getVisibleHeight() { 226 | LayoutParams lp = (LayoutParams) refreshContentView.getLayoutParams(); 227 | return lp.height; 228 | } 229 | 230 | public void setVisibleHeight(int height) { 231 | if (height < 0) height = 0; 232 | LayoutParams lp = (LayoutParams) refreshContentView.getLayoutParams(); 233 | lp.height = height; 234 | refreshContentView.setLayoutParams(lp); 235 | } 236 | 237 | public void setPullToRefreshListener(PullToRefreshListener pullToRefreshListener) { 238 | this.pullToRefreshListener = pullToRefreshListener; 239 | } 240 | 241 | private void rotationAnimator(float rotation) { 242 | ValueAnimator animator = ValueAnimator.ofFloat(imageArrow.getRotation(), rotation); 243 | animator.setDuration(200).start(); 244 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 245 | @Override 246 | public void onAnimationUpdate(ValueAnimator animation) { 247 | imageArrow.setRotation((Float) animation.getAnimatedValue()); 248 | } 249 | }); 250 | animator.start(); 251 | } 252 | 253 | private void smoothScrollTo(int destHeight) { 254 | ValueAnimator animator = ValueAnimator.ofInt(getVisibleHeight(), destHeight); 255 | animator.setDuration(300).start(); 256 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 257 | @Override 258 | public void onAnimationUpdate(ValueAnimator animation) { 259 | setVisibleHeight((int) animation.getAnimatedValue()); 260 | } 261 | }); 262 | animator.start(); 263 | } 264 | 265 | 266 | /** 267 | * 获取屏幕高度 268 | * 269 | * @return 270 | */ 271 | private int getScreenHeight() { 272 | WindowManager wm = (WindowManager) getContext() 273 | .getSystemService(Context.WINDOW_SERVICE); 274 | return wm.getDefaultDisplay().getHeight(); 275 | } 276 | 277 | 278 | public static String friendlyTime(Date time) { 279 | //获取time距离当前的秒数 280 | int ct = (int)((System.currentTimeMillis() - time.getTime())/1000); 281 | 282 | if(ct == 0) { 283 | return "刚刚"; 284 | } 285 | 286 | if(ct > 0 && ct < 60) { 287 | return ct + "秒前"; 288 | } 289 | 290 | if(ct >= 60 && ct < 3600) { 291 | return Math.max(ct / 60,1) + "分钟前"; 292 | } 293 | if(ct >= 3600 && ct < 86400) 294 | return ct / 3600 + "小时前"; 295 | if(ct >= 86400 && ct < 2592000){ //86400 * 30 296 | int day = ct / 86400 ; 297 | return day + "天前"; 298 | } 299 | if(ct >= 2592000 && ct < 31104000) { //86400 * 30 300 | return ct / 2592000 + "月前"; 301 | } 302 | return ct / 31104000 + "年前"; 303 | } 304 | 305 | } 306 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/java/com/androidkun/adapter/BaseAdapter.java: -------------------------------------------------------------------------------- 1 | package com.androidkun.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.ViewGroup; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by Kun on 2016/12/14. 13 | * GitHub: https://github.com/AndroidKun 14 | * CSDN: http://blog.csdn.net/a1533588867 15 | * Description: 通用Adapter基类 16 | */ 17 | 18 | public abstract class BaseAdapter extends RecyclerView.Adapter { 19 | 20 | protected Context context; 21 | protected int layoutId; 22 | protected List datas; 23 | protected LayoutInflater inflater; 24 | 25 | protected final static int GET = 1; 26 | protected final static int POST = 2; 27 | /** 28 | * 设置是否显示EmptyView 29 | */ 30 | protected boolean showEmptyView = false; 31 | /** 32 | * 标识是否显示EmptyView 33 | */ 34 | protected boolean isShowEmptyView = false; 35 | /** 36 | * 全部加载完毕是否显示底部View 37 | */ 38 | protected boolean isShowEndView = false; 39 | /** 40 | * 是否添加了显示底部View的数据 41 | */ 42 | protected boolean isAddShowEndViewData = false; 43 | 44 | public BaseAdapter(Context context, int layoutId, List datas) { 45 | this.context = context; 46 | inflater = LayoutInflater.from(context); 47 | this.layoutId = layoutId; 48 | this.datas = datas; 49 | if(this.datas == null){ 50 | this.datas = new ArrayList<>(); 51 | } 52 | } 53 | 54 | @Override 55 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 56 | return ViewHolder.get(context,parent,layoutId); 57 | } 58 | 59 | @Override 60 | public void onBindViewHolder(ViewHolder holder, int position) { 61 | convert(holder,datas.get(position)); 62 | } 63 | 64 | public abstract void convert(ViewHolder holder, T t); 65 | 66 | @Override 67 | public int getItemCount() { 68 | return datas.size(); 69 | } 70 | 71 | public List getDatas(){ 72 | return datas; 73 | } 74 | 75 | public void setDatas(List datas){ 76 | this.datas = datas; 77 | if(this.datas == null){ 78 | this.datas = new ArrayList<>(); 79 | isShowEmptyView = true; 80 | }else{ 81 | isShowEmptyView = false; 82 | } 83 | notifyDataSetChanged(); 84 | } 85 | 86 | public void insertDataAtTop(T newData){ 87 | if(datas!=null){ 88 | datas.add(0,newData); 89 | }else{ 90 | datas = new ArrayList<>(); 91 | datas.add(newData); 92 | } 93 | notifyDataSetChanged(); 94 | } 95 | 96 | public void addDatas(List newDatas){ 97 | if(this.datas == null){ 98 | this.datas = new ArrayList<>(); 99 | } 100 | int last = datas.size(); 101 | this.datas.addAll(newDatas); 102 | notifyItemInserted(last + 1); 103 | } 104 | public void addData(T newData){ 105 | if(this.datas == null){ 106 | this.datas = new ArrayList<>(); 107 | } 108 | this.datas.add(newData); 109 | notifyDataSetChanged(); 110 | } 111 | public void addData(T newData,int index){ 112 | if(this.datas == null){ 113 | this.datas = new ArrayList<>(); 114 | } 115 | this.datas.add(index,newData); 116 | notifyDataSetChanged(); 117 | } 118 | 119 | public void setShowEmptyView(boolean showEmptyView) { 120 | this.showEmptyView = showEmptyView; 121 | } 122 | 123 | public void setIsShowEmptyView(boolean show) { 124 | if(!showEmptyView) return; 125 | isShowEmptyView = show; 126 | if(isShowEmptyView){ 127 | this.datas.clear(); 128 | insertDataAtTop((T) new Object()); 129 | } 130 | 131 | } 132 | 133 | public boolean isShowEndView() { 134 | return isShowEndView; 135 | } 136 | 137 | public void setShowEndView(boolean showEndView) { 138 | isShowEndView = showEndView; 139 | } 140 | 141 | public void addEndViewData(){ 142 | if(isAddShowEndViewData || !isShowEndView) return; 143 | isAddShowEndViewData = true; 144 | if(datas==null){ 145 | datas = new ArrayList<>(); 146 | } 147 | datas.add((T) new Object()); 148 | } 149 | 150 | public void removeEndViewData(){ 151 | if(!isAddShowEndViewData || !isShowEndView) return; 152 | isAddShowEndViewData = false; 153 | if(datas!=null && datas.size()>0){ 154 | datas.remove(datas.size()-1); 155 | } 156 | } 157 | 158 | 159 | public void removeItem(int position){ 160 | if(datas != null && datas.size() > position){ 161 | datas.remove(position); 162 | notifyItemRemoved(position); 163 | } 164 | } 165 | 166 | public void clearDatas(){ 167 | this.datas.clear(); 168 | notifyDataSetChanged(); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/java/com/androidkun/adapter/ViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.androidkun.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.text.Spanned; 6 | import android.util.SparseArray; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | /** 14 | * Created by Kun on 2016/12/14. 15 | * GitHub: https://github.com/AndroidKun 16 | * CSDN: http://blog.csdn.net/a1533588867 17 | * Description:通用ViewHolder 18 | */ 19 | 20 | public class ViewHolder extends RecyclerView.ViewHolder { 21 | 22 | private SparseArray views; 23 | private View convertView; 24 | private Context context; 25 | 26 | public ViewHolder(Context context, View itemView) { 27 | super(itemView); 28 | this.context = context; 29 | convertView = itemView; 30 | views = new SparseArray<>(); 31 | } 32 | 33 | public static ViewHolder get(Context context, ViewGroup viewGroup, int layoutId) { 34 | View itemView = LayoutInflater.from(context).inflate(layoutId, viewGroup, false); 35 | return new ViewHolder(context, itemView); 36 | } 37 | 38 | public T getView(int viewId) { 39 | View view = views.get(viewId); 40 | if (view == null) { 41 | view = convertView.findViewById(viewId); 42 | views.put(viewId, view); 43 | } 44 | return (T) view; 45 | } 46 | 47 | public ViewHolder setText(int viewId, String text) { 48 | TextView tv = getView(viewId); 49 | tv.setText(text); 50 | return this; 51 | } 52 | public ViewHolder setText(int viewId, Spanned text) { 53 | TextView tv = getView(viewId); 54 | tv.setText(text); 55 | return this; 56 | } 57 | 58 | public String getText(int viewId) { 59 | TextView tv = getView(viewId); 60 | return tv.getText().toString(); 61 | } 62 | 63 | public ViewHolder setTextColor(int viewId, int color) { 64 | TextView tv = getView(viewId); 65 | tv.setTextColor(color); 66 | return this; 67 | } 68 | 69 | public ViewHolder setImageResource(int viewId, int resId) { 70 | ImageView view = getView(viewId); 71 | view.setImageResource(resId); 72 | return this; 73 | } 74 | 75 | 76 | public ViewHolder setViewVisiable(int viewId, int visibility) { 77 | getView(viewId).setVisibility(visibility); 78 | return this; 79 | } 80 | 81 | public ViewHolder setViewBackgroundResource(int viewId, int resId) { 82 | getView(viewId).setBackgroundResource(resId); 83 | return this; 84 | } 85 | 86 | public ViewHolder setOnclickListener(int viewId, View.OnClickListener listener) { 87 | View view = getView(viewId); 88 | view.setOnClickListener(listener); 89 | return this; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/java/com/androidkun/callback/OnItemClickListener.java: -------------------------------------------------------------------------------- 1 | package com.androidkun.callback; 2 | 3 | /** 4 | * Created by Kennor on 2018/4/16. 5 | * GitHub: https://github.com/AndroidKun 6 | * CSDN: http://blog.csdn.net/a1533588867 7 | * Description: 8 | */ 9 | 10 | public interface OnItemClickListener { 11 | void onItemClick(int position, Object data); 12 | } 13 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/java/com/androidkun/callback/PullToRefreshListener.java: -------------------------------------------------------------------------------- 1 | package com.androidkun.callback; 2 | 3 | /** 4 | * Created by Kun on 2017/2/7. 5 | * GitHub: https://github.com/AndroidKun 6 | * CSDN: http://blog.csdn.net/a1533588867 7 | * Description:下拉刷新回调接口 8 | */ 9 | 10 | public interface PullToRefreshListener { 11 | void onRefresh(); 12 | void onLoadMore(); 13 | } 14 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/res/drawable/ic_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/pulltorefreshlibrary/src/main/res/drawable/ic_arrow.png -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/res/drawable/ic_load_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/pulltorefreshlibrary/src/main/res/drawable/ic_load_more.png -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/res/drawable/ic_refresh_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/pulltorefreshlibrary/src/main/res/drawable/ic_refresh_arrow.png -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/res/drawable/ic_refreshing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndroidKun/PullToRefreshRecyclerView/f5b6e7552ac149efa52acf2b487aef9aa5587e0f/pulltorefreshlibrary/src/main/res/drawable/ic_refreshing.png -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/res/layout/layout_load_more_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/res/layout/layout_refresh_head_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 21 | 27 | 28 | 33 | 34 | 41 | 42 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PullToRefreshLibrary 3 | 释放立即刷新 4 | 下拉刷新 5 | 正在刷新 6 | 刷新成功 7 | 刷新失败 8 | 加载失败 9 | 10 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PullToRefreshLibrary 3 | release refresh 4 | pull to refresh 5 | refreshing 6 | refresh success 7 | refresh fail 8 | load more fail 9 | 10 | -------------------------------------------------------------------------------- /pulltorefreshlibrary/src/test/java/com/androidkun/pulltorefreshlibrary/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.androidkun.pulltorefreshlibrary; 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 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':pulltorefreshlibrary' 2 | --------------------------------------------------------------------------------