├── settings.gradle ├── extendedrecyclerview ├── gradle.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── layout │ │ │ ├── layout_progress.xml │ │ │ ├── layout_more_progress.xml │ │ │ ├── layout_extendrecycler_content.xml │ │ │ └── layout_progress_recyclerview.xml │ │ └── values │ │ │ └── attrs.xml │ │ └── java │ │ └── com │ │ └── leowong │ │ └── extendedrecyclerview │ │ ├── models │ │ └── ViewItem.java │ │ ├── PullToRefreshHandlerView.java │ │ ├── decoration │ │ ├── GridSpacingItemDecoration.java │ │ └── DividerItemDecoration.java │ │ ├── RecyclerItemClickListener.java │ │ ├── utils │ │ └── WrapperUtils.java │ │ ├── adapters │ │ ├── CommonAdapter.java │ │ ├── LoadMoreWrapperAdapter.java │ │ └── LoadMoreAdapter.java │ │ └── ExtendedRecyclerView.java ├── .gitignore └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── app ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── icon_nothing.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── attrs.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── menu │ │ │ │ └── menu_item.xml │ │ │ └── layout │ │ │ │ ├── item_string.xml │ │ │ │ ├── item_grid_string.xml │ │ │ │ ├── activity_vertical_sample.xml │ │ │ │ ├── activity_custom_refresh_sample.xml │ │ │ │ ├── item_no_data.xml │ │ │ │ ├── activity_common.xml │ │ │ │ ├── layout_custom_refresh.xml │ │ │ │ ├── view_more_progress.xml │ │ │ │ └── item_click_loadmore.xml │ │ ├── java │ │ │ └── cn │ │ │ │ └── aixuetang │ │ │ │ └── com │ │ │ │ └── recyclerview │ │ │ │ ├── widgets │ │ │ │ ├── adapters │ │ │ │ │ ├── AutoLoadListAdapter.java │ │ │ │ │ ├── GridAdapter.java │ │ │ │ │ ├── GridCommonAdapter.java │ │ │ │ │ └── ClickLoadListAdapter.java │ │ │ │ ├── CustomRefreshLayout.java │ │ │ │ └── ProgressWheel.java │ │ │ │ ├── CommonDemoActivity.java │ │ │ │ ├── LoadMoreWrapperActivity.java │ │ │ │ ├── LinearLayoutAutoLoadMoreActivity.java │ │ │ │ ├── LinearLayoutClickLoadMoreActivity.java │ │ │ │ ├── ExtendedRecyclerViewDemos.java │ │ │ │ ├── CustomSwipeRefreshGridLayoutActivity.java │ │ │ │ └── GridLayoutActivity.java │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── cn │ │ └── aixuetang │ │ └── com │ │ └── recyclerview │ │ └── ApplicationTest.java ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── .gitattributes ├── .gitignore ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app',':extendedrecyclerview' 2 | -------------------------------------------------------------------------------- /extendedrecyclerview/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4608m -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglg/ExtendedRecyclerView/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglg/ExtendedRecyclerView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglg/ExtendedRecyclerView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglg/ExtendedRecyclerView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/icon_nothing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglg/ExtendedRecyclerView/HEAD/app/src/main/res/mipmap-xhdpi/icon_nothing.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglg/ExtendedRecyclerView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ExtendedRecyclerView 3 | 4 | Hello world! 5 | Settings 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Oct 26 09:47:13 CST 2017 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-4.1-all.zip 7 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/res/layout/layout_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_item.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/models/ViewItem.java: -------------------------------------------------------------------------------- 1 | package com.leowong.extendedrecyclerview.models; 2 | 3 | import java.io.Serializable; 4 | 5 | public class ViewItem implements Serializable { 6 | 7 | 8 | public Object model; 9 | public int itemType; 10 | 11 | public ViewItem(int itemType, Object model) { 12 | this.itemType = itemType; 13 | this.model = model; 14 | } 15 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/aixuetang/com/recyclerview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | lint.xml 5 | 6 | #Eclipse 7 | .project 8 | .classpath 9 | .settings 10 | .checkstyle 11 | 12 | #IntelliJ IDEA 13 | .idea 14 | *.iml 15 | *.ipr 16 | *.iws 17 | classes 18 | gen-external-apklibs 19 | 20 | #Maven 21 | target 22 | release.properties 23 | pom.xml.* 24 | 25 | #Ant 26 | ant.properties 27 | local.properties 28 | proguard.cfg 29 | proguard-project.txt 30 | 31 | #Other 32 | .DS_Store 33 | dist/ 34 | tmp 35 | 36 | # Gradle 37 | .gradle 38 | /build 39 | /out -------------------------------------------------------------------------------- /app/src/main/res/layout/item_string.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | lint.xml 5 | 6 | #Eclipse 7 | .project 8 | .classpath 9 | .settings 10 | .checkstyle 11 | 12 | #IntelliJ IDEA 13 | .idea 14 | *.iml 15 | *.ipr 16 | *.iws 17 | classes 18 | gen-external-apklibs 19 | 20 | #Maven 21 | target 22 | release.properties 23 | pom.xml.* 24 | 25 | #Ant 26 | ant.properties 27 | local.properties 28 | proguard.cfg 29 | proguard-project.txt 30 | 31 | #Other 32 | .DS_Store 33 | dist/ 34 | tmp 35 | 36 | # Gradle 37 | .gradle 38 | /build 39 | /out -------------------------------------------------------------------------------- /extendedrecyclerview/.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | lint.xml 5 | 6 | #Eclipse 7 | .project 8 | .classpath 9 | .settings 10 | .checkstyle 11 | 12 | #IntelliJ IDEA 13 | .idea 14 | *.iml 15 | *.ipr 16 | *.iws 17 | classes 18 | gen-external-apklibs 19 | 20 | #Maven 21 | target 22 | release.properties 23 | pom.xml.* 24 | 25 | #Ant 26 | ant.properties 27 | local.properties 28 | proguard.cfg 29 | proguard-project.txt 30 | 31 | #Other 32 | .DS_Store 33 | dist/ 34 | tmp 35 | 36 | # Gradle 37 | .gradle 38 | /build 39 | /out -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/res/layout/layout_more_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/res/layout/layout_extendrecycler_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_grid_string.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_vertical_sample.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 G:\development\android-sdks/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/main/res/layout/activity_custom_refresh_sample.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_no_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_common.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_custom_refresh.xml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion '26.0.2' 6 | 7 | defaultConfig { 8 | applicationId "cn.aixuetang.com.recyclerview" 9 | minSdkVersion 15 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile 'jp.wasabeef:recyclerview-animators:2.2.3' 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | compile 'com.android.support:appcompat-v7:26.1.0' 26 | compile 'in.srain.cube:ultra-ptr:1.0.11' 27 | compile project(':extendedrecyclerview') 28 | compile 'com.android.support:cardview-v7:26.1.0' 29 | } 30 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/widgets/adapters/AutoLoadListAdapter.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview.widgets.adapters; 2 | 3 | 4 | import com.leowong.extendedrecyclerview.adapters.LoadMoreAdapter; 5 | import com.leowong.extendedrecyclerview.models.ViewItem; 6 | 7 | import java.util.List; 8 | 9 | import cn.aixuetang.com.recyclerview.R; 10 | 11 | public class AutoLoadListAdapter extends LoadMoreAdapter { 12 | public AutoLoadListAdapter(List mDatas) { 13 | super(mDatas); 14 | } 15 | 16 | public AutoLoadListAdapter(List mDatas, int layoutId) { 17 | super(mDatas, layoutId); 18 | } 19 | 20 | @Override 21 | public int getNormalLayoutId(int itemType) { 22 | return R.layout.item_string; 23 | } 24 | 25 | @Override 26 | public void onBindNormalViewHolder(CommonViewHolder holder, int position) { 27 | holder.setText(R.id.textView, mDatas.get(position).model.toString()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_more_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_click_loadmore.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/widgets/adapters/GridAdapter.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview.widgets.adapters; 2 | 3 | import com.leowong.extendedrecyclerview.adapters.LoadMoreAdapter; 4 | import com.leowong.extendedrecyclerview.models.ViewItem; 5 | 6 | import java.util.List; 7 | 8 | import cn.aixuetang.com.recyclerview.R; 9 | 10 | /** 11 | * User: wanglg 12 | * Date: 2015-12-07 13 | * Time: 11:46 14 | * FIXME 15 | */ 16 | public class GridAdapter extends LoadMoreAdapter { 17 | public GridAdapter(List mDatas) { 18 | super(mDatas); 19 | } 20 | 21 | public GridAdapter(List mDatas, int layoutId) { 22 | super(mDatas, layoutId); 23 | } 24 | 25 | @Override 26 | public int getNormalLayoutId(int itemType) { 27 | return R.layout.item_grid_string; 28 | } 29 | 30 | @Override 31 | public void onBindNormalViewHolder(CommonViewHolder holder, int position) { 32 | holder.setText(R.id.textView, mDatas.get(position).model.toString()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/res/layout/layout_progress_recyclerview.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 11 | 12 | 17 | 18 | 19 | 20 | 26 | 27 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/widgets/adapters/GridCommonAdapter.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview.widgets.adapters; 2 | 3 | import com.leowong.extendedrecyclerview.adapters.CommonAdapter; 4 | 5 | import java.util.List; 6 | 7 | import cn.aixuetang.com.recyclerview.R; 8 | 9 | /** 10 | * User: wanglg 11 | * Date: 2015-12-07 12 | * Time: 11:46 13 | * FIXME 14 | */ 15 | public class GridCommonAdapter extends CommonAdapter { 16 | public GridCommonAdapter(List mDatas) { 17 | super(mDatas); 18 | } 19 | 20 | @Override 21 | public int getLayoutId(int viewType) { 22 | return R.layout.item_grid_string; 23 | } 24 | 25 | @Override 26 | public void onBindViewHolder(CommonViewHolder holder, int position) { 27 | holder.setText(R.id.textView, mDatas.get(position)); 28 | } 29 | 30 | /* public GridCommonAdapter(List mDatas, int layoutId) { 31 | super(mDatas, layoutId); 32 | }*/ 33 | 34 | /* @Override 35 | public int getNormalLayoutId(int itemType) { 36 | return R.layout.item_grid_string; 37 | } 38 | 39 | @Override 40 | public void onBindNormalViewHolder(CommonViewHolder holder, int position) { 41 | holder.setText(R.id.textView, mDatas.get(position).model.toString()); 42 | }*/ 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/widgets/adapters/ClickLoadListAdapter.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview.widgets.adapters; 2 | 3 | 4 | import android.view.View; 5 | 6 | import com.leowong.extendedrecyclerview.adapters.LoadMoreAdapter; 7 | import com.leowong.extendedrecyclerview.models.ViewItem; 8 | 9 | import java.util.List; 10 | 11 | import cn.aixuetang.com.recyclerview.R; 12 | 13 | public class ClickLoadListAdapter extends LoadMoreAdapter { 14 | public ClickLoadListAdapter(List mDatas) { 15 | super(mDatas); 16 | } 17 | 18 | public ClickLoadListAdapter(List mDatas, int layoutId) { 19 | super(mDatas, layoutId); 20 | } 21 | 22 | @Override 23 | public int getNormalLayoutId(int itemType) { 24 | return R.layout.item_string; 25 | } 26 | 27 | @Override 28 | public void onBindNormalViewHolder(CommonViewHolder holder, int position) { 29 | holder.setText(R.id.textView, mDatas.get(position).model.toString()); 30 | } 31 | 32 | @Override 33 | public void onBindLoadMoreViewHolder(CommonViewHolder holder, int position) { 34 | super.onBindLoadMoreViewHolder(holder, position); 35 | holder.setVisible(R.id.progressText, View.VISIBLE); 36 | holder.setVisible(R.id.progress, View.INVISIBLE); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/PullToRefreshHandlerView.java: -------------------------------------------------------------------------------- 1 | package com.leowong.extendedrecyclerview; 2 | 3 | import android.content.Context; 4 | import android.support.v4.widget.SwipeRefreshLayout; 5 | import android.util.AttributeSet; 6 | 7 | /** 8 | * Default Pull-refresh View 9 | * User: wanglg 10 | * Date: 2016-01-14 11 | * Time: 14:07 12 | * FIXME 13 | */ 14 | class PullToRefreshHandlerView extends SwipeRefreshLayout implements ExtendedRecyclerView.PullToRefreshHandler { 15 | public PullToRefreshHandlerView(Context context) { 16 | super(context); 17 | initView(); 18 | } 19 | 20 | public PullToRefreshHandlerView(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | initView(); 23 | } 24 | 25 | private void initView() { 26 | setEnabled(false); 27 | setColorSchemeResources(android.R.color.holo_orange_light, android.R.color.holo_blue_light, android.R.color.holo_green_light, android.R.color.holo_red_light); 28 | } 29 | 30 | @Override 31 | public void swipeRefreshComplete() { 32 | setRefreshing(false); 33 | } 34 | 35 | @Override 36 | public void setOnRefreshListener(OnRefreshListener listener) { 37 | setEnabled(true); 38 | super.setOnRefreshListener(listener); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/widgets/CustomRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview.widgets; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import com.leowong.extendedrecyclerview.ExtendedRecyclerView; 7 | 8 | import in.srain.cube.views.ptr.PtrClassicFrameLayout; 9 | import in.srain.cube.views.ptr.PtrHandler; 10 | 11 | /** 12 | * 自定义下来刷新view 13 | * User: wanglg 14 | * Date: 2016-01-15 15 | * Time: 14:37 16 | * FIXME 17 | */ 18 | public class CustomRefreshLayout extends PtrClassicFrameLayout implements ExtendedRecyclerView.PullToRefreshHandler { 19 | public CustomRefreshLayout(Context context) { 20 | super(context); 21 | } 22 | 23 | public CustomRefreshLayout(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | initView(); 26 | } 27 | 28 | public CustomRefreshLayout(Context context, AttributeSet attrs, int defStyle) { 29 | super(context, attrs, defStyle); 30 | initView(); 31 | } 32 | 33 | private void initView() { 34 | setEnabled(false); 35 | } 36 | 37 | @Override 38 | public void swipeRefreshComplete() { 39 | refreshComplete(); 40 | } 41 | 42 | @Override 43 | public void setPtrHandler(PtrHandler ptrHandler) { 44 | setEnabled(true); 45 | super.setPtrHandler(ptrHandler); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /extendedrecyclerview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release'//添加 3 | // 这个version是区分library版本的,因此当我们需要更新library时记得修改这个version 4 | version = "1.0.5" 5 | android { 6 | compileSdkVersion 26 7 | buildToolsVersion '26.0.2' 8 | resourcePrefix "recyclerview" 9 | defaultConfig { 10 | minSdkVersion 15 11 | targetSdkVersion 25 12 | versionCode 3 13 | versionName "1.0.5" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | } 19 | } 20 | lintOptions { 21 | abortOnError false 22 | checkReleaseBuilds false 23 | } 24 | dexOptions { 25 | preDexLibraries true 26 | javaMaxHeapSize "3g" 27 | incremental true 28 | dexInProcess = true 29 | } 30 | 31 | } 32 | publish { 33 | userOrg = 'leowong'//bintray.com用户名 34 | groupId = 'com.leowong.library'//jcenter上的路径 35 | artifactId = 'extendedrecyclerview'//项目名称 36 | publishVersion = '1.0.5'//版本号 37 | desc = 'Oh hi, this is a nice description for a project, right?'//描述 38 | website = 'https://github.com/wanglg/ExtendedRecyclerView'//网站,不重要;尽量模拟github上的地址,例如我这样的;当然你有地址最好了 39 | } 40 | //apply from: '../gradle/gradle-mvn-push.gradle' 41 | 42 | dependencies { 43 | compile 'com.android.support:recyclerview-v7:26.1.0' 44 | compile 'com.leowong.library:multistatelayout:1.0.2' 45 | } 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/CommonDemoActivity.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | 8 | import com.leowong.extendedrecyclerview.ExtendedRecyclerView; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import cn.aixuetang.com.recyclerview.widgets.adapters.GridCommonAdapter; 14 | 15 | /** 16 | * User: wanglg 17 | * Date: 2016-07-20 18 | * Time: 10:47 19 | * FIXME 20 | */ 21 | public class CommonDemoActivity extends AppCompatActivity { 22 | private ExtendedRecyclerView mRecycler; 23 | private GridCommonAdapter gridCommonAdapter; 24 | 25 | @Override 26 | protected void onCreate(@Nullable Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_common); 29 | mRecycler = (ExtendedRecyclerView) findViewById(R.id.list); 30 | gridCommonAdapter = new GridCommonAdapter(getList(15)); 31 | mRecycler.setLayoutManager(new LinearLayoutManager(this)); 32 | mRecycler.setAdapter(gridCommonAdapter); 33 | } 34 | 35 | public List getList(int length) { 36 | List datas = new ArrayList<>(); 37 | for (int i = 0; i < length; i++) { 38 | datas.add(i + " str"); 39 | } 40 | return datas; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/decoration/GridSpacingItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.leowong.extendedrecyclerview.decoration; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | 7 | public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { 8 | private int spanCount; 9 | private int spacing; 10 | private boolean includeEdge; 11 | 12 | 13 | public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) { 14 | this.spanCount = spanCount; 15 | this.spacing = spacing; 16 | this.includeEdge = includeEdge; 17 | } 18 | 19 | @Override 20 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 21 | int position = parent.getChildAdapterPosition(view); 22 | int column = position % spanCount; 23 | 24 | if (includeEdge) { 25 | outRect.left = spacing - column * spacing / spanCount; 26 | outRect.right = (column + 1) * spacing / spanCount; 27 | 28 | if (position < spanCount) { 29 | outRect.top = spacing; 30 | } 31 | outRect.bottom = spacing; 32 | } else { 33 | outRect.left = column * spacing / spanCount; 34 | outRect.right = spacing - (column + 1) * spacing / spanCount; 35 | if (position >= spanCount) { 36 | outRect.top = spacing; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description## 2 | 1、多状态切换功能,ExtendedRecyclerView继承[MultiStateLayout][],拥有MultiStateLayout的所有功能,使用方法请参照demo 3 | [MultiStateLayout]: https://github.com/wanglg/MultiStateLayout 4 | 2、封装了一个CommonAdapter便于快速编写RecyclerView的Adapter,我们只需要实现 5 | ``` 6 | public abstract int getLayoutId(int viewType);//返回item 布局id 7 | 8 | public abstract onBindViewHolder(CommonViewHolder holder, int position);//给item绑定数据 9 | ``` 10 | 具体怎么写可以看demo代码,很简单的封装让你的adapter不超过50行代码 11 | 12 | 3、加载更多改用LoadMoreAdapter方式实现,原理是根据getLayoutId的参数不同来返回不同的layout来实现的,我们可以通过setPageCount或构造方法来自定义每页item数量,加载更多回调是通过给LoadMoreAdapter设置ILoadMoreCallback回调 13 | ``` 14 | public interface ILoadMoreCallback { 15 | void loadMore(CommonViewHolder holder, int position); 16 | } 17 | ``` 18 | 参数position为即将要加载下一页的第一个item的position 19 | 20 | 4、完全可以自定义下拉刷新控件,ExtendedRecyclerView默认内部集成v4的SwipeRefreshLayout来做下拉刷新,如果你项目不是用SwipeRefreshLayout来做下拉刷新,你可以通过ExtendedRecyclerView的recyclerSwipe属性来指向你自己的下拉刷新控件,这里需要下拉刷新控件实现ExtendedRecyclerView.PullToRefreshHandler接口,具体可以参照CustomSwipeRefreshGridLayoutActivity来修改下拉刷新效果。 21 | 22 | 5、增加LoadMoreWrapper可以从原有adapter无改变代码支持加载更多 23 | 24 | ## 效果Demo## 25 | ![image](https://github.com/wanglg/resource/blob/master/Default.gif) 26 | ![image](https://github.com/wanglg/resource/blob/master/Custom.gif) 27 | ## Using Gradle## 28 | ``` 29 | repositories { 30 | maven { 31 | url 'https://dl.bintray.com/leowong/maven' 32 | } 33 | } 34 | 35 | compile 'com.leowong.library:extendedrecyclerview:1.0.5' 36 | ``` 37 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/RecyclerItemClickListener.java: -------------------------------------------------------------------------------- 1 | package com.leowong.extendedrecyclerview; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.GestureDetector; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | 9 | public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener { 10 | private OnItemClickListener mListener; 11 | 12 | public interface OnItemClickListener { 13 | void onItemClick(View view, int position); 14 | } 15 | 16 | GestureDetector mGestureDetector; 17 | 18 | public RecyclerItemClickListener(Context context, OnItemClickListener listener) { 19 | mListener = listener; 20 | mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { 21 | @Override 22 | public boolean onSingleTapUp(MotionEvent e) { 23 | return true; 24 | } 25 | }); 26 | } 27 | 28 | @Override 29 | public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) { 30 | View childView = view.findChildViewUnder(e.getX(), e.getY()); 31 | if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) { 32 | mListener.onItemClick(childView, view.getChildLayoutPosition(childView)); 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | @Override 39 | public void onTouchEvent(RecyclerView rv, MotionEvent e) { 40 | } 41 | 42 | @Override 43 | public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 44 | } 45 | } -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/utils/WrapperUtils.java: -------------------------------------------------------------------------------- 1 | package com.leowong.extendedrecyclerview.utils; 2 | 3 | import android.support.v7.widget.GridLayoutManager; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.support.v7.widget.StaggeredGridLayoutManager; 6 | import android.view.ViewGroup; 7 | 8 | /** 9 | * User: wanglg 10 | * Date: 2017-10-27 11 | * Time: 16:26 12 | * FIXME 13 | */ 14 | public class WrapperUtils { 15 | public interface SpanSizeCallback { 16 | int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position); 17 | } 18 | 19 | public static void onAttachedToRecyclerView(RecyclerView.Adapter innerAdapter, RecyclerView recyclerView, final SpanSizeCallback callback) { 20 | innerAdapter.onAttachedToRecyclerView(recyclerView); 21 | 22 | RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 23 | if (layoutManager instanceof GridLayoutManager) { 24 | final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; 25 | final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup(); 26 | 27 | gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 28 | @Override 29 | public int getSpanSize(int position) { 30 | return callback.getSpanSize(gridLayoutManager, spanSizeLookup, position); 31 | } 32 | }); 33 | gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount()); 34 | } 35 | } 36 | 37 | public static void setFullSpan(RecyclerView.ViewHolder holder) { 38 | ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 39 | 40 | if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { 41 | 42 | StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; 43 | 44 | p.setFullSpan(true); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/LoadMoreWrapperActivity.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | 10 | import com.leowong.extendedrecyclerview.ExtendedRecyclerView; 11 | import com.leowong.extendedrecyclerview.adapters.LoadMoreWrapperAdapter; 12 | import com.leowong.extendedrecyclerview.models.ViewItem; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import cn.aixuetang.com.recyclerview.widgets.adapters.GridCommonAdapter; 18 | 19 | /** 20 | * User: wanglg 21 | * Date: 2017-10-27 22 | * Time: 14:35 23 | * FIXME 24 | */ 25 | public class LoadMoreWrapperActivity extends Activity implements LoadMoreWrapperAdapter.ILoadMoreCallback { 26 | private ExtendedRecyclerView mRecycler; 27 | private GridCommonAdapter gridCommonAdapter; 28 | private LoadMoreWrapperAdapter loadMoreWrapperAdapter; 29 | 30 | @Override 31 | protected void onCreate(@Nullable Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_common); 34 | mRecycler = (ExtendedRecyclerView) findViewById(R.id.list); 35 | gridCommonAdapter = new GridCommonAdapter(getList(15)); 36 | loadMoreWrapperAdapter = new LoadMoreWrapperAdapter(gridCommonAdapter); 37 | loadMoreWrapperAdapter.setLoadMoreView(com.leowong.extendedrecyclerview.R.layout.layout_more_progress); 38 | loadMoreWrapperAdapter.setLoadMoreCallback(this); 39 | mRecycler.setLayoutManager(new LinearLayoutManager(this)); 40 | mRecycler.setAdapter(loadMoreWrapperAdapter); 41 | } 42 | 43 | public List getList(int length) { 44 | List datas = new ArrayList<>(); 45 | for (int i = 0; i < length; i++) { 46 | datas.add(i + " str"); 47 | } 48 | return datas; 49 | } 50 | 51 | @Override 52 | public void loadMore(RecyclerView.ViewHolder holder, int position) { 53 | final int index = position; 54 | Handler handler = new Handler(); 55 | handler.postDelayed(new Runnable() { 56 | public void run() { 57 | 58 | List data = gridCommonAdapter.getDatas(); 59 | 60 | for (int i = index; i < index + 15; i++) { 61 | if (i >= 100) { 62 | loadMoreWrapperAdapter.setLoadingCompleted(true); 63 | break; 64 | } 65 | data.add(i + " str"); 66 | } 67 | loadMoreWrapperAdapter.notifyDataSetChanged(); 68 | } 69 | }, 2000); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/adapters/CommonAdapter.java: -------------------------------------------------------------------------------- 1 | package com.leowong.extendedrecyclerview.adapters; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.util.SparseArray; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.CheckBox; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | 17 | /** 18 | * Simple to use RecyclerView 19 | * Created by wangliugeng on 2015/5/15. 20 | */ 21 | public abstract class CommonAdapter extends RecyclerView.Adapter { 22 | protected List mDatas; 23 | public Context mContext; 24 | 25 | public CommonAdapter(List mDatas) { 26 | if (mDatas == null) 27 | mDatas = new ArrayList<>(); 28 | this.mDatas = mDatas; 29 | } 30 | 31 | @Override 32 | public CommonAdapter.CommonViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 33 | View itemView = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(viewType), parent, false); 34 | mContext = itemView.getContext(); 35 | return new CommonViewHolder(itemView); 36 | } 37 | 38 | 39 | @Override 40 | public int getItemCount() { 41 | if (mDatas == null) 42 | return 0; 43 | return mDatas.size(); 44 | } 45 | 46 | public void addItem(T viewItem) { 47 | if (viewItem != null && mDatas != null) { 48 | mDatas.add(viewItem); 49 | notifyItemInserted(mDatas.size() - 1); 50 | } 51 | } 52 | 53 | /** 54 | * add new data 55 | * 56 | * @param newData add to foot 57 | */ 58 | public void addAll(List newData) { 59 | if (mDatas != null) { 60 | int start = mDatas.size(); 61 | mDatas.addAll(newData == null ? new ArrayList() : newData); 62 | notifyItemRangeInserted(start, newData == null ? 0 : newData.size()); 63 | } else { 64 | replaceAll(newData); 65 | } 66 | } 67 | 68 | /** 69 | * There will be a clear animation effects 70 | * 71 | * @param newData replace data 72 | */ 73 | public void replaceAll(List newData) { 74 | clearAll(); 75 | if (newData == null) { 76 | newData = new ArrayList<>(); 77 | } 78 | mDatas = newData; 79 | notifyItemRangeInserted(0, mDatas.size()); 80 | } 81 | 82 | public void refreshData(List newData) { 83 | this.mDatas = newData; 84 | notifyDataSetChanged(); 85 | } 86 | 87 | public void clearAll() { 88 | if (mDatas == null) 89 | return; 90 | int size = this.mDatas.size(); 91 | if (size > 0) { 92 | mDatas = new ArrayList<>(); 93 | this.notifyItemRangeRemoved(0, size); 94 | } 95 | } 96 | 97 | public List getDatas() { 98 | return mDatas; 99 | } 100 | 101 | public abstract int getLayoutId(int viewType); 102 | 103 | public static class CommonViewHolder extends RecyclerView.ViewHolder { 104 | private final SparseArray mViews; 105 | public Context mContext; 106 | 107 | public CommonViewHolder(View itemView) { 108 | super(itemView); 109 | mContext = itemView.getContext(); 110 | mViews = new SparseArray<>(); 111 | } 112 | 113 | public T getView(int viewId) { 114 | View view = mViews.get(viewId); 115 | if (view == null) { 116 | view = itemView.findViewById(viewId); 117 | mViews.put(viewId, view); 118 | } 119 | return (T) view; 120 | } 121 | 122 | public void setText(int viewId, String text) { 123 | TextView view = getView(viewId); 124 | view.setText(text); 125 | } 126 | 127 | public void setTextColor(int viewId, int textColor) { 128 | TextView view = getView(viewId); 129 | view.setTextColor(textColor); 130 | } 131 | 132 | public void setVisible(int viewId, int visible) { 133 | View view = getView(viewId); 134 | view.setVisibility(visible); 135 | } 136 | 137 | public void setImageResource(int viewId, int drawableId) { 138 | ImageView view = getView(viewId); 139 | view.setImageResource(drawableId); 140 | } 141 | 142 | public void setChecked(int viewId, boolean checked) { 143 | CheckBox view = getView(viewId); 144 | view.setChecked(checked); 145 | } 146 | 147 | public void setOnClickListener(int viewId, View.OnClickListener clickListener) { 148 | getView(viewId).setOnClickListener(clickListener); 149 | } 150 | 151 | public void setOnItemClickListener(View.OnClickListener clickListener) { 152 | itemView.setOnClickListener(clickListener); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/decoration/DividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.leowong.extendedrecyclerview.decoration; 18 | 19 | import android.content.Context; 20 | import android.content.res.TypedArray; 21 | import android.graphics.Canvas; 22 | import android.graphics.Rect; 23 | import android.graphics.drawable.Drawable; 24 | import android.support.v4.view.ViewCompat; 25 | import android.support.v7.widget.LinearLayoutManager; 26 | import android.support.v7.widget.RecyclerView; 27 | import android.view.View; 28 | 29 | public class DividerItemDecoration extends RecyclerView.ItemDecoration { 30 | 31 | private static final int[] ATTRS = new int[]{ 32 | android.R.attr.listDivider 33 | }; 34 | 35 | public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; 36 | 37 | public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; 38 | 39 | private Drawable mDivider; 40 | 41 | private int mOrientation; 42 | 43 | private int leftMarign; 44 | private int rightMarign; 45 | private boolean mDrawLastItemDecoration; 46 | 47 | public DividerItemDecoration(Context context, int orientation) { 48 | final TypedArray a = context.obtainStyledAttributes(ATTRS); 49 | mDivider = a.getDrawable(0); 50 | a.recycle(); 51 | setOrientation(orientation); 52 | } 53 | 54 | public void setOrientation(int orientation) { 55 | if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { 56 | throw new IllegalArgumentException("invalid orientation"); 57 | } 58 | mOrientation = orientation; 59 | } 60 | 61 | public void setmDrawLastItemDecoration(boolean mDrawLastItemDecoration) { 62 | this.mDrawLastItemDecoration = mDrawLastItemDecoration; 63 | } 64 | 65 | public void setLeftMarign(int leftMarign) { 66 | this.leftMarign = leftMarign; 67 | } 68 | 69 | public void setRightMarign(int rightMarign) { 70 | this.rightMarign = rightMarign; 71 | } 72 | 73 | @Override 74 | public void onDraw(Canvas c, RecyclerView parent) { 75 | if (mOrientation == VERTICAL_LIST) { 76 | drawVertical(c, parent); 77 | } else { 78 | drawHorizontal(c, parent); 79 | } 80 | } 81 | 82 | public void drawVertical(Canvas c, RecyclerView parent) { 83 | final int left = parent.getPaddingLeft() + leftMarign; 84 | final int right = parent.getWidth() - parent.getPaddingRight() - rightMarign; 85 | 86 | final int childCount = parent.getChildCount(); 87 | int max = mDrawLastItemDecoration ? childCount : childCount - 1; 88 | for (int i = 0; i < max; i++) { 89 | final View child = parent.getChildAt(i); 90 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 91 | .getLayoutParams(); 92 | final int top = child.getBottom() + params.bottomMargin + 93 | Math.round(ViewCompat.getTranslationY(child)); 94 | final int bottom = top + mDivider.getIntrinsicHeight(); 95 | mDivider.setBounds(left, top, right, bottom); 96 | mDivider.draw(c); 97 | } 98 | } 99 | 100 | public void drawHorizontal(Canvas c, RecyclerView parent) { 101 | final int top = parent.getPaddingTop(); 102 | final int bottom = parent.getHeight() - parent.getPaddingBottom(); 103 | 104 | final int childCount = parent.getChildCount(); 105 | int max = mDrawLastItemDecoration ? childCount : childCount - 1; 106 | for (int i = 0; i < max; i++) { 107 | final View child = parent.getChildAt(i); 108 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 109 | .getLayoutParams(); 110 | final int left = child.getRight() + params.rightMargin + 111 | Math.round(ViewCompat.getTranslationX(child)); 112 | final int right = left + mDivider.getIntrinsicHeight(); 113 | mDivider.setBounds(left, top, right, bottom); 114 | mDivider.draw(c); 115 | } 116 | } 117 | 118 | @Override 119 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 120 | if (mOrientation == VERTICAL_LIST) { 121 | outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); 122 | } else { 123 | outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/adapters/LoadMoreWrapperAdapter.java: -------------------------------------------------------------------------------- 1 | package com.leowong.extendedrecyclerview.adapters; 2 | 3 | import android.support.v7.widget.GridLayoutManager; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.support.v7.widget.StaggeredGridLayoutManager; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.leowong.extendedrecyclerview.utils.WrapperUtils; 11 | 12 | /** 13 | * User: wanglg 14 | * Date: 2017-10-27 15 | * Time: 11:46 16 | * FIXME 17 | */ 18 | public class LoadMoreWrapperAdapter extends RecyclerView.Adapter { 19 | public static final int ITEM_TYPE_LOAD_MORE = Integer.MAX_VALUE - 2; 20 | private RecyclerView.Adapter mInnerAdapter; 21 | private View mLoadMoreView; 22 | private int mLoadMoreLayoutId; 23 | private boolean isLoadingCompleted; 24 | private ILoadMoreCallback callback; 25 | 26 | public LoadMoreWrapperAdapter(RecyclerView.Adapter adapter) { 27 | mInnerAdapter = adapter; 28 | } 29 | 30 | @Override 31 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 32 | if (viewType == ITEM_TYPE_LOAD_MORE) { 33 | CommonAdapter.CommonViewHolder holder; 34 | if (mLoadMoreView != null) { 35 | holder = new CommonAdapter.CommonViewHolder(mLoadMoreView); 36 | } else { 37 | View itemView = LayoutInflater.from(parent.getContext()).inflate(mLoadMoreLayoutId, parent, 38 | false); 39 | holder = new CommonAdapter.CommonViewHolder(itemView); 40 | } 41 | return holder; 42 | } 43 | return mInnerAdapter.onCreateViewHolder(parent, viewType); 44 | } 45 | 46 | @Override 47 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 48 | if (isShowLoadMore(position)) { 49 | if (callback != null) { 50 | callback.loadMore(holder, position); 51 | } 52 | return; 53 | } 54 | mInnerAdapter.onBindViewHolder(holder, position); 55 | } 56 | 57 | public void setLoadMoreCallback(ILoadMoreCallback callback) { 58 | this.callback = callback; 59 | } 60 | 61 | @Override 62 | public int getItemCount() { 63 | return isLoadingCompleted ? mInnerAdapter.getItemCount() : mInnerAdapter.getItemCount() + (hasLoadMore() ? 1 : 0); 64 | } 65 | 66 | @Override 67 | public int getItemViewType(int position) { 68 | if (isShowLoadMore(position)) { 69 | return ITEM_TYPE_LOAD_MORE; 70 | } 71 | return mInnerAdapter.getItemViewType(position); 72 | } 73 | 74 | private boolean isShowLoadMore(int position) { 75 | return hasLoadMore() && !isLoadingCompleted && (position >= mInnerAdapter.getItemCount()); 76 | } 77 | 78 | private boolean hasLoadMore() { 79 | return mLoadMoreView != null || mLoadMoreLayoutId != 0; 80 | } 81 | 82 | public LoadMoreWrapperAdapter setLoadMoreView(View loadMoreView) { 83 | mLoadMoreView = loadMoreView; 84 | return this; 85 | } 86 | 87 | public boolean getLoadingCompleted() { 88 | return isLoadingCompleted; 89 | } 90 | 91 | public void setLoadingCompleted(boolean isLoadingCompleted) { 92 | this.isLoadingCompleted = isLoadingCompleted; 93 | } 94 | 95 | public LoadMoreWrapperAdapter setLoadMoreView(int layoutId) { 96 | mLoadMoreLayoutId = layoutId; 97 | return this; 98 | } 99 | 100 | @Override 101 | public void onAttachedToRecyclerView(RecyclerView recyclerView) { 102 | WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback() { 103 | @Override 104 | public int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position) { 105 | if (isShowLoadMore(position)) { 106 | return layoutManager.getSpanCount(); 107 | } 108 | if (oldLookup != null) { 109 | return oldLookup.getSpanSize(position); 110 | } 111 | return 1; 112 | } 113 | }); 114 | } 115 | 116 | 117 | @Override 118 | public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { 119 | mInnerAdapter.onViewAttachedToWindow(holder); 120 | 121 | if (isShowLoadMore(holder.getLayoutPosition())) { 122 | setFullSpan(holder); 123 | } 124 | } 125 | 126 | private void setFullSpan(RecyclerView.ViewHolder holder) { 127 | ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 128 | 129 | if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { 130 | StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; 131 | 132 | p.setFullSpan(true); 133 | } 134 | } 135 | 136 | public interface ILoadMoreCallback { 137 | void loadMore(RecyclerView.ViewHolder holder, int position); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/LinearLayoutAutoLoadMoreActivity.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.view.Menu; 9 | import android.widget.Toast; 10 | 11 | import com.leowong.extendedrecyclerview.ExtendedRecyclerView; 12 | import com.leowong.extendedrecyclerview.adapters.CommonAdapter; 13 | import com.leowong.extendedrecyclerview.adapters.LoadMoreAdapter; 14 | import com.leowong.extendedrecyclerview.decoration.DividerItemDecoration; 15 | import com.leowong.extendedrecyclerview.models.ViewItem; 16 | 17 | import java.util.ArrayList; 18 | 19 | import cn.aixuetang.com.recyclerview.widgets.adapters.AutoLoadListAdapter; 20 | import jp.wasabeef.recyclerview.animators.FadeInAnimator; 21 | 22 | public class LinearLayoutAutoLoadMoreActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, LoadMoreAdapter.ILoadMoreCallback { 23 | private ExtendedRecyclerView mRecycler; 24 | private AutoLoadListAdapter mAdapter; 25 | int position = 0; 26 | private LinearLayoutManager linearLayoutManager; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_vertical_sample); 32 | 33 | ArrayList list = new ArrayList<>(); 34 | mAdapter = new AutoLoadListAdapter(list, R.layout.view_more_progress); 35 | 36 | mRecycler = (ExtendedRecyclerView) findViewById(R.id.list); 37 | linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); 38 | mRecycler.setLayoutManager(linearLayoutManager); 39 | Thread thread = new Thread(new Runnable() { 40 | @Override 41 | public void run() { 42 | try { 43 | Thread.sleep(2000); 44 | } catch (InterruptedException e) { 45 | e.printStackTrace(); 46 | } 47 | 48 | runOnUiThread(new Runnable() { 49 | @Override 50 | public void run() { 51 | mRecycler.setAdapter(mAdapter); 52 | } 53 | }); 54 | 55 | try { 56 | Thread.sleep(1000); 57 | } catch (InterruptedException e) { 58 | e.printStackTrace(); 59 | } 60 | 61 | runOnUiThread(new Runnable() { 62 | @Override 63 | public void run() { 64 | ArrayList data = new ArrayList(); 65 | for (int i = 0; i < mAdapter.getPageCount(); i++) { 66 | data.add(new ViewItem(0, "default str" + position++)); 67 | } 68 | mAdapter.addAll(data); 69 | } 70 | }); 71 | } 72 | }); 73 | thread.start(); 74 | mAdapter.setLoadMoreCallback(this); 75 | // ((SwipeRefreshLayout) mRecycler.getSwipeRefreshView()).setOnRefreshListener(this); 76 | FadeInAnimator fadeInAnimator = new FadeInAnimator(); 77 | mRecycler.setItemAnimator(fadeInAnimator); 78 | //添加分割线 79 | DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST); 80 | dividerItemDecoration.setLeftMarign(200); 81 | dividerItemDecoration.setRightMarign(100); 82 | mRecycler.addItemDecoration(dividerItemDecoration); 83 | } 84 | 85 | 86 | @Override 87 | public void onRefresh() { 88 | Toast.makeText(this, "Refresh", Toast.LENGTH_LONG).show(); 89 | 90 | Handler handler = new Handler(); 91 | handler.postDelayed(new Runnable() { 92 | public void run() { 93 | position = 0; 94 | ArrayList data = new ArrayList(); 95 | for (int i = 0; i < mAdapter.getPageCount(); i++) { 96 | data.add(new ViewItem(0, "refresh str" + position++)); 97 | } 98 | mAdapter.replaceAll(data); 99 | } 100 | }, 2000); 101 | } 102 | 103 | @Override 104 | public boolean onCreateOptionsMenu(Menu menu) { 105 | return super.onCreateOptionsMenu(menu); 106 | } 107 | 108 | @Override 109 | public void loadMore(CommonAdapter.CommonViewHolder holder, int position) { 110 | Toast.makeText(this, "loadmore-->" + position, Toast.LENGTH_LONG).show(); 111 | final int index = position; 112 | Handler handler = new Handler(); 113 | handler.postDelayed(new Runnable() { 114 | public void run() { 115 | ArrayList data = new ArrayList<>(); 116 | 117 | for (int i = index; i < index + mAdapter.getPageCount(); i++) { 118 | if (i >= 100) 119 | break; 120 | data.add(new ViewItem(0, "loadmore str" + i)); 121 | } 122 | mAdapter.addAll(data); 123 | } 124 | }, 2000); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/adapters/LoadMoreAdapter.java: -------------------------------------------------------------------------------- 1 | package com.leowong.extendedrecyclerview.adapters; 2 | 3 | import com.leowong.extendedrecyclerview.R; 4 | import com.leowong.extendedrecyclerview.models.ViewItem; 5 | 6 | import java.util.List; 7 | 8 | 9 | public abstract class LoadMoreAdapter extends CommonAdapter { 10 | 11 | public static final int VIEW_TYPE_ITEM_LOAD_MORE = 2015063009; 12 | private int loadMoreLayoutId = R.layout.layout_more_progress; 13 | private boolean isLoading; 14 | private boolean isLoadingCompleted; 15 | 16 | 17 | private int pageCount = 25; 18 | 19 | 20 | private ILoadMoreCallback callback; 21 | 22 | public LoadMoreAdapter(List mDatas) { 23 | super(mDatas); 24 | if (this.mDatas.size() >= pageCount) { 25 | this.mDatas.add(getLoadMoreItem()); 26 | } 27 | 28 | } 29 | 30 | public LoadMoreAdapter(int pageCount, List mDatas) { 31 | super(mDatas); 32 | this.pageCount = pageCount; 33 | if (this.mDatas.size() >= pageCount) { 34 | this.mDatas.add(getLoadMoreItem()); 35 | } 36 | } 37 | 38 | /** 39 | * @param mDatas bind data 40 | * @param loadMoreLayoutId load more layout id 41 | */ 42 | public LoadMoreAdapter(List mDatas, int loadMoreLayoutId) { 43 | super(mDatas); 44 | this.loadMoreLayoutId = loadMoreLayoutId; 45 | if (this.mDatas.size() >= pageCount) { 46 | this.mDatas.add(getLoadMoreItem()); 47 | } 48 | } 49 | 50 | public LoadMoreAdapter(int pageCount, List mDatas, int loadMoreLayoutId) { 51 | super(mDatas); 52 | this.pageCount = pageCount; 53 | this.loadMoreLayoutId = loadMoreLayoutId; 54 | if (this.mDatas.size() >= pageCount) { 55 | this.mDatas.add(getLoadMoreItem()); 56 | } 57 | } 58 | 59 | @Override 60 | public int getLayoutId(int viewType) { 61 | if (viewType == VIEW_TYPE_ITEM_LOAD_MORE) 62 | return loadMoreLayoutId; 63 | return getNormalLayoutId(viewType); 64 | } 65 | 66 | public int getPageCount() { 67 | return pageCount; 68 | } 69 | 70 | public void setPageCount(int pageCount) { 71 | this.pageCount = pageCount; 72 | int lastPosition = mDatas.size() - 1; 73 | if (lastPosition >= 0) { 74 | ViewItem viewItem = mDatas.get(lastPosition); 75 | if (viewItem.itemType == VIEW_TYPE_ITEM_LOAD_MORE) { 76 | mDatas.remove(lastPosition); 77 | } 78 | } 79 | if (this.mDatas.size() >= pageCount) { 80 | this.mDatas.add(getLoadMoreItem()); 81 | } 82 | } 83 | 84 | public abstract int getNormalLayoutId(int itemType); 85 | 86 | public abstract void onBindNormalViewHolder(CommonViewHolder holder, int position); 87 | 88 | 89 | public void onBindLoadMoreViewHolder(CommonViewHolder holder, int position) { 90 | } 91 | 92 | @Override 93 | public void onBindViewHolder(CommonViewHolder holder, int position) { 94 | 95 | if (position == mDatas.size() - 1 && position >= pageCount - 1 && !isLoading && !isLoadingCompleted) { 96 | isLoading = true; 97 | onBindLoadMoreViewHolder(holder, position); 98 | if (callback != null) { 99 | callback.loadMore(holder, position); 100 | } 101 | } 102 | if (mDatas.get(position).itemType != VIEW_TYPE_ITEM_LOAD_MORE) { 103 | onBindNormalViewHolder(holder, position); 104 | } 105 | } 106 | 107 | @Override 108 | public int getItemViewType(int position) { 109 | ViewItem viewItem = mDatas.get(position); 110 | return viewItem.itemType; 111 | } 112 | 113 | @Override 114 | public void addAll(List newData) { 115 | hideLoadMore(); 116 | if (newData == null || newData.size() < pageCount) { 117 | isLoadingCompleted = true; 118 | } else if (!isLoadingCompleted) { 119 | newData.add(getLoadMoreItem()); 120 | } 121 | super.addAll(newData); 122 | isLoading = false; 123 | } 124 | 125 | @Override 126 | public void replaceAll(List newData) { 127 | isLoading = false; 128 | isLoadingCompleted = false; 129 | if (newData == null || newData.size() < pageCount) { 130 | isLoadingCompleted = true; 131 | } else if (!isLoadingCompleted) { 132 | newData.add(getLoadMoreItem()); 133 | } 134 | super.replaceAll(newData); 135 | } 136 | 137 | public void setLoadMoreCallback(ILoadMoreCallback callback) { 138 | this.callback = callback; 139 | } 140 | 141 | public boolean getLoadingCompleted() { 142 | return isLoadingCompleted; 143 | } 144 | 145 | public void setLoadingCompleted(boolean isLoadingCompleted) { 146 | this.isLoadingCompleted = isLoadingCompleted; 147 | } 148 | 149 | private ViewItem getLoadMoreItem() { 150 | return new ViewItem(VIEW_TYPE_ITEM_LOAD_MORE, null); 151 | } 152 | 153 | private void hideLoadMore() { 154 | int lastPosition = mDatas.size() - 1; 155 | if (lastPosition >= 0) { 156 | ViewItem viewItem = mDatas.get(lastPosition); 157 | if (viewItem.itemType == VIEW_TYPE_ITEM_LOAD_MORE) { 158 | mDatas.remove(lastPosition); 159 | notifyItemRemoved(lastPosition); 160 | } 161 | } 162 | } 163 | 164 | public interface ILoadMoreCallback { 165 | void loadMore(CommonViewHolder holder, int position); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/LinearLayoutClickLoadMoreActivity.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.view.Menu; 9 | import android.view.View; 10 | import android.widget.Toast; 11 | 12 | import com.leowong.extendedrecyclerview.ExtendedRecyclerView; 13 | import com.leowong.extendedrecyclerview.adapters.CommonAdapter; 14 | import com.leowong.extendedrecyclerview.adapters.LoadMoreAdapter; 15 | import com.leowong.extendedrecyclerview.decoration.DividerItemDecoration; 16 | import com.leowong.extendedrecyclerview.models.ViewItem; 17 | 18 | import java.util.ArrayList; 19 | 20 | import cn.aixuetang.com.recyclerview.widgets.adapters.ClickLoadListAdapter; 21 | import jp.wasabeef.recyclerview.animators.FadeInAnimator; 22 | 23 | public class LinearLayoutClickLoadMoreActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, LoadMoreAdapter.ILoadMoreCallback { 24 | private ExtendedRecyclerView mRecycler; 25 | private ClickLoadListAdapter mAdapter; 26 | int position = 0; 27 | private LinearLayoutManager linearLayoutManager; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_vertical_sample); 33 | 34 | ArrayList list = new ArrayList<>(); 35 | mAdapter = new ClickLoadListAdapter(list, R.layout.item_click_loadmore); 36 | 37 | mRecycler = (ExtendedRecyclerView) findViewById(R.id.list); 38 | linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); 39 | mRecycler.setLayoutManager(linearLayoutManager); 40 | Thread thread = new Thread(new Runnable() { 41 | @Override 42 | public void run() { 43 | try { 44 | Thread.sleep(2000); 45 | } catch (InterruptedException e) { 46 | e.printStackTrace(); 47 | } 48 | 49 | runOnUiThread(new Runnable() { 50 | @Override 51 | public void run() { 52 | mRecycler.setAdapter(mAdapter); 53 | } 54 | }); 55 | 56 | try { 57 | Thread.sleep(1000); 58 | } catch (InterruptedException e) { 59 | e.printStackTrace(); 60 | } 61 | 62 | runOnUiThread(new Runnable() { 63 | @Override 64 | public void run() { 65 | ArrayList data = new ArrayList(); 66 | for (int i = 0; i < mAdapter.getPageCount(); i++) { 67 | data.add(new ViewItem(0, "default str" + position++)); 68 | } 69 | mAdapter.addAll(data); 70 | } 71 | }); 72 | } 73 | }); 74 | thread.start(); 75 | mAdapter.setLoadMoreCallback(this); 76 | ((SwipeRefreshLayout) mRecycler.getSwipeRefreshView()).setOnRefreshListener(this); 77 | FadeInAnimator fadeInAnimator = new FadeInAnimator(); 78 | mRecycler.setItemAnimator(fadeInAnimator); 79 | DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST); 80 | dividerItemDecoration.setLeftMarign(200); 81 | dividerItemDecoration.setRightMarign(100); 82 | mRecycler.addItemDecoration(dividerItemDecoration); 83 | } 84 | 85 | 86 | @Override 87 | public void onRefresh() { 88 | Toast.makeText(this, "Refresh", Toast.LENGTH_LONG).show(); 89 | 90 | Handler handler = new Handler(); 91 | handler.postDelayed(new Runnable() { 92 | public void run() { 93 | position = 0; 94 | ArrayList data = new ArrayList(); 95 | for (int i = 0; i < mAdapter.getPageCount(); i++) { 96 | data.add(new ViewItem(0, "refresh str" + position++)); 97 | } 98 | mAdapter.replaceAll(data); 99 | } 100 | }, 2000); 101 | } 102 | 103 | @Override 104 | public boolean onCreateOptionsMenu(Menu menu) { 105 | return super.onCreateOptionsMenu(menu); 106 | } 107 | 108 | @Override 109 | public void loadMore(final CommonAdapter.CommonViewHolder holder, int position) { 110 | Toast.makeText(this, "loadmore-->" + position, Toast.LENGTH_LONG).show(); 111 | final int index = position; 112 | holder.setOnClickListener(R.id.progressText, new View.OnClickListener() { 113 | @Override 114 | public void onClick(View v) { 115 | holder.setVisible(R.id.progressText, View.INVISIBLE); 116 | holder.setVisible(R.id.progress, View.VISIBLE); 117 | Handler handler = new Handler(); 118 | handler.postDelayed(new Runnable() { 119 | public void run() { 120 | ArrayList data = new ArrayList(); 121 | 122 | for (int i = index; i < index + mAdapter.getPageCount(); i++) { 123 | if (i >= 100) 124 | break; 125 | data.add(new ViewItem(0, "loadmore str" + i)); 126 | } 127 | mAdapter.addAll(data); 128 | } 129 | }, 2000); 130 | } 131 | }); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/ExtendedRecyclerViewDemos.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.aixuetang.com.recyclerview; 18 | 19 | import android.app.ListActivity; 20 | import android.content.Intent; 21 | import android.content.pm.PackageManager; 22 | import android.content.pm.ResolveInfo; 23 | import android.os.Bundle; 24 | import android.view.View; 25 | import android.widget.ListView; 26 | import android.widget.SimpleAdapter; 27 | 28 | import java.text.Collator; 29 | import java.util.ArrayList; 30 | import java.util.Collections; 31 | import java.util.Comparator; 32 | import java.util.HashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | 36 | public class ExtendedRecyclerViewDemos extends ListActivity { 37 | 38 | @Override 39 | public void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | 42 | Intent intent = getIntent(); 43 | String path = intent.getStringExtra("com.example.android.apis.Path"); 44 | 45 | if (path == null) { 46 | path = ""; 47 | } 48 | 49 | setListAdapter(new SimpleAdapter(this, getData(path), 50 | android.R.layout.simple_list_item_1, new String[] { "title" }, 51 | new int[] { android.R.id.text1 })); 52 | getListView().setTextFilterEnabled(true); 53 | } 54 | 55 | protected List> getData(String prefix) { 56 | List> myData = new ArrayList>(); 57 | 58 | Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 59 | mainIntent.addCategory("com.example.extendedrecyclerview.SAMPLE_CODE"); 60 | 61 | PackageManager pm = getPackageManager(); 62 | List list = pm.queryIntentActivities(mainIntent, 0); 63 | 64 | if (null == list) 65 | return myData; 66 | 67 | String[] prefixPath; 68 | String prefixWithSlash = prefix; 69 | 70 | if (prefix.equals("")) { 71 | prefixPath = null; 72 | } else { 73 | prefixPath = prefix.split("/"); 74 | prefixWithSlash = prefix + "/"; 75 | } 76 | 77 | int len = list.size(); 78 | 79 | Map entries = new HashMap(); 80 | 81 | for (int i = 0; i < len; i++) { 82 | ResolveInfo info = list.get(i); 83 | CharSequence labelSeq = info.loadLabel(pm); 84 | String label = labelSeq != null 85 | ? labelSeq.toString() 86 | : info.activityInfo.name; 87 | 88 | if (prefixWithSlash.length() == 0 || label.startsWith(prefixWithSlash)) { 89 | 90 | String[] labelPath = label.split("/"); 91 | 92 | String nextLabel = prefixPath == null ? labelPath[0] : labelPath[prefixPath.length]; 93 | 94 | if ((prefixPath != null ? prefixPath.length : 0) == labelPath.length - 1) { 95 | addItem(myData, nextLabel, activityIntent( 96 | info.activityInfo.applicationInfo.packageName, 97 | info.activityInfo.name)); 98 | } else { 99 | if (entries.get(nextLabel) == null) { 100 | addItem(myData, nextLabel, browseIntent( 101 | prefix.equals("") ? nextLabel : prefix + "/" + nextLabel)); 102 | entries.put(nextLabel, true); 103 | } 104 | } 105 | } 106 | } 107 | 108 | Collections.sort(myData, sDisplayNameComparator); 109 | 110 | return myData; 111 | } 112 | 113 | private final static Comparator> sDisplayNameComparator = 114 | new Comparator>() { 115 | private final Collator collator = Collator.getInstance(); 116 | 117 | @Override 118 | public int compare(Map map1, Map map2) { 119 | return collator.compare(map1.get("title"), map2.get("title")); 120 | } 121 | }; 122 | 123 | protected Intent activityIntent(String pkg, String componentName) { 124 | Intent result = new Intent(); 125 | result.setClassName(pkg, componentName); 126 | return result; 127 | } 128 | 129 | protected Intent browseIntent(String path) { 130 | Intent result = new Intent(); 131 | result.setClass(this, ExtendedRecyclerViewDemos.class); 132 | result.putExtra("com.example.android.apis.Path", path); 133 | return result; 134 | } 135 | 136 | protected void addItem(List> data, String name, Intent intent) { 137 | Map temp = new HashMap(); 138 | temp.put("title", name); 139 | temp.put("intent", intent); 140 | data.add(temp); 141 | } 142 | 143 | @Override 144 | @SuppressWarnings("unchecked") 145 | protected void onListItemClick(ListView l, View v, int position, long id) { 146 | Map map = (Map)l.getItemAtPosition(position); 147 | 148 | Intent intent = (Intent) map.get("intent"); 149 | startActivity(intent); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/CustomSwipeRefreshGridLayoutActivity.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.GridLayoutManager; 7 | import android.view.Menu; 8 | import android.view.View; 9 | import android.widget.Toast; 10 | 11 | import com.leowong.extendedrecyclerview.ExtendedRecyclerView; 12 | import com.leowong.extendedrecyclerview.adapters.CommonAdapter; 13 | import com.leowong.extendedrecyclerview.adapters.LoadMoreAdapter; 14 | import com.leowong.extendedrecyclerview.decoration.GridSpacingItemDecoration; 15 | import com.leowong.extendedrecyclerview.models.ViewItem; 16 | 17 | import java.util.ArrayList; 18 | 19 | import cn.aixuetang.com.recyclerview.widgets.CustomRefreshLayout; 20 | import cn.aixuetang.com.recyclerview.widgets.adapters.GridAdapter; 21 | import in.srain.cube.views.ptr.PtrDefaultHandler; 22 | import in.srain.cube.views.ptr.PtrFrameLayout; 23 | import in.srain.cube.views.ptr.PtrHandler; 24 | import jp.wasabeef.recyclerview.animators.FadeInAnimator; 25 | 26 | 27 | public class CustomSwipeRefreshGridLayoutActivity extends AppCompatActivity implements LoadMoreAdapter.ILoadMoreCallback, PtrHandler { 28 | private ExtendedRecyclerView mRecycler; 29 | private GridAdapter mAdapter; 30 | int position = 0; 31 | private GridLayoutManager gridLayoutManager; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_custom_refresh_sample); 37 | 38 | ArrayList list = new ArrayList<>(); 39 | mAdapter = new GridAdapter(list); 40 | 41 | mRecycler = (ExtendedRecyclerView) findViewById(R.id.list); 42 | gridLayoutManager = new GridLayoutManager(this, 2); 43 | gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 44 | @Override 45 | public int getSpanSize(int position) { 46 | switch (mAdapter.getItemViewType(position)) { 47 | case LoadMoreAdapter.VIEW_TYPE_ITEM_LOAD_MORE: 48 | return gridLayoutManager.getSpanCount(); 49 | default: 50 | return 1; 51 | } 52 | } 53 | }); 54 | mRecycler.setLayoutManager(gridLayoutManager); 55 | Thread thread = new Thread(new Runnable() { 56 | @Override 57 | public void run() { 58 | try { 59 | Thread.sleep(2000); 60 | } catch (InterruptedException e) { 61 | e.printStackTrace(); 62 | } 63 | 64 | runOnUiThread(new Runnable() { 65 | @Override 66 | public void run() { 67 | mRecycler.setAdapter(mAdapter); 68 | } 69 | }); 70 | 71 | try { 72 | Thread.sleep(1000); 73 | } catch (InterruptedException e) { 74 | e.printStackTrace(); 75 | } 76 | 77 | runOnUiThread(new Runnable() { 78 | @Override 79 | public void run() { 80 | ArrayList data = new ArrayList<>(); 81 | for (int i = 0; i < mAdapter.getPageCount(); i++) { 82 | data.add(new ViewItem(0, "default str" + position++)); 83 | } 84 | mAdapter.addAll(data); 85 | } 86 | }); 87 | } 88 | }); 89 | thread.start(); 90 | mAdapter.setPageCount(20); 91 | mAdapter.setLoadMoreCallback(this); 92 | ((CustomRefreshLayout) mRecycler.getSwipeRefreshView()).setPtrHandler(this); 93 | FadeInAnimator fadeInAnimator = new FadeInAnimator(); 94 | mRecycler.setItemAnimator(fadeInAnimator); 95 | GridSpacingItemDecoration gridSpacingItemDecoration = new GridSpacingItemDecoration(2, 40, false); 96 | mRecycler.addItemDecoration(gridSpacingItemDecoration); 97 | } 98 | 99 | 100 | public void onRefresh() { 101 | Toast.makeText(this, "Refresh", Toast.LENGTH_LONG).show(); 102 | 103 | Handler handler = new Handler(); 104 | handler.postDelayed(new Runnable() { 105 | public void run() { 106 | position = 0; 107 | ArrayList data = new ArrayList<>(); 108 | for (int i = 0; i < mAdapter.getPageCount(); i++) { 109 | data.add(new ViewItem(0, "refresh str" + position++)); 110 | } 111 | mAdapter.replaceAll(data); 112 | } 113 | }, 2000); 114 | } 115 | 116 | @Override 117 | public boolean onCreateOptionsMenu(Menu menu) { 118 | return super.onCreateOptionsMenu(menu); 119 | } 120 | 121 | @Override 122 | public void loadMore(CommonAdapter.CommonViewHolder holder, int position) { 123 | Toast.makeText(this, "loadmore-->" + position, Toast.LENGTH_LONG).show(); 124 | final int index = position; 125 | Handler handler = new Handler(); 126 | handler.postDelayed(new Runnable() { 127 | public void run() { 128 | ArrayList data = new ArrayList<>(); 129 | 130 | for (int i = index; i < index + mAdapter.getPageCount(); i++) { 131 | if (i >= 100) 132 | break; 133 | data.add(new ViewItem(0, "loadmore str" + i)); 134 | } 135 | mAdapter.addAll(data); 136 | } 137 | }, 2000); 138 | } 139 | 140 | @Override 141 | public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) { 142 | return PtrDefaultHandler.checkContentCanBePulledDown(frame, content, header); 143 | } 144 | 145 | @Override 146 | public void onRefreshBegin(PtrFrameLayout frame) { 147 | onRefresh(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/GridLayoutActivity.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.GridLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.Menu; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.widget.Toast; 13 | 14 | import com.leowong.extendedrecyclerview.ExtendedRecyclerView; 15 | import com.leowong.extendedrecyclerview.RecyclerItemClickListener; 16 | import com.leowong.extendedrecyclerview.adapters.CommonAdapter; 17 | import com.leowong.extendedrecyclerview.adapters.LoadMoreAdapter; 18 | import com.leowong.extendedrecyclerview.decoration.GridSpacingItemDecoration; 19 | import com.leowong.extendedrecyclerview.models.ViewItem; 20 | 21 | import java.util.ArrayList; 22 | 23 | import cn.aixuetang.com.recyclerview.widgets.adapters.GridAdapter; 24 | import jp.wasabeef.recyclerview.animators.FadeInAnimator; 25 | 26 | public class GridLayoutActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, LoadMoreAdapter.ILoadMoreCallback { 27 | private ExtendedRecyclerView mRecycler; 28 | private GridAdapter mAdapter; 29 | int position = 0; 30 | private GridLayoutManager gridLayoutManager; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_vertical_sample); 36 | 37 | ArrayList list = new ArrayList<>(); 38 | mAdapter = new GridAdapter(list); 39 | 40 | mRecycler = (ExtendedRecyclerView) findViewById(R.id.list); 41 | gridLayoutManager = new GridLayoutManager(this, 2); 42 | gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 43 | @Override 44 | public int getSpanSize(int position) { 45 | switch (mAdapter.getItemViewType(position)) { 46 | case LoadMoreAdapter.VIEW_TYPE_ITEM_LOAD_MORE: 47 | return gridLayoutManager.getSpanCount(); 48 | default: 49 | return 1; 50 | } 51 | } 52 | }); 53 | mRecycler.setLayoutManager(gridLayoutManager); 54 | Thread thread = new Thread(new Runnable() { 55 | @Override 56 | public void run() { 57 | try { 58 | Thread.sleep(2000); 59 | } catch (InterruptedException e) { 60 | e.printStackTrace(); 61 | } 62 | 63 | runOnUiThread(new Runnable() { 64 | @Override 65 | public void run() { 66 | mRecycler.setAdapter(mAdapter); 67 | } 68 | }); 69 | 70 | try { 71 | Thread.sleep(1000); 72 | } catch (InterruptedException e) { 73 | e.printStackTrace(); 74 | } 75 | 76 | runOnUiThread(new Runnable() { 77 | @Override 78 | public void run() { 79 | ArrayList data = new ArrayList<>(); 80 | for (int i = 0; i < mAdapter.getPageCount(); i++) { 81 | data.add(new ViewItem(0, "default str" + position++)); 82 | } 83 | mAdapter.addAll(data); 84 | } 85 | }); 86 | } 87 | }); 88 | thread.start(); 89 | mAdapter.setPageCount(20); 90 | mAdapter.setLoadMoreCallback(this); 91 | ((SwipeRefreshLayout) mRecycler.getSwipeRefreshView()).setOnRefreshListener(this); 92 | FadeInAnimator fadeInAnimator = new FadeInAnimator(); 93 | mRecycler.setItemAnimator(fadeInAnimator); 94 | GridSpacingItemDecoration gridSpacingItemDecoration = new GridSpacingItemDecoration(2, 40, false); 95 | mRecycler.addItemDecoration(gridSpacingItemDecoration); 96 | mRecycler.setOnItemClickListener(new RecyclerItemClickListener.OnItemClickListener() { 97 | @Override 98 | public void onItemClick(View view, int position) { 99 | Toast.makeText(GridLayoutActivity.this, "tap " + position, Toast.LENGTH_SHORT).show(); 100 | } 101 | }); 102 | 103 | } 104 | 105 | 106 | @Override 107 | public void onRefresh() { 108 | Toast.makeText(this, "Refresh", Toast.LENGTH_LONG).show(); 109 | 110 | Handler handler = new Handler(); 111 | handler.postDelayed(new Runnable() { 112 | public void run() { 113 | position = 0; 114 | ArrayList data = new ArrayList<>(); 115 | for (int i = 0; i < mAdapter.getPageCount(); i++) { 116 | data.add(new ViewItem(0, "refresh str" + position++)); 117 | } 118 | mAdapter.replaceAll(data); 119 | } 120 | }, 2000); 121 | } 122 | 123 | @Override 124 | public boolean onCreateOptionsMenu(Menu menu) { 125 | return super.onCreateOptionsMenu(menu); 126 | } 127 | 128 | @Override 129 | public void loadMore(CommonAdapter.CommonViewHolder holder, int position) { 130 | Toast.makeText(this, "loadmore-->" + position, Toast.LENGTH_LONG).show(); 131 | final int index = position; 132 | Handler handler = new Handler(); 133 | handler.postDelayed(new Runnable() { 134 | public void run() { 135 | ArrayList data = new ArrayList<>(); 136 | 137 | for (int i = index; i < index + mAdapter.getPageCount(); i++) { 138 | if (i >= 100) 139 | break; 140 | data.add(new ViewItem(0, "loadmore str" + i)); 141 | } 142 | mAdapter.addAll(data); 143 | } 144 | }, 2000); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /extendedrecyclerview/src/main/java/com/leowong/extendedrecyclerview/ExtendedRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.leowong.extendedrecyclerview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.leowong.library.multistatelayout.MultiStateLayout; 11 | 12 | /** 13 | * User: wanglg 14 | * Date: 2016-01-13 15 | * Time: 14:34 16 | * FIXME 17 | */ 18 | public class ExtendedRecyclerView extends MultiStateLayout { 19 | 20 | protected boolean mClipToPadding; 21 | protected int mPadding; 22 | protected int mPaddingTop; 23 | protected int mPaddingBottom; 24 | protected int mPaddingLeft; 25 | protected int mPaddingRight; 26 | protected int mScrollbarStyle; 27 | private PullToRefreshHandler pullToRefreshHandler; 28 | 29 | 30 | /** 31 | * Pull-refresh View 32 | */ 33 | private ViewGroup mSwipeRefreshView; 34 | private RecyclerView mRecyclerView; 35 | 36 | public ExtendedRecyclerView(Context context, AttributeSet attrs) { 37 | super(context, attrs); 38 | initAttr(attrs); 39 | initRecyclerView(); 40 | initSwipeRefreshView(); 41 | } 42 | 43 | public ExtendedRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { 44 | super(context, attrs, defStyleAttr); 45 | initAttr(attrs); 46 | initRecyclerView(); 47 | initSwipeRefreshView(); 48 | } 49 | 50 | @Override 51 | protected int getDefaultContentLayoutId(AttributeSet attrs) { 52 | TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ExtendedRecyclerView); 53 | int layoutId = R.layout.layout_extendrecycler_content; 54 | if (a != null) { 55 | try { 56 | layoutId = a.getResourceId(R.styleable.ExtendedRecyclerView_recyclerSwipe, R.layout.layout_extendrecycler_content); 57 | } finally { 58 | a.recycle(); 59 | } 60 | } 61 | return layoutId; 62 | } 63 | 64 | private void initAttr(AttributeSet attrs) { 65 | TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ExtendedRecyclerView); 66 | if (a != null) { 67 | try { 68 | mClipToPadding = a.getBoolean(R.styleable.ExtendedRecyclerView_recyclerClipToPadding, false); 69 | mPadding = (int) a.getDimension(R.styleable.ExtendedRecyclerView_recyclerPadding, -1.0f); 70 | mPaddingTop = (int) a.getDimension(R.styleable.ExtendedRecyclerView_recyclerPaddingTop, 0.0f); 71 | mPaddingBottom = (int) a.getDimension(R.styleable.ExtendedRecyclerView_recyclerPaddingBottom, 0.0f); 72 | mPaddingLeft = (int) a.getDimension(R.styleable.ExtendedRecyclerView_recyclerPaddingLeft, 0.0f); 73 | mPaddingRight = (int) a.getDimension(R.styleable.ExtendedRecyclerView_recyclerPaddingRight, 0.0f); 74 | mScrollbarStyle = a.getInt(R.styleable.ExtendedRecyclerView_scrollbarStyle, -1); 75 | } finally { 76 | a.recycle(); 77 | } 78 | } 79 | 80 | } 81 | 82 | protected void initSwipeRefreshView() { 83 | mSwipeRefreshView = (ViewGroup) this.findViewById(R.id.swipe_layout); 84 | if (mSwipeRefreshView == null) { 85 | throw new IllegalStateException("ExtendedRecyclerView must have a Pull-refresh view "); 86 | } 87 | if (mSwipeRefreshView instanceof PullToRefreshHandler) { 88 | setPullToRefreshHandler((PullToRefreshHandler) mSwipeRefreshView); 89 | } 90 | } 91 | 92 | public RecyclerView getRecyclerView() { 93 | return mRecyclerView; 94 | } 95 | 96 | public void setPullToRefreshHandler(PullToRefreshHandler pullToRefresh) { 97 | this.pullToRefreshHandler = pullToRefresh; 98 | } 99 | 100 | public void setOnItemClickListener(RecyclerItemClickListener.OnItemClickListener onItemClickListener) { 101 | getRecyclerView().addOnItemTouchListener(new RecyclerItemClickListener(getContext(), onItemClickListener)); 102 | } 103 | 104 | public void setHasFixedSize(boolean hasFixedSize) { 105 | mRecyclerView.setHasFixedSize(hasFixedSize); 106 | } 107 | 108 | public ViewGroup getSwipeRefreshView() { 109 | return mSwipeRefreshView; 110 | } 111 | 112 | private void initRecyclerView() { 113 | 114 | View recyclerView = this.findViewById(android.R.id.list); 115 | 116 | if (recyclerView instanceof RecyclerView) 117 | mRecyclerView = (RecyclerView) recyclerView; 118 | else { 119 | throw new IllegalArgumentException("ExtendedRecyclerView works with a RecyclerView!"); 120 | } 121 | mRecyclerView.setClipToPadding(mClipToPadding); 122 | 123 | 124 | if (mPadding != -1.0f) { 125 | mRecyclerView.setPadding(mPadding, mPadding, mPadding, mPadding); 126 | } else { 127 | mRecyclerView.setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom); 128 | } 129 | 130 | if (mScrollbarStyle != -1) { 131 | mRecyclerView.setScrollBarStyle(mScrollbarStyle); 132 | } 133 | } 134 | 135 | /** 136 | * Set the layout manager to the recycler 137 | * 138 | * @param manager RecyclerView LayoutManager 139 | */ 140 | public void setLayoutManager(RecyclerView.LayoutManager manager) { 141 | mRecyclerView.setLayoutManager(manager); 142 | } 143 | 144 | /** 145 | * Set the adapter to the recycler 146 | * Automatically hide the progressbar 147 | * Set the refresh to false 148 | * If adapter is empty, then the emptyview is shown 149 | * 150 | * @param adapter RecyclerView adapter 151 | */ 152 | public void setAdapter(RecyclerView.Adapter adapter) { 153 | mRecyclerView.setAdapter(adapter); 154 | if (pullToRefreshHandler != null) { 155 | pullToRefreshHandler.swipeRefreshComplete(); 156 | } 157 | adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { 158 | @Override 159 | public void onItemRangeChanged(int positionStart, int itemCount) { 160 | super.onItemRangeChanged(positionStart, itemCount); 161 | update(); 162 | } 163 | 164 | @Override 165 | public void onItemRangeInserted(int positionStart, int itemCount) { 166 | super.onItemRangeInserted(positionStart, itemCount); 167 | update(); 168 | } 169 | 170 | @Override 171 | public void onItemRangeRemoved(int positionStart, int itemCount) { 172 | super.onItemRangeRemoved(positionStart, itemCount); 173 | update(); 174 | } 175 | 176 | @Override 177 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 178 | super.onItemRangeMoved(fromPosition, toPosition, itemCount); 179 | update(); 180 | } 181 | 182 | @Override 183 | public void onChanged() { 184 | super.onChanged(); 185 | update(); 186 | } 187 | 188 | private void update() { 189 | if (pullToRefreshHandler != null) { 190 | pullToRefreshHandler.swipeRefreshComplete(); 191 | } 192 | if (mRecyclerView.getAdapter().getItemCount() == 0) { 193 | switchState(State.STATE_EMPTY); 194 | } else { 195 | switchState(State.STATE_CONTENT); 196 | } 197 | } 198 | }); 199 | if (adapter.getItemCount() == 0) { 200 | switchState(State.STATE_EMPTY); 201 | } else { 202 | switchState(State.STATE_CONTENT); 203 | } 204 | } 205 | 206 | /** 207 | * Set the empty adapter to the recycler 208 | * show the progressbar 209 | * hide recycler view ,empty view 210 | * adapter data should be empty 211 | * 212 | * @param adapter RecyclerView adapter 213 | */ 214 | public void setProgressAdapter(RecyclerView.Adapter adapter) { 215 | mRecyclerView.setAdapter(adapter); 216 | switchState(State.STATE_PROGRESS); 217 | if (pullToRefreshHandler != null) { 218 | pullToRefreshHandler.swipeRefreshComplete(); 219 | } 220 | adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { 221 | @Override 222 | public void onItemRangeChanged(int positionStart, int itemCount) { 223 | super.onItemRangeChanged(positionStart, itemCount); 224 | update(); 225 | } 226 | 227 | @Override 228 | public void onItemRangeInserted(int positionStart, int itemCount) { 229 | super.onItemRangeInserted(positionStart, itemCount); 230 | update(); 231 | } 232 | 233 | @Override 234 | public void onItemRangeRemoved(int positionStart, int itemCount) { 235 | super.onItemRangeRemoved(positionStart, itemCount); 236 | update(); 237 | } 238 | 239 | @Override 240 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 241 | super.onItemRangeMoved(fromPosition, toPosition, itemCount); 242 | update(); 243 | } 244 | 245 | @Override 246 | public void onChanged() { 247 | super.onChanged(); 248 | update(); 249 | } 250 | 251 | private void update() { 252 | if (pullToRefreshHandler != null) { 253 | pullToRefreshHandler.swipeRefreshComplete(); 254 | } 255 | if (mRecyclerView.getAdapter().getItemCount() == 0) { 256 | switchState(State.STATE_EMPTY); 257 | } else { 258 | switchState(State.STATE_CONTENT); 259 | } 260 | } 261 | }); 262 | } 263 | 264 | public void setItemAnimator(RecyclerView.ItemAnimator animator) { 265 | mRecyclerView.setItemAnimator(animator); 266 | } 267 | 268 | public void addItemDecoration(RecyclerView.ItemDecoration itemDecoration) { 269 | mRecyclerView.addItemDecoration(itemDecoration); 270 | } 271 | 272 | public void addItemDecoration(RecyclerView.ItemDecoration itemDecoration, int index) { 273 | mRecyclerView.addItemDecoration(itemDecoration, index); 274 | } 275 | 276 | public void removeItemDecoration(RecyclerView.ItemDecoration itemDecoration) { 277 | mRecyclerView.removeItemDecoration(itemDecoration); 278 | } 279 | 280 | /** 281 | * refresh finish callback 282 | */ 283 | public interface PullToRefreshHandler { 284 | void swipeRefreshComplete(); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /app/src/main/java/cn/aixuetang/com/recyclerview/widgets/ProgressWheel.java: -------------------------------------------------------------------------------- 1 | package cn.aixuetang.com.recyclerview.widgets; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.Paint.Style; 8 | import android.graphics.RectF; 9 | import android.os.Parcel; 10 | import android.os.Parcelable; 11 | import android.os.SystemClock; 12 | import android.support.annotation.NonNull; 13 | import android.util.AttributeSet; 14 | import android.util.DisplayMetrics; 15 | import android.util.TypedValue; 16 | import android.view.View; 17 | 18 | import cn.aixuetang.com.recyclerview.R; 19 | 20 | 21 | /** 22 | * A Material style progress wheel, compatible up to 2.2. 23 | * Todd Davies' Progress Wheel https://github.com/Todd-Davies/ProgressWheel 24 | * 25 | * @author Nico Hormazábal 26 | *

27 | * Licensed under the Apache License 2.0 license see: 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | */ 30 | public class ProgressWheel extends View { 31 | private static final String TAG = ProgressWheel.class.getSimpleName(); 32 | 33 | /** 34 | * ********* 35 | * DEFAULTS * 36 | * ********** 37 | */ 38 | //Sizes (with defaults in DP) 39 | private int circleRadius = 28; 40 | private int barWidth = 4; 41 | private int rimWidth = 4; 42 | 43 | private final int barLength = 16; 44 | private final int barMaxLength = 270; 45 | 46 | private boolean fillRadius = false; 47 | 48 | private double timeStartGrowing = 0; 49 | private double barSpinCycleTime = 460; 50 | private float barExtraLength = 0; 51 | private boolean barGrowingFromFront = true; 52 | 53 | private long pausedTimeWithoutGrowing = 0; 54 | private final long pauseGrowingTime = 200; 55 | 56 | //Colors (with defaults) 57 | private int barColor = 0xAA000000; 58 | private int rimColor = 0x00FFFFFF; 59 | 60 | //Paints 61 | private Paint barPaint = new Paint(); 62 | private Paint rimPaint = new Paint(); 63 | 64 | //Rectangles 65 | private RectF circleBounds = new RectF(); 66 | 67 | //Animation 68 | //The amount of degrees per second 69 | private float spinSpeed = 230.0f; 70 | //private float spinSpeed = 120.0f; 71 | // The last time the spinner was animated 72 | private long lastTimeAnimated = 0; 73 | 74 | private boolean linearProgress; 75 | 76 | private float mProgress = 0.0f; 77 | private float mTargetProgress = 0.0f; 78 | private boolean isSpinning = false; 79 | 80 | private ProgressCallback callback; 81 | 82 | /** 83 | * The constructor for the ProgressWheel 84 | * 85 | * @param context 86 | * @param attrs 87 | */ 88 | public ProgressWheel(Context context, AttributeSet attrs) { 89 | super(context, attrs); 90 | 91 | parseAttributes(context.obtainStyledAttributes(attrs, 92 | R.styleable.ProgressWheel)); 93 | } 94 | 95 | /** 96 | * The constructor for the ProgressWheel 97 | * 98 | * @param context 99 | */ 100 | public ProgressWheel(Context context) { 101 | super(context); 102 | } 103 | 104 | //---------------------------------- 105 | //Setting up stuff 106 | //---------------------------------- 107 | 108 | @Override 109 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 110 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 111 | 112 | int viewWidth = circleRadius * 2 + this.getPaddingLeft() + this.getPaddingRight(); 113 | int viewHeight = circleRadius * 2 + this.getPaddingTop() + this.getPaddingBottom(); 114 | 115 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 116 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 117 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 118 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 119 | 120 | int width; 121 | int height; 122 | 123 | //Measure Width 124 | if (widthMode == MeasureSpec.EXACTLY) { 125 | //Must be this size 126 | width = widthSize; 127 | } else if (widthMode == MeasureSpec.AT_MOST) { 128 | //Can't be bigger than... 129 | width = Math.min(viewWidth, widthSize); 130 | } else { 131 | //Be whatever you want 132 | width = viewWidth; 133 | } 134 | 135 | //Measure Height 136 | if (heightMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.EXACTLY) { 137 | //Must be this size 138 | height = heightSize; 139 | } else if (heightMode == MeasureSpec.AT_MOST) { 140 | //Can't be bigger than... 141 | height = Math.min(viewHeight, heightSize); 142 | } else { 143 | //Be whatever you want 144 | height = viewHeight; 145 | } 146 | 147 | setMeasuredDimension(width, height); 148 | } 149 | 150 | /** 151 | * Use onSizeChanged instead of onAttachedToWindow to get the dimensions of the view, 152 | * because this method is called after measuring the dimensions of MATCH_PARENT & WRAP_CONTENT. 153 | * Use this dimensions to setup the bounds and paints. 154 | */ 155 | @Override 156 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 157 | super.onSizeChanged(w, h, oldw, oldh); 158 | 159 | setupBounds(w, h); 160 | setupPaints(); 161 | invalidate(); 162 | } 163 | 164 | /** 165 | * Set the properties of the paints we're using to 166 | * draw the progress wheel 167 | */ 168 | private void setupPaints() { 169 | barPaint.setColor(barColor); 170 | barPaint.setAntiAlias(true); 171 | barPaint.setStyle(Style.STROKE); 172 | barPaint.setStrokeWidth(barWidth); 173 | 174 | rimPaint.setColor(rimColor); 175 | rimPaint.setAntiAlias(true); 176 | rimPaint.setStyle(Style.STROKE); 177 | rimPaint.setStrokeWidth(rimWidth); 178 | } 179 | 180 | /** 181 | * Set the bounds of the component 182 | */ 183 | private void setupBounds(int layout_width, int layout_height) { 184 | int paddingTop = getPaddingTop(); 185 | int paddingBottom = getPaddingBottom(); 186 | int paddingLeft = getPaddingLeft(); 187 | int paddingRight = getPaddingRight(); 188 | 189 | if (!fillRadius) { 190 | // Width should equal to Height, find the min value to setup the circle 191 | int minValue = Math.min(layout_width - paddingLeft - paddingRight, 192 | layout_height - paddingBottom - paddingTop); 193 | 194 | int circleDiameter = Math.min(minValue, circleRadius * 2 - barWidth * 2); 195 | 196 | // Calc the Offset if needed for centering the wheel in the available space 197 | int xOffset = (layout_width - paddingLeft - paddingRight - circleDiameter) / 2 + paddingLeft; 198 | int yOffset = (layout_height - paddingTop - paddingBottom - circleDiameter) / 2 + paddingTop; 199 | 200 | circleBounds = new RectF(xOffset + barWidth, 201 | yOffset + barWidth, 202 | xOffset + circleDiameter - barWidth, 203 | yOffset + circleDiameter - barWidth); 204 | } else { 205 | circleBounds = new RectF(paddingLeft + barWidth, 206 | paddingTop + barWidth, 207 | layout_width - paddingRight - barWidth, 208 | layout_height - paddingBottom - barWidth); 209 | } 210 | } 211 | 212 | /** 213 | * Parse the attributes passed to the view from the XML 214 | * 215 | * @param a the attributes to parse 216 | */ 217 | private void parseAttributes(TypedArray a) { 218 | // We transform the default values from DIP to pixels 219 | DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); 220 | barWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, barWidth, metrics); 221 | rimWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rimWidth, metrics); 222 | circleRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleRadius, metrics); 223 | 224 | circleRadius = (int) a.getDimension(R.styleable.ProgressWheel_matProg_circleRadius, circleRadius); 225 | 226 | fillRadius = a.getBoolean(R.styleable.ProgressWheel_matProg_fillRadius, false); 227 | 228 | barWidth = (int) a.getDimension(R.styleable.ProgressWheel_matProg_barWidth, barWidth); 229 | 230 | rimWidth = (int) a.getDimension(R.styleable.ProgressWheel_matProg_rimWidth, rimWidth); 231 | 232 | float baseSpinSpeed = a.getFloat(R.styleable.ProgressWheel_matProg_spinSpeed, spinSpeed / 360.0f); 233 | spinSpeed = baseSpinSpeed * 360; 234 | 235 | barSpinCycleTime = a.getInt(R.styleable.ProgressWheel_matProg_barSpinCycleTime, (int) barSpinCycleTime); 236 | 237 | barColor = a.getColor(R.styleable.ProgressWheel_matProg_barColor, barColor); 238 | 239 | rimColor = a.getColor(R.styleable.ProgressWheel_matProg_rimColor, rimColor); 240 | 241 | linearProgress = a.getBoolean(R.styleable.ProgressWheel_matProg_linearProgress, false); 242 | 243 | if (a.getBoolean(R.styleable.ProgressWheel_matProg_progressIndeterminate, false)) { 244 | spin(); 245 | } 246 | 247 | // Recycle 248 | a.recycle(); 249 | } 250 | 251 | public void setCallback(ProgressCallback progressCallback) { 252 | callback = progressCallback; 253 | 254 | if (!isSpinning) { 255 | runCallback(); 256 | } 257 | } 258 | 259 | //---------------------------------- 260 | //Animation stuff 261 | //---------------------------------- 262 | 263 | protected void onDraw(Canvas canvas) { 264 | super.onDraw(canvas); 265 | 266 | canvas.drawArc(circleBounds, 360, 360, false, rimPaint); 267 | 268 | boolean mustInvalidate = false; 269 | 270 | if (isSpinning) { 271 | //Draw the spinning bar 272 | mustInvalidate = true; 273 | 274 | long deltaTime = (SystemClock.uptimeMillis() - lastTimeAnimated); 275 | float deltaNormalized = deltaTime * spinSpeed / 1000.0f; 276 | 277 | updateBarLength(deltaTime); 278 | 279 | mProgress += deltaNormalized; 280 | if (mProgress > 360) { 281 | mProgress -= 360f; 282 | 283 | // A full turn has been completed 284 | // we run the callback with -1 in case we want to 285 | // do something, like changing the color 286 | runCallback(-1.0f); 287 | } 288 | lastTimeAnimated = SystemClock.uptimeMillis(); 289 | 290 | float from = mProgress - 90; 291 | float length = barLength + barExtraLength; 292 | 293 | if (isInEditMode()) { 294 | from = 0; 295 | length = 135; 296 | } 297 | 298 | canvas.drawArc(circleBounds, from, length, false, 299 | barPaint); 300 | } else { 301 | float oldProgress = mProgress; 302 | 303 | if (mProgress != mTargetProgress) { 304 | //We smoothly increase the progress bar 305 | mustInvalidate = true; 306 | 307 | float deltaTime = (float) (SystemClock.uptimeMillis() - lastTimeAnimated) / 1000; 308 | float deltaNormalized = deltaTime * spinSpeed; 309 | 310 | mProgress = Math.min(mProgress + deltaNormalized, mTargetProgress); 311 | lastTimeAnimated = SystemClock.uptimeMillis(); 312 | } 313 | 314 | if (oldProgress != mProgress) { 315 | runCallback(); 316 | } 317 | 318 | float offset = 0.0f; 319 | float progress = mProgress; 320 | if (!linearProgress) { 321 | float factor = 2.0f; 322 | offset = (float) (1.0f - Math.pow(1.0f - mProgress / 360.0f, 2.0f * factor)) * 360.0f; 323 | progress = (float) (1.0f - Math.pow(1.0f - mProgress / 360.0f, factor)) * 360.0f; 324 | } 325 | 326 | if (isInEditMode()) { 327 | progress = 360; 328 | } 329 | 330 | canvas.drawArc(circleBounds, offset - 90, progress, false, barPaint); 331 | } 332 | 333 | if (mustInvalidate) { 334 | invalidate(); 335 | } 336 | } 337 | 338 | @Override 339 | protected void onVisibilityChanged(@NonNull View changedView, int visibility) { 340 | super.onVisibilityChanged(changedView, visibility); 341 | 342 | if (visibility == VISIBLE) { 343 | lastTimeAnimated = SystemClock.uptimeMillis(); 344 | } 345 | } 346 | 347 | private void updateBarLength(long deltaTimeInMilliSeconds) { 348 | if (pausedTimeWithoutGrowing >= pauseGrowingTime) { 349 | timeStartGrowing += deltaTimeInMilliSeconds; 350 | 351 | if (timeStartGrowing > barSpinCycleTime) { 352 | // We completed a size change cycle 353 | // (growing or shrinking) 354 | timeStartGrowing -= barSpinCycleTime; 355 | //if(barGrowingFromFront) { 356 | pausedTimeWithoutGrowing = 0; 357 | //} 358 | barGrowingFromFront = !barGrowingFromFront; 359 | } 360 | 361 | float distance = (float) Math.cos((timeStartGrowing / barSpinCycleTime + 1) * Math.PI) / 2 + 0.5f; 362 | float destLength = (barMaxLength - barLength); 363 | 364 | if (barGrowingFromFront) { 365 | barExtraLength = distance * destLength; 366 | } else { 367 | float newLength = destLength * (1 - distance); 368 | mProgress += (barExtraLength - newLength); 369 | barExtraLength = newLength; 370 | } 371 | } else { 372 | pausedTimeWithoutGrowing += deltaTimeInMilliSeconds; 373 | } 374 | } 375 | 376 | /** 377 | * Check if the wheel is currently spinning 378 | */ 379 | 380 | public boolean isSpinning() { 381 | return isSpinning; 382 | } 383 | 384 | /** 385 | * Reset the count (in increment mode) 386 | */ 387 | public void resetCount() { 388 | mProgress = 0.0f; 389 | mTargetProgress = 0.0f; 390 | invalidate(); 391 | } 392 | 393 | /** 394 | * Turn off spin mode 395 | */ 396 | public void stopSpinning() { 397 | isSpinning = false; 398 | mProgress = 0.0f; 399 | mTargetProgress = 0.0f; 400 | invalidate(); 401 | } 402 | 403 | /** 404 | * Puts the view on spin mode 405 | */ 406 | public void spin() { 407 | lastTimeAnimated = SystemClock.uptimeMillis(); 408 | isSpinning = true; 409 | invalidate(); 410 | } 411 | 412 | private void runCallback(float value) { 413 | if (callback != null) { 414 | callback.onProgressUpdate(value); 415 | } 416 | } 417 | 418 | private void runCallback() { 419 | if (callback != null) { 420 | float normalizedProgress = (float) Math.round(mProgress * 100 / 360.0f) / 100; 421 | callback.onProgressUpdate(normalizedProgress); 422 | } 423 | } 424 | 425 | /** 426 | * Set the progress to a specific value, 427 | * the bar will smoothly animate until that value 428 | * 429 | * @param progress the progress between 0 and 1 430 | */ 431 | public void setProgress(float progress) { 432 | if (isSpinning) { 433 | mProgress = 0.0f; 434 | isSpinning = false; 435 | 436 | runCallback(); 437 | } 438 | 439 | if (progress > 1.0f) { 440 | progress -= 1.0f; 441 | } else if (progress < 0) { 442 | progress = 0; 443 | } 444 | 445 | if (progress == mTargetProgress) { 446 | return; 447 | } 448 | 449 | // If we are currently in the right position 450 | // we set again the last time animated so the 451 | // animation starts smooth from here 452 | if (mProgress == mTargetProgress) { 453 | lastTimeAnimated = SystemClock.uptimeMillis(); 454 | } 455 | 456 | mTargetProgress = Math.min(progress * 360.0f, 360.0f); 457 | 458 | invalidate(); 459 | } 460 | 461 | /** 462 | * Set the progress to a specific value, 463 | * the bar will be set instantly to that value 464 | * 465 | * @param progress the progress between 0 and 1 466 | */ 467 | public void setInstantProgress(float progress) { 468 | if (isSpinning) { 469 | mProgress = 0.0f; 470 | isSpinning = false; 471 | } 472 | 473 | if (progress > 1.0f) { 474 | progress -= 1.0f; 475 | } else if (progress < 0) { 476 | progress = 0; 477 | } 478 | 479 | if (progress == mTargetProgress) { 480 | return; 481 | } 482 | 483 | mTargetProgress = Math.min(progress * 360.0f, 360.0f); 484 | mProgress = mTargetProgress; 485 | lastTimeAnimated = SystemClock.uptimeMillis(); 486 | invalidate(); 487 | } 488 | 489 | // Great way to save a view's state http://stackoverflow.com/a/7089687/1991053 490 | @Override 491 | public Parcelable onSaveInstanceState() { 492 | Parcelable superState = super.onSaveInstanceState(); 493 | 494 | WheelSavedState ss = new WheelSavedState(superState); 495 | 496 | // We save everything that can be changed at runtime 497 | ss.mProgress = this.mProgress; 498 | ss.mTargetProgress = this.mTargetProgress; 499 | ss.isSpinning = this.isSpinning; 500 | ss.spinSpeed = this.spinSpeed; 501 | ss.barWidth = this.barWidth; 502 | ss.barColor = this.barColor; 503 | ss.rimWidth = this.rimWidth; 504 | ss.rimColor = this.rimColor; 505 | ss.circleRadius = this.circleRadius; 506 | ss.linearProgress = this.linearProgress; 507 | ss.fillRadius = this.fillRadius; 508 | 509 | return ss; 510 | } 511 | 512 | @Override 513 | public void onRestoreInstanceState(Parcelable state) { 514 | if (!(state instanceof WheelSavedState)) { 515 | super.onRestoreInstanceState(state); 516 | return; 517 | } 518 | 519 | WheelSavedState ss = (WheelSavedState) state; 520 | super.onRestoreInstanceState(ss.getSuperState()); 521 | 522 | this.mProgress = ss.mProgress; 523 | this.mTargetProgress = ss.mTargetProgress; 524 | this.isSpinning = ss.isSpinning; 525 | this.spinSpeed = ss.spinSpeed; 526 | this.barWidth = ss.barWidth; 527 | this.barColor = ss.barColor; 528 | this.rimWidth = ss.rimWidth; 529 | this.rimColor = ss.rimColor; 530 | this.circleRadius = ss.circleRadius; 531 | this.linearProgress = ss.linearProgress; 532 | this.fillRadius = ss.fillRadius; 533 | 534 | this.lastTimeAnimated = SystemClock.uptimeMillis(); 535 | } 536 | 537 | //---------------------------------- 538 | //Getters + setters 539 | //---------------------------------- 540 | 541 | /** 542 | * @return the current progress between 0.0 and 1.0, 543 | * if the wheel is indeterminate, then the result is -1 544 | */ 545 | public float getProgress() { 546 | return isSpinning ? -1 : mProgress / 360.0f; 547 | } 548 | 549 | /** 550 | * Sets the determinate progress mode 551 | * 552 | * @param isLinear if the progress should increase linearly 553 | */ 554 | public void setLinearProgress(boolean isLinear) { 555 | linearProgress = isLinear; 556 | if (!isSpinning) { 557 | invalidate(); 558 | } 559 | } 560 | 561 | /** 562 | * @return the radius of the wheel in pixels 563 | */ 564 | public int getCircleRadius() { 565 | return circleRadius; 566 | } 567 | 568 | /** 569 | * Sets the radius of the wheel 570 | * 571 | * @param circleRadius the expected radius, in pixels 572 | */ 573 | public void setCircleRadius(int circleRadius) { 574 | this.circleRadius = circleRadius; 575 | if (!isSpinning) { 576 | invalidate(); 577 | } 578 | } 579 | 580 | /** 581 | * @return the width of the spinning bar 582 | */ 583 | public int getBarWidth() { 584 | return barWidth; 585 | } 586 | 587 | /** 588 | * Sets the width of the spinning bar 589 | * 590 | * @param barWidth the spinning bar width in pixels 591 | */ 592 | public void setBarWidth(int barWidth) { 593 | this.barWidth = barWidth; 594 | if (!isSpinning) { 595 | invalidate(); 596 | } 597 | } 598 | 599 | /** 600 | * @return the color of the spinning bar 601 | */ 602 | public int getBarColor() { 603 | return barColor; 604 | } 605 | 606 | /** 607 | * Sets the color of the spinning bar 608 | * 609 | * @param barColor The spinning bar color 610 | */ 611 | public void setBarColor(int barColor) { 612 | this.barColor = barColor; 613 | setupPaints(); 614 | if (!isSpinning) { 615 | invalidate(); 616 | } 617 | } 618 | 619 | /** 620 | * @return the color of the wheel's contour 621 | */ 622 | public int getRimColor() { 623 | return rimColor; 624 | } 625 | 626 | /** 627 | * Sets the color of the wheel's contour 628 | * 629 | * @param rimColor the color for the wheel 630 | */ 631 | public void setRimColor(int rimColor) { 632 | this.rimColor = rimColor; 633 | setupPaints(); 634 | if (!isSpinning) { 635 | invalidate(); 636 | } 637 | } 638 | 639 | /** 640 | * @return the base spinning speed, in full circle turns per second 641 | * (1.0 equals on full turn in one second), this value also is applied for 642 | * the smoothness when setting a progress 643 | */ 644 | public float getSpinSpeed() { 645 | return spinSpeed / 360.0f; 646 | } 647 | 648 | /** 649 | * Sets the base spinning speed, in full circle turns per second 650 | * (1.0 equals on full turn in one second), this value also is applied for 651 | * the smoothness when setting a progress 652 | * 653 | * @param spinSpeed the desired base speed in full turns per second 654 | */ 655 | public void setSpinSpeed(float spinSpeed) { 656 | this.spinSpeed = spinSpeed * 360.0f; 657 | } 658 | 659 | /** 660 | * @return the width of the wheel's contour in pixels 661 | */ 662 | public int getRimWidth() { 663 | return rimWidth; 664 | } 665 | 666 | /** 667 | * Sets the width of the wheel's contour 668 | * 669 | * @param rimWidth the width in pixels 670 | */ 671 | public void setRimWidth(int rimWidth) { 672 | this.rimWidth = rimWidth; 673 | if (!isSpinning) { 674 | invalidate(); 675 | } 676 | } 677 | 678 | static class WheelSavedState extends BaseSavedState { 679 | float mProgress; 680 | float mTargetProgress; 681 | boolean isSpinning; 682 | float spinSpeed; 683 | int barWidth; 684 | int barColor; 685 | int rimWidth; 686 | int rimColor; 687 | int circleRadius; 688 | boolean linearProgress; 689 | boolean fillRadius; 690 | 691 | WheelSavedState(Parcelable superState) { 692 | super(superState); 693 | } 694 | 695 | private WheelSavedState(Parcel in) { 696 | super(in); 697 | this.mProgress = in.readFloat(); 698 | this.mTargetProgress = in.readFloat(); 699 | this.isSpinning = in.readByte() != 0; 700 | this.spinSpeed = in.readFloat(); 701 | this.barWidth = in.readInt(); 702 | this.barColor = in.readInt(); 703 | this.rimWidth = in.readInt(); 704 | this.rimColor = in.readInt(); 705 | this.circleRadius = in.readInt(); 706 | this.linearProgress = in.readByte() != 0; 707 | this.fillRadius = in.readByte() != 0; 708 | } 709 | 710 | @Override 711 | public void writeToParcel(Parcel out, int flags) { 712 | super.writeToParcel(out, flags); 713 | out.writeFloat(this.mProgress); 714 | out.writeFloat(this.mTargetProgress); 715 | out.writeByte((byte) (isSpinning ? 1 : 0)); 716 | out.writeFloat(this.spinSpeed); 717 | out.writeInt(this.barWidth); 718 | out.writeInt(this.barColor); 719 | out.writeInt(this.rimWidth); 720 | out.writeInt(this.rimColor); 721 | out.writeInt(this.circleRadius); 722 | out.writeByte((byte) (linearProgress ? 1 : 0)); 723 | out.writeByte((byte) (fillRadius ? 1 : 0)); 724 | } 725 | 726 | //required field that makes Parcelables from a Parcel 727 | public static final Creator CREATOR = 728 | new Creator() { 729 | public WheelSavedState createFromParcel(Parcel in) { 730 | return new WheelSavedState(in); 731 | } 732 | 733 | public WheelSavedState[] newArray(int size) { 734 | return new WheelSavedState[size]; 735 | } 736 | }; 737 | } 738 | 739 | public interface ProgressCallback { 740 | /** 741 | * Method to call when the progress reaches a value 742 | * in order to avoid float precision issues, the progress 743 | * is rounded to a float with two decimals. 744 | *

745 | * In indeterminate mode, the callback is called each time 746 | * the wheel completes an animation cycle, with, the progress value is -1.0f 747 | * 748 | * @param progress a double value between 0.00 and 1.00 both included 749 | */ 750 | public void onProgressUpdate(float progress); 751 | } 752 | } 753 | --------------------------------------------------------------------------------