├── Sample ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── layout │ │ │ │ ├── activity_main.xml │ │ │ │ ├── item_custom_stick.xml │ │ │ │ ├── activity_custom_stick.xml │ │ │ │ ├── activity_custom.xml │ │ │ │ └── item_custom.xml │ │ │ └── values-w820dp │ │ │ │ └── dimens.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── zly │ │ │ │ └── www │ │ │ │ └── simple │ │ │ │ ├── customdecoration │ │ │ │ ├── CityBean.java │ │ │ │ ├── CustomDecorationAdapter.java │ │ │ │ └── CustomDecorationActivity.java │ │ │ │ ├── AppApplication.java │ │ │ │ ├── adapter │ │ │ │ ├── CustomAdapter.java │ │ │ │ └── CustomDecorationAdapter.java │ │ │ │ ├── DisplayUtil.java │ │ │ │ ├── customall │ │ │ │ └── CustomAllActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── customheader │ │ │ │ └── CustomHeaderActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── zly │ │ │ └── www │ │ │ └── simple │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── zly │ │ └── www │ │ └── simple │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── ErvLibrary ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── ids.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── attrs.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── cube_ptr_string.xml │ │ │ │ └── cube_ptr_attrs.xml │ │ │ ├── drawable-xxhdpi │ │ │ │ └── icon_erv_emply.jpg │ │ │ ├── drawable-xhdpi │ │ │ │ └── ptr_rotate_arrow.png │ │ │ └── layout │ │ │ │ ├── cube_ptr_simple_loading.xml │ │ │ │ ├── erv_default_empty.xml │ │ │ │ ├── erv_default_footer.xml │ │ │ │ ├── erv_layout.xml │ │ │ │ └── cube_ptr_classic_default_header.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── zly │ │ │ │ └── www │ │ │ │ └── easyrecyclerview │ │ │ │ ├── listener │ │ │ │ ├── OnLoadListener.java │ │ │ │ ├── OnRefreshListener.java │ │ │ │ └── ItemClickSupport.java │ │ │ │ ├── adapter │ │ │ │ ├── viewholder │ │ │ │ │ ├── FooterViewHolder.java │ │ │ │ │ └── BaseViewHolder.java │ │ │ │ ├── MultipleAdapter.java │ │ │ │ └── CommonAdapter.java │ │ │ │ ├── ptrlib │ │ │ │ ├── PtrHandler.java │ │ │ │ ├── PtrUIHandler.java │ │ │ │ ├── PtrDefaultHandler.java │ │ │ │ ├── PtrUIHandlerHook.java │ │ │ │ ├── PtrClassicFrameLayout.java │ │ │ │ ├── util │ │ │ │ │ ├── PtrLocalDisplay.java │ │ │ │ │ └── PtrCLog.java │ │ │ │ ├── header │ │ │ │ │ ├── StoreHouseBarItem.java │ │ │ │ │ ├── MaterialHeader.java │ │ │ │ │ ├── StoreHouseHeader.java │ │ │ │ │ ├── StoreHousePath.java │ │ │ │ │ └── MaterialProgressDrawable.java │ │ │ │ ├── indicator │ │ │ │ │ ├── PtrTensionIndicator.java │ │ │ │ │ └── PtrIndicator.java │ │ │ │ ├── PtrUIHandlerHolder.java │ │ │ │ └── PtrClassicDefaultHeader.java │ │ │ │ ├── footer │ │ │ │ ├── ErvLoadUIHandle.java │ │ │ │ └── ErvDefaultFooter.java │ │ │ │ ├── utils │ │ │ │ ├── DisplayUtil.java │ │ │ │ └── LayoutManagerUtil.java │ │ │ │ ├── EasyDefRecyclerView.java │ │ │ │ ├── decoration │ │ │ │ └── StickItemDecoration.java │ │ │ │ └── EasyRecyclerView.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── zly │ │ │ └── www │ │ │ └── easyrecyclerview │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── zly │ │ └── www │ │ └── easyrecyclerview │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── gradlew └── README.md /Sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ErvLibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':ErvLibrary', ':Sample' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketZLY/EasyRecyclerView/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketZLY/EasyRecyclerView/HEAD/Sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketZLY/EasyRecyclerView/HEAD/Sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketZLY/EasyRecyclerView/HEAD/Sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketZLY/EasyRecyclerView/HEAD/Sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketZLY/EasyRecyclerView/HEAD/Sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/drawable-xxhdpi/icon_erv_emply.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketZLY/EasyRecyclerView/HEAD/ErvLibrary/src/main/res/drawable-xxhdpi/icon_erv_emply.jpg -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/drawable-xhdpi/ptr_rotate_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketZLY/EasyRecyclerView/HEAD/ErvLibrary/src/main/res/drawable-xhdpi/ptr_rotate_arrow.png -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | EasyRecyclerView 3 | 4 | 加载中... 5 | 暂无更多数据 6 | 7 | -------------------------------------------------------------------------------- /Sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /Sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /Sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Simple 3 | 4 | 5 | 定制头部 6 | 定制全部 7 | 定制装饰 8 | 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/listener/OnLoadListener.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.listener; 2 | 3 | /** 4 | * 加载更多 5 | * Created by zly on 2016/10/27 0027. 6 | */ 7 | 8 | public interface OnLoadListener { 9 | 10 | void onLoadListener(); 11 | } 12 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/listener/OnRefreshListener.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.listener; 2 | 3 | /** 4 | * 刷新 5 | * Created by zly on 2016/10/24 0024. 6 | */ 7 | 8 | public interface OnRefreshListener { 9 | 10 | void onRefreshListener(); 11 | } 12 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/src/main/res/layout/item_custom_stick.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/layout/cube_ptr_simple_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/layout/erv_default_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/customdecoration/CityBean.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple.customdecoration; 2 | 3 | /** 4 | * Created by zly on 2016/11/28 0028. 5 | */ 6 | 7 | public class CityBean { 8 | 9 | public String tag; 10 | public String name; 11 | 12 | public CityBean(String tag, String name) { 13 | this.tag = tag; 14 | this.name = name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sample/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/adapter/viewholder/FooterViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.adapter.viewholder; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by zly on 2016/10/31 0031. 7 | */ 8 | 9 | public class FooterViewHolder extends BaseViewHolder { 10 | 11 | public FooterViewHolder(View itemView) { 12 | super(itemView); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/layout/erv_default_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /Sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/adapter/viewholder/BaseViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.adapter.viewholder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by zly on 2016/10/31 0031. 8 | */ 9 | 10 | public class BaseViewHolder extends RecyclerView.ViewHolder { 11 | 12 | public BaseViewHolder(View itemView) { 13 | super(itemView); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sample/src/test/java/com/zly/www/simple/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /Sample/src/main/res/layout/activity_custom_stick.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ErvLibrary/src/test/java/com/zly/www/easyrecyclerview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/AppApplication.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * Created by zly on 2016/12/2 0002. 7 | */ 8 | 9 | public class AppApplication extends Application { 10 | 11 | private static AppApplication mApplication; 12 | 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | mApplication = this; 17 | } 18 | 19 | public static AppApplication getInstance(){ 20 | return mApplication; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sample/src/main/res/layout/activity_custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/PtrHandler.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib; 2 | 3 | import android.view.View; 4 | 5 | public interface PtrHandler { 6 | 7 | /** 8 | * Check can do refresh or not. For example the content is empty or the first child is in view. 9 | * 10 | */ 11 | public boolean checkCanDoRefresh(final PtrFrameLayout frame, final View content, final View header); 12 | 13 | /** 14 | * When refresh begin 15 | * 16 | * @param frame 17 | */ 18 | public void onRefreshBegin(final PtrFrameLayout frame); 19 | } -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/values/cube_ptr_string.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 下拉刷新 5 | 下拉刷新 6 | 释放刷新 7 | 刷新中... 8 | 刷新完成. 9 | 10 | 最后更新时间:  11 |  秒前 12 |  分钟前 13 |  小时前 14 | 15 | -------------------------------------------------------------------------------- /Sample/src/main/res/layout/item_custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /Sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /ErvLibrary/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/footer/ErvLoadUIHandle.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.footer; 2 | 3 | import com.zly.www.easyrecyclerview.listener.OnLoadListener; 4 | 5 | /** 6 | * 刷新ui操作 7 | * Created by zly on 2016/10/24 0024. 8 | */ 9 | 10 | public interface ErvLoadUIHandle { 11 | 12 | /** 13 | * 允许加载更多 14 | */ 15 | int LOAD = 1; 16 | 17 | /** 18 | * 暂无更多数据 19 | */ 20 | int NOMORE = 2; 21 | 22 | /** 23 | * 加载失败 24 | */ 25 | int LOADFAIL = 3; 26 | 27 | /** 28 | * @return 获取底部当前状态 29 | */ 30 | int getState(); 31 | 32 | void onLoading(); 33 | 34 | void onNoMore(); 35 | 36 | void onLoadFail(OnLoadListener listener); 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | ### Android template 10 | # Built application files 11 | *.apk 12 | *.ap_ 13 | 14 | # Files for the ART/Dalvik VM 15 | *.dex 16 | 17 | # Java class files 18 | *.class 19 | 20 | # Generated files 21 | bin/ 22 | gen/ 23 | out/ 24 | 25 | # Gradle files 26 | .gradle/ 27 | build/ 28 | 29 | # Local configuration file (sdk path, etc) 30 | local.properties 31 | 32 | # Proguard folder generated by Eclipse 33 | proguard/ 34 | 35 | # Log Files 36 | *.log 37 | 38 | # Android Studio Navigation editor temp files 39 | .navigation/ 40 | 41 | # Android Studio captures folder 42 | captures/ 43 | 44 | 45 | 46 | # Keystore files 47 | *.jks 48 | 49 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/adapter/MultipleAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.adapter; 2 | 3 | import com.zly.www.easyrecyclerview.adapter.viewholder.BaseViewHolder; 4 | 5 | /** 6 | * 多条目adapter 7 | * Created by zly on 2016/10/31 0031. 8 | */ 9 | 10 | public abstract class MultipleAdapter extends CommonAdapter { 11 | 12 | @Override 13 | public int getItemViewType(int position) { 14 | if(getFooter() != null && getItemCount() - 1 == position){ 15 | return TYPE_FOOTER; 16 | } 17 | 18 | if(customItemViewType(position) == TYPE_FOOTER){ 19 | throw new RuntimeException("customItemViewType(int position) 返回值不能为"+TYPE_FOOTER); 20 | } 21 | return customItemViewType(position); 22 | } 23 | 24 | public abstract int customItemViewType(int position); 25 | } 26 | -------------------------------------------------------------------------------- /Sample/src/androidTest/java/com/zly/www/simple/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.zly.www.simple", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ErvLibrary/src/androidTest/java/com/zly/www/easyrecyclerview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.zly.www.easyrecyclerview", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/layout/erv_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/PtrUIHandler.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib; 2 | 3 | 4 | import com.zly.www.easyrecyclerview.ptrlib.indicator.PtrIndicator; 5 | 6 | /** 7 | * 8 | */ 9 | public interface PtrUIHandler { 10 | 11 | /** 12 | * When the content view has reached top and refresh has been completed, view will be reset. 13 | * 14 | * @param frame 15 | */ 16 | public void onUIReset(PtrFrameLayout frame); 17 | 18 | /** 19 | * prepare for loading 20 | * 21 | * @param frame 22 | */ 23 | public void onUIRefreshPrepare(PtrFrameLayout frame); 24 | 25 | /** 26 | * perform refreshing UI 27 | */ 28 | public void onUIRefreshBegin(PtrFrameLayout frame); 29 | 30 | /** 31 | * perform UI after refresh 32 | */ 33 | public void onUIRefreshComplete(PtrFrameLayout frame); 34 | 35 | public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator); 36 | } 37 | -------------------------------------------------------------------------------- /Sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.zly.www.simple" 9 | minSdkVersion 15 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(include: ['*.jar'], dir: 'libs') 27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 28 | exclude group: 'com.android.support', module: 'support-annotations' 29 | }) 30 | compile 'com.android.support:appcompat-v7:24.2.1' 31 | testCompile 'junit:junit:4.12' 32 | compile project(':ErvLibrary') 33 | compile 'com.jakewharton:butterknife:8.4.0' 34 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' 35 | } 36 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/adapter/CustomAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple.adapter; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | import android.widget.TextView; 6 | 7 | import com.zly.www.easyrecyclerview.adapter.CommonAdapter; 8 | import com.zly.www.easyrecyclerview.adapter.viewholder.BaseViewHolder; 9 | import com.zly.www.simple.R; 10 | 11 | import butterknife.BindView; 12 | import butterknife.ButterKnife; 13 | 14 | /** 15 | * Created by zly on 2016/10/13 0013. 16 | */ 17 | 18 | public class CustomAdapter extends CommonAdapter { 19 | 20 | @Override 21 | public ViewHolder createCustomViewHolder(ViewGroup parent, int viewType) { 22 | return new ViewHolder(inflateView(R.layout.item_custom, parent)); 23 | } 24 | 25 | @Override 26 | public void bindCustomViewHolder(ViewHolder holder, String s, int position) { 27 | holder.tv.setText(s); 28 | } 29 | 30 | class ViewHolder extends BaseViewHolder { 31 | @BindView(R.id.tv) 32 | TextView tv; 33 | 34 | ViewHolder(View itemView) { 35 | super(itemView); 36 | ButterKnife.bind(this,itemView); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/values/cube_ptr_attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/customdecoration/CustomDecorationAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple.customdecoration; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | import android.widget.TextView; 6 | 7 | import com.zly.www.easyrecyclerview.adapter.CommonAdapter; 8 | import com.zly.www.easyrecyclerview.adapter.viewholder.BaseViewHolder; 9 | import com.zly.www.simple.R; 10 | 11 | import butterknife.BindView; 12 | import butterknife.ButterKnife; 13 | 14 | /** 15 | * Created by zly on 2016/11/30 0030. 16 | */ 17 | 18 | public class CustomDecorationAdapter extends CommonAdapter { 19 | 20 | 21 | @Override 22 | public ViewHolder createCustomViewHolder(ViewGroup parent, int viewType) { 23 | return new ViewHolder(inflateView(R.layout.item_custom_stick,parent)); 24 | } 25 | 26 | @Override 27 | public void bindCustomViewHolder(ViewHolder holder, CityBean cityBean, int position) { 28 | holder.tv.setText(cityBean.name); 29 | } 30 | 31 | class ViewHolder extends BaseViewHolder { 32 | 33 | @BindView(R.id.tv) 34 | TextView tv; 35 | 36 | public ViewHolder(View itemView) { 37 | super(itemView); 38 | ButterKnife.bind(this,itemView); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/DisplayUtil.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple; 2 | 3 | /** 4 | * dp sp px转换 5 | * Created by zly on 2016/7/28 0028. 6 | */ 7 | public class DisplayUtil { 8 | /** 9 | * 将px值转换为dip或dp值,保证尺寸大小不变 10 | */ 11 | public static int px2dip(float pxValue) { 12 | final float scale = AppApplication.getInstance().getResources().getDisplayMetrics().density; 13 | return (int) (pxValue / scale + 0.5f); 14 | } 15 | 16 | /** 17 | * 将dip或dp值转换为px值,保证尺寸大小不变 18 | */ 19 | public static int dip2px(float dipValue) { 20 | final float scale = AppApplication.getInstance().getResources().getDisplayMetrics().density; 21 | return (int) (dipValue * scale + 0.5f); 22 | } 23 | 24 | /** 25 | * 将px值转换为sp值,保证文字大小不变 26 | */ 27 | public static int px2sp(float pxValue) { 28 | final float fontScale = AppApplication.getInstance().getResources().getDisplayMetrics().scaledDensity; 29 | return (int) (pxValue / fontScale + 0.5f); 30 | } 31 | 32 | /** 33 | * 将sp值转换为px值,保证文字大小不变 34 | */ 35 | public static int sp2px(float spValue) { 36 | final float fontScale = AppApplication.getInstance().getResources().getDisplayMetrics().scaledDensity; 37 | return (int) (spValue * fontScale + 0.5f); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/adapter/CustomDecorationAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple.adapter; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | import android.widget.TextView; 6 | 7 | import com.zly.www.easyrecyclerview.adapter.CommonAdapter; 8 | import com.zly.www.easyrecyclerview.adapter.viewholder.BaseViewHolder; 9 | import com.zly.www.simple.R; 10 | import com.zly.www.simple.customdecoration.CityBean; 11 | 12 | import butterknife.BindView; 13 | import butterknife.ButterKnife; 14 | 15 | /** 16 | * Created by zly on 2016/11/30 0030. 17 | */ 18 | 19 | public class CustomDecorationAdapter extends CommonAdapter { 20 | 21 | 22 | @Override 23 | public ViewHolder createCustomViewHolder(ViewGroup parent, int viewType) { 24 | return new ViewHolder(inflateView(R.layout.item_custom_stick,parent)); 25 | } 26 | 27 | @Override 28 | public void bindCustomViewHolder(ViewHolder holder, CityBean cityBean, int position) { 29 | holder.tv.setText(cityBean.name); 30 | } 31 | 32 | class ViewHolder extends BaseViewHolder { 33 | 34 | @BindView(R.id.tv) 35 | TextView tv; 36 | 37 | public ViewHolder(View itemView) { 38 | super(itemView); 39 | ButterKnife.bind(this,itemView); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/utils/DisplayUtil.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.utils; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * dp sp px转换 7 | * Created by zly on 2016/7/28 0028. 8 | */ 9 | public class DisplayUtil { 10 | /** 11 | * 将px值转换为dip或dp值,保证尺寸大小不变 12 | */ 13 | public static int px2dip(Context context, float pxValue) { 14 | final float scale = context.getResources().getDisplayMetrics().density; 15 | return (int) (pxValue / scale + 0.5f); 16 | } 17 | 18 | /** 19 | * 将dip或dp值转换为px值,保证尺寸大小不变 20 | */ 21 | public static int dip2px(Context context, float dipValue) { 22 | final float scale = context.getResources().getDisplayMetrics().density; 23 | return (int) (dipValue * scale + 0.5f); 24 | } 25 | 26 | /** 27 | * 将px值转换为sp值,保证文字大小不变 28 | */ 29 | public static int px2sp(Context context, float pxValue) { 30 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 31 | return (int) (pxValue / fontScale + 0.5f); 32 | } 33 | 34 | /** 35 | * 将sp值转换为px值,保证文字大小不变 36 | */ 37 | public static int sp2px(Context context, float spValue) { 38 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 39 | return (int) (spValue * fontScale + 0.5f); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ErvLibrary/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release'//添加 3 | 4 | android { 5 | compileSdkVersion 24 6 | buildToolsVersion "24.0.2" 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:24.2.1' 28 | testCompile 'junit:junit:4.12' 29 | compile 'com.android.support:recyclerview-v7:24.2.1' 30 | 31 | } 32 | 33 | 34 | //添加 35 | publish { 36 | userOrg = 'zhuliyuan'//bintray.com用户名 37 | groupId = 'com.yysauce'//jcenter上的路径 38 | artifactId = 'easyrecyclerview'//项目名称 39 | publishVersion = '1.0.0'//版本号 40 | desc = 'EasyRecyclerView'//描述,不重要 41 | website = 'https://github.com/zhuliyuan921112/EasyRecyclerView'//网站,不重要;尽量模拟github上的地址,例如我这样的;当然你有地址最好了 42 | } -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/PtrDefaultHandler.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib; 2 | 3 | import android.view.View; 4 | import android.widget.AbsListView; 5 | 6 | public abstract class PtrDefaultHandler implements PtrHandler { 7 | 8 | public static boolean canChildScrollUp(View view) { 9 | if (android.os.Build.VERSION.SDK_INT < 14) { 10 | if (view instanceof AbsListView) { 11 | final AbsListView absListView = (AbsListView) view; 12 | return absListView.getChildCount() > 0 13 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 14 | .getTop() < absListView.getPaddingTop()); 15 | } else { 16 | return view.getScrollY() > 0; 17 | } 18 | } else { 19 | return view.canScrollVertically(-1); 20 | } 21 | } 22 | 23 | /** 24 | * Default implement for check can perform pull to refresh 25 | * 26 | * @param frame 27 | * @param content 28 | * @param header 29 | * @return 30 | */ 31 | public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) { 32 | return !canChildScrollUp(content); 33 | } 34 | 35 | @Override 36 | public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) { 37 | return checkContentCanBePulledDown(frame, content, header); 38 | } 39 | } -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/PtrUIHandlerHook.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib; 2 | 3 | /** 4 | * Run a hook runnable, the runnable will run only once. 5 | * After the runnable is done, call resume to resume. 6 | * Once run, call takeover will directory call the resume action 7 | */ 8 | public abstract class PtrUIHandlerHook implements Runnable { 9 | 10 | private Runnable mResumeAction; 11 | private static final byte STATUS_PREPARE = 0; 12 | private static final byte STATUS_IN_HOOK = 1; 13 | private static final byte STATUS_RESUMED = 2; 14 | private byte mStatus = STATUS_PREPARE; 15 | 16 | public void takeOver() { 17 | takeOver(null); 18 | } 19 | 20 | public void takeOver(Runnable resumeAction) { 21 | if (resumeAction != null) { 22 | mResumeAction = resumeAction; 23 | } 24 | switch (mStatus) { 25 | case STATUS_PREPARE: 26 | mStatus = STATUS_IN_HOOK; 27 | run(); 28 | break; 29 | case STATUS_IN_HOOK: 30 | break; 31 | case STATUS_RESUMED: 32 | resume(); 33 | break; 34 | } 35 | } 36 | 37 | public void reset() { 38 | mStatus = STATUS_PREPARE; 39 | } 40 | 41 | public void resume() { 42 | if (mResumeAction != null) { 43 | mResumeAction.run(); 44 | } 45 | mStatus = STATUS_RESUMED; 46 | } 47 | 48 | /** 49 | * Hook should always have a resume action, which is hooked by this hook. 50 | * 51 | * @param runnable 52 | */ 53 | public void setResumeAction(Runnable runnable) { 54 | mResumeAction = runnable; 55 | } 56 | } -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/PtrClassicFrameLayout.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | public class PtrClassicFrameLayout extends PtrFrameLayout { 7 | 8 | private PtrClassicDefaultHeader mPtrClassicHeader; 9 | 10 | public PtrClassicFrameLayout(Context context) { 11 | super(context); 12 | initViews(); 13 | } 14 | 15 | public PtrClassicFrameLayout(Context context, AttributeSet attrs) { 16 | super(context, attrs); 17 | initViews(); 18 | } 19 | 20 | public PtrClassicFrameLayout(Context context, AttributeSet attrs, int defStyle) { 21 | super(context, attrs, defStyle); 22 | initViews(); 23 | } 24 | 25 | private void initViews() { 26 | mPtrClassicHeader = new PtrClassicDefaultHeader(getContext()); 27 | setHeaderView(mPtrClassicHeader); 28 | addPtrUIHandler(mPtrClassicHeader); 29 | } 30 | 31 | public PtrClassicDefaultHeader getHeader() { 32 | return mPtrClassicHeader; 33 | } 34 | 35 | /** 36 | * Specify the last update time by this key string 37 | * 38 | * @param key 39 | */ 40 | public void setLastUpdateTimeKey(String key) { 41 | if (mPtrClassicHeader != null) { 42 | mPtrClassicHeader.setLastUpdateTimeKey(key); 43 | } 44 | } 45 | 46 | /** 47 | * Using an object to specify the last update time. 48 | * 49 | * @param object 50 | */ 51 | public void setLastUpdateTimeRelateObject(Object object) { 52 | if (mPtrClassicHeader != null) { 53 | mPtrClassicHeader.setLastUpdateTimeRelateObject(object); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/util/PtrLocalDisplay.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib.util; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.view.View; 6 | import android.view.WindowManager; 7 | 8 | public class PtrLocalDisplay { 9 | 10 | public static int SCREEN_WIDTH_PIXELS; 11 | public static int SCREEN_HEIGHT_PIXELS; 12 | public static float SCREEN_DENSITY; 13 | public static int SCREEN_WIDTH_DP; 14 | public static int SCREEN_HEIGHT_DP; 15 | 16 | public static void init(Context context) { 17 | if (context == null) { 18 | return; 19 | } 20 | DisplayMetrics dm = new DisplayMetrics(); 21 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 22 | wm.getDefaultDisplay().getMetrics(dm); 23 | SCREEN_WIDTH_PIXELS = dm.widthPixels; 24 | SCREEN_HEIGHT_PIXELS = dm.heightPixels; 25 | SCREEN_DENSITY = dm.density; 26 | SCREEN_WIDTH_DP = (int) (SCREEN_WIDTH_PIXELS / dm.density); 27 | SCREEN_HEIGHT_DP = (int) (SCREEN_HEIGHT_PIXELS / dm.density); 28 | } 29 | 30 | public static int dp2px(float dp) { 31 | final float scale = SCREEN_DENSITY; 32 | return (int) (dp * scale + 0.5f); 33 | } 34 | 35 | public static int designedDP2px(float designedDp) { 36 | if (SCREEN_WIDTH_DP != 320) { 37 | designedDp = designedDp * SCREEN_WIDTH_DP / 320f; 38 | } 39 | return dp2px(designedDp); 40 | } 41 | 42 | public static void setPadding(final View view, float left, float top, float right, float bottom) { 43 | view.setPadding(designedDP2px(left), dp2px(top), designedDP2px(right), dp2px(bottom)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/EasyDefRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | 8 | import com.zly.www.easyrecyclerview.footer.ErvDefaultFooter; 9 | import com.zly.www.easyrecyclerview.ptrlib.PtrClassicDefaultHeader; 10 | 11 | /** 12 | * 默认底部空布局实现 13 | * Created by zly on 2016/11/1 0001. 14 | */ 15 | 16 | public class EasyDefRecyclerView extends EasyRecyclerView { 17 | 18 | 19 | private PtrClassicDefaultHeader mPtrClassicHeader; 20 | 21 | public EasyDefRecyclerView(Context context) { 22 | this(context, null); 23 | } 24 | 25 | public EasyDefRecyclerView(Context context, AttributeSet attrs) { 26 | this(context, attrs, 0); 27 | } 28 | 29 | public EasyDefRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { 30 | super(context, attrs, defStyleAttr); 31 | init(context); 32 | } 33 | 34 | private void init(Context context) { 35 | setHeaderView(mPtrClassicHeader = new PtrClassicDefaultHeader(context)); 36 | setFooterView(new ErvDefaultFooter(context)); 37 | setLayoutManager(new LinearLayoutManager(getContext())); 38 | setOnEmptyViewClick(new OnClickListener() { 39 | @Override 40 | public void onClick(View v) { 41 | autoRefresh(); 42 | } 43 | }); 44 | } 45 | 46 | public void setLastUpdateTimeKey(String key) { 47 | if (mPtrClassicHeader != null) { 48 | mPtrClassicHeader.setLastUpdateTimeKey(key); 49 | } 50 | } 51 | 52 | public void setLastUpdateTimeRelateObject(Object object) { 53 | if (mPtrClassicHeader != null) { 54 | mPtrClassicHeader.setLastUpdateTimeRelateObject(object); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/customall/CustomAllActivity.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple.customall; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import com.zly.www.easyrecyclerview.EasyDefRecyclerView; 8 | import com.zly.www.easyrecyclerview.listener.OnLoadListener; 9 | import com.zly.www.easyrecyclerview.listener.OnRefreshListener; 10 | import com.zly.www.simple.R; 11 | import com.zly.www.simple.adapter.CustomAdapter; 12 | 13 | import butterknife.BindView; 14 | import butterknife.ButterKnife; 15 | 16 | /** 17 | * 自定义头部和底部 18 | * Created by zly on 2016/11/28 0028. 19 | */ 20 | public class CustomAllActivity extends AppCompatActivity implements OnRefreshListener, OnLoadListener { 21 | 22 | @BindView(R.id.erv) 23 | EasyDefRecyclerView erv; 24 | 25 | private CustomAdapter mAdapter; 26 | private Handler handler = new Handler(); 27 | private boolean isFail = false; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_custom); 33 | ButterKnife.bind(this); 34 | 35 | erv.setAdapter(mAdapter = new CustomAdapter()); 36 | erv.setLastUpdateTimeRelateObject(this); 37 | erv.setOnRefreshListener(this); 38 | erv.setOnLoadListener(this); 39 | } 40 | 41 | @Override 42 | public void onRefreshListener() { 43 | handler.postDelayed(new Runnable() { 44 | @Override 45 | public void run() { 46 | for (int i = 0; i < 10; i++) { 47 | mAdapter.insert("最新数据",0); 48 | } 49 | } 50 | }, 3000); 51 | } 52 | 53 | @Override 54 | public void onLoadListener() { 55 | handler.postDelayed(new Runnable() { 56 | @Override 57 | public void run() { 58 | 59 | if (mAdapter.getItemCount() > 20 && !isFail) { 60 | isFail = true; 61 | erv.loadFail(); 62 | } else if (mAdapter.getItemCount() > 30) { 63 | erv.noMore(); 64 | } else { 65 | for (int i = 0; i < 10; i++) { 66 | mAdapter.add("更多数据"); 67 | } 68 | } 69 | } 70 | }, 3000); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/header/StoreHouseBarItem.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib.header; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.PointF; 6 | import android.view.animation.Animation; 7 | import android.view.animation.Transformation; 8 | 9 | import java.util.Random; 10 | 11 | /** 12 | * Created by srain on 11/6/14. 13 | */ 14 | public class StoreHouseBarItem extends Animation { 15 | 16 | public PointF midPoint; 17 | public float translationX; 18 | public int index; 19 | 20 | private final Paint mPaint = new Paint(); 21 | private float mFromAlpha = 1.0f; 22 | private float mToAlpha = 0.4f; 23 | private PointF mCStartPoint; 24 | private PointF mCEndPoint; 25 | 26 | public StoreHouseBarItem(int index, PointF start, PointF end, int color, int lineWidth) { 27 | this.index = index; 28 | 29 | midPoint = new PointF((start.x + end.x) / 2, (start.y + end.y) / 2); 30 | 31 | mCStartPoint = new PointF(start.x - midPoint.x, start.y - midPoint.y); 32 | mCEndPoint = new PointF(end.x - midPoint.x, end.y - midPoint.y); 33 | 34 | setColor(color); 35 | setLineWidth(lineWidth); 36 | mPaint.setAntiAlias(true); 37 | mPaint.setStyle(Paint.Style.STROKE); 38 | } 39 | 40 | public void setLineWidth(int width) { 41 | mPaint.setStrokeWidth(width); 42 | } 43 | 44 | public void setColor(int color) { 45 | mPaint.setColor(color); 46 | } 47 | 48 | public void resetPosition(int horizontalRandomness) { 49 | Random random = new Random(); 50 | int randomNumber = -random.nextInt(horizontalRandomness) + horizontalRandomness; 51 | translationX = randomNumber; 52 | } 53 | 54 | @Override 55 | protected void applyTransformation(float interpolatedTime, Transformation t) { 56 | float alpha = mFromAlpha; 57 | alpha = alpha + ((mToAlpha - alpha) * interpolatedTime); 58 | setAlpha(alpha); 59 | } 60 | 61 | public void start(float fromAlpha, float toAlpha) { 62 | mFromAlpha = fromAlpha; 63 | mToAlpha = toAlpha; 64 | super.start(); 65 | } 66 | 67 | public void setAlpha(float alpha) { 68 | mPaint.setAlpha((int) (alpha * 255)); 69 | } 70 | 71 | public void draw(Canvas canvas) { 72 | canvas.drawLine(mCStartPoint.x, mCStartPoint.y, mCEndPoint.x, mCEndPoint.y, mPaint); 73 | } 74 | } -------------------------------------------------------------------------------- /ErvLibrary/src/main/res/layout/cube_ptr_classic_default_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 17 | 18 | 24 | 25 | 32 | 33 | 34 | 40 | 41 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.GridLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.View; 9 | 10 | import com.zly.www.easyrecyclerview.EasyRecyclerView; 11 | import com.zly.www.easyrecyclerview.listener.ItemClickSupport; 12 | import com.zly.www.simple.adapter.CustomAdapter; 13 | import com.zly.www.simple.customall.CustomAllActivity; 14 | import com.zly.www.simple.customdecoration.CustomDecorationActivity; 15 | import com.zly.www.simple.customheader.CustomHeaderActivity; 16 | 17 | import butterknife.BindView; 18 | import butterknife.ButterKnife; 19 | 20 | /** 21 | * Created by zly on 2016/11/28 0028. 22 | */ 23 | 24 | public class MainActivity extends AppCompatActivity { 25 | 26 | @BindView(R.id.erv) 27 | EasyRecyclerView erv; 28 | 29 | private CustomAdapter mAdapter; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_main); 35 | ButterKnife.bind(this); 36 | 37 | initView(); 38 | initData(); 39 | } 40 | 41 | private void initView() { 42 | erv.setLayoutManager(new GridLayoutManager(this,3)); 43 | erv.setAdapter(mAdapter = new CustomAdapter()); 44 | 45 | ItemClickSupport.addTo(erv.getRecyclerView()).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() { 46 | @Override 47 | public void onItemClicked(RecyclerView recyclerView, int position, View v) { 48 | switch (position){ 49 | case 0: 50 | startActivity(new Intent(MainActivity.this,CustomHeaderActivity.class)); 51 | break; 52 | case 1: 53 | startActivity(new Intent(MainActivity.this,CustomAllActivity.class)); 54 | break; 55 | case 2: 56 | startActivity(new Intent(MainActivity.this,CustomDecorationActivity.class)); 57 | break; 58 | } 59 | } 60 | }); 61 | } 62 | 63 | private void initData() { 64 | mAdapter.add(getString(R.string.custom_header)); 65 | mAdapter.add(getString(R.string.custom_header_footer)); 66 | mAdapter.add(getString(R.string.custom_item_decoration)); 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/customdecoration/CustomDecorationActivity.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple.customdecoration; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | 7 | import com.zly.www.easyrecyclerview.EasyRecyclerView; 8 | import com.zly.www.easyrecyclerview.decoration.StickItemDecoration; 9 | import com.zly.www.simple.R; 10 | 11 | import butterknife.BindView; 12 | import butterknife.ButterKnife; 13 | 14 | /** 15 | * 条目吸附demo 16 | * Created by zly on 2016/11/28 0028. 17 | */ 18 | 19 | public class CustomDecorationActivity extends AppCompatActivity { 20 | 21 | @BindView(R.id.erv) 22 | EasyRecyclerView erv; 23 | 24 | private CustomDecorationAdapter mAdapter; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_custom_stick); 30 | ButterKnife.bind(this); 31 | 32 | initView(); 33 | initData(); 34 | } 35 | 36 | private void initView() { 37 | erv.setLayoutManager(new LinearLayoutManager(this)); 38 | erv.setAdapter(mAdapter = new CustomDecorationAdapter()); 39 | erv.addItemDecoration(new StickItemDecoration(this,mAdapter.getData()) { 40 | @Override 41 | public String getTag(int position) { 42 | return mAdapter.getItem(position).tag; 43 | } 44 | 45 | }); 46 | } 47 | 48 | private void initData() { 49 | mAdapter.add(new CityBean("a","安庆")); 50 | mAdapter.add(new CityBean("a","安徽")); 51 | mAdapter.add(new CityBean("a","鞍山")); 52 | mAdapter.add(new CityBean("a","澳门")); 53 | mAdapter.add(new CityBean("b","白城")); 54 | mAdapter.add(new CityBean("b","白沙")); 55 | mAdapter.add(new CityBean("b","北海")); 56 | mAdapter.add(new CityBean("b","北京")); 57 | mAdapter.add(new CityBean("b","保定")); 58 | mAdapter.add(new CityBean("b","宝鸡")); 59 | mAdapter.add(new CityBean("c","长沙")); 60 | mAdapter.add(new CityBean("c","成都")); 61 | mAdapter.add(new CityBean("c","常德")); 62 | mAdapter.add(new CityBean("d","大理")); 63 | mAdapter.add(new CityBean("d","大连")); 64 | mAdapter.add(new CityBean("d","大庆")); 65 | mAdapter.add(new CityBean("e","鄂州")); 66 | mAdapter.add(new CityBean("h","哈尔滨")); 67 | mAdapter.add(new CityBean("w","万宁")); 68 | mAdapter.add(new CityBean("w","潍坊")); 69 | mAdapter.add(new CityBean("w","威海")); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/zly/www/simple/customheader/CustomHeaderActivity.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.simple.customheader; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import com.zly.www.easyrecyclerview.EasyDefRecyclerView; 8 | import com.zly.www.easyrecyclerview.listener.OnLoadListener; 9 | import com.zly.www.easyrecyclerview.listener.OnRefreshListener; 10 | import com.zly.www.easyrecyclerview.ptrlib.header.MaterialHeader; 11 | import com.zly.www.simple.DisplayUtil; 12 | import com.zly.www.simple.R; 13 | import com.zly.www.simple.adapter.CustomAdapter; 14 | 15 | import butterknife.BindView; 16 | import butterknife.ButterKnife; 17 | 18 | /** 19 | * 自定义头部 20 | * Created by zly on 2016/11/28 0028. 21 | */ 22 | public class CustomHeaderActivity extends AppCompatActivity implements OnRefreshListener, OnLoadListener { 23 | 24 | @BindView(R.id.erv) 25 | EasyDefRecyclerView erv; 26 | 27 | private CustomAdapter mAdapter; 28 | private Handler handler = new Handler(); 29 | private boolean isFail = false; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_custom); 35 | ButterKnife.bind(this); 36 | 37 | MaterialHeader materialHeader = new MaterialHeader(this); 38 | materialHeader.setPadding(0, DisplayUtil.dip2px(20),0,DisplayUtil.dip2px(20)); 39 | erv.setHeaderView(materialHeader); 40 | erv.setPinContent(true); 41 | erv.setAdapter(mAdapter = new CustomAdapter()); 42 | erv.setLastUpdateTimeRelateObject(this); 43 | erv.setOnRefreshListener(this); 44 | erv.setOnLoadListener(this); 45 | } 46 | 47 | @Override 48 | public void onRefreshListener() { 49 | handler.postDelayed(new Runnable() { 50 | @Override 51 | public void run() { 52 | for (int i = 0; i < 10; i++) { 53 | mAdapter.insert("最新数据",0); 54 | } 55 | } 56 | }, 3000); 57 | } 58 | 59 | @Override 60 | public void onLoadListener() { 61 | handler.postDelayed(new Runnable() { 62 | @Override 63 | public void run() { 64 | 65 | if (mAdapter.getItemCount() > 20 && !isFail) { 66 | isFail = true; 67 | erv.loadFail(); 68 | } else if (mAdapter.getItemCount() > 30) { 69 | erv.noMore(); 70 | } else { 71 | for (int i = 0; i < 10; i++) { 72 | mAdapter.add("更多数据"); 73 | } 74 | } 75 | } 76 | }, 3000); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/footer/ErvDefaultFooter.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.footer; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.text.SpannableStringBuilder; 6 | import android.text.TextPaint; 7 | import android.text.method.LinkMovementMethod; 8 | import android.text.style.ClickableSpan; 9 | import android.util.AttributeSet; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.FrameLayout; 14 | import android.widget.TextView; 15 | 16 | import com.zly.www.easyrecyclerview.R; 17 | import com.zly.www.easyrecyclerview.listener.OnLoadListener; 18 | 19 | import java.lang.ref.WeakReference; 20 | 21 | import static android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE; 22 | 23 | /** 24 | * 默认底部实现 25 | * Created by zly on 2016/10/25 0025. 26 | */ 27 | 28 | public class ErvDefaultFooter extends FrameLayout implements ErvLoadUIHandle { 29 | 30 | private TextView footer; 31 | private int mState = LOAD; 32 | 33 | public ErvDefaultFooter(Context context) { 34 | this(context, null); 35 | } 36 | 37 | public ErvDefaultFooter(Context context, AttributeSet attrs) { 38 | this(context, attrs, 0); 39 | } 40 | 41 | public ErvDefaultFooter(Context context, AttributeSet attrs, int defStyleAttr) { 42 | super(context, attrs, defStyleAttr); 43 | init(); 44 | } 45 | 46 | private void init() { 47 | setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)); 48 | View view = LayoutInflater.from(getContext()).inflate(R.layout.erv_default_footer, this); 49 | footer = (TextView) view.findViewById(R.id.footerTv); 50 | onLoading(); 51 | } 52 | 53 | @Override 54 | public void onLoading() { 55 | mState = LOAD; 56 | footer.setText(getResources().getString(R.string.erv_loading)); 57 | } 58 | 59 | @Override 60 | public void onNoMore() { 61 | mState = NOMORE; 62 | footer.setText(getResources().getString(R.string.erv_noMore)); 63 | } 64 | 65 | @Override 66 | public void onLoadFail(OnLoadListener listener) { 67 | mState = LOADFAIL; 68 | String content = "加载失败,"; 69 | String click = "点击重试"; 70 | SpannableStringBuilder builder = new SpannableStringBuilder(content+click); 71 | builder.setSpan(new Click(listener),content.length(),content.length()+click.length(),SPAN_INCLUSIVE_INCLUSIVE); 72 | footer.setText(builder); 73 | footer.setMovementMethod(new LinkMovementMethod()); 74 | } 75 | 76 | @Override 77 | public int getState() { 78 | return mState; 79 | } 80 | 81 | class Click extends ClickableSpan{ 82 | 83 | private WeakReference reference; 84 | 85 | public Click(OnLoadListener listener) { 86 | this.reference = new WeakReference(listener); 87 | } 88 | 89 | @Override 90 | public void onClick(View v) { 91 | onLoading(); 92 | OnLoadListener onLoadListener = reference.get(); 93 | if(onLoadListener != null){ 94 | onLoadListener.onLoadListener(); 95 | } 96 | } 97 | 98 | @Override 99 | public void updateDrawState(TextPaint ds) { 100 | ds.setColor(Color.BLUE); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/utils/LayoutManagerUtil.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.utils; 2 | 3 | import android.support.v7.widget.GridLayoutManager; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.support.v7.widget.StaggeredGridLayoutManager; 7 | 8 | import java.util.Arrays; 9 | 10 | import static com.zly.www.easyrecyclerview.utils.LayoutManagerUtil.LAYOUT_MANAGER_TYPE.GRID; 11 | import static com.zly.www.easyrecyclerview.utils.LayoutManagerUtil.LAYOUT_MANAGER_TYPE.LINEAR; 12 | import static com.zly.www.easyrecyclerview.utils.LayoutManagerUtil.LAYOUT_MANAGER_TYPE.STAGGERED_GRID; 13 | 14 | /** 15 | * @author chenzj 16 | * 类的描述 - 17 | * 2016/10/9 10:36 18 | * admin@chenzhongjin.cn 19 | */ 20 | public class LayoutManagerUtil { 21 | 22 | public static int getFirstVisibleItemPosition(RecyclerView.LayoutManager layoutManager) { 23 | int firstVisibleItemPosition = -1; 24 | 25 | switch (getLayoutManagerType(layoutManager)) { 26 | case LINEAR: 27 | case GRID: 28 | firstVisibleItemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); 29 | break; 30 | case STAGGERED_GRID: 31 | firstVisibleItemPosition = caseStaggeredGrid(layoutManager, true); 32 | break; 33 | } 34 | return firstVisibleItemPosition; 35 | } 36 | 37 | public static int getLastVisibleItemPosition(RecyclerView.LayoutManager layoutManager) { 38 | int lastVisibleItemPosition = -1; 39 | switch (getLayoutManagerType(layoutManager)) { 40 | case LINEAR: 41 | case GRID: 42 | lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); 43 | break; 44 | case STAGGERED_GRID: 45 | lastVisibleItemPosition = caseStaggeredGrid(layoutManager, false); 46 | break; 47 | } 48 | return lastVisibleItemPosition; 49 | } 50 | 51 | private static LAYOUT_MANAGER_TYPE getLayoutManagerType(RecyclerView.LayoutManager layoutManager) { 52 | LAYOUT_MANAGER_TYPE layoutManagerType; 53 | 54 | if (layoutManager instanceof GridLayoutManager) { 55 | layoutManagerType = GRID; 56 | } else if (layoutManager instanceof LinearLayoutManager) { 57 | layoutManagerType = LINEAR; 58 | } else if (layoutManager instanceof StaggeredGridLayoutManager) { 59 | layoutManagerType = STAGGERED_GRID; 60 | } else { 61 | throw new RuntimeException("Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager " + 62 | "and StaggeredGridLayoutManager"); 63 | } 64 | return layoutManagerType; 65 | } 66 | 67 | private static int caseStaggeredGrid(RecyclerView.LayoutManager layoutManager, boolean findFirst) { 68 | StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager; 69 | int[] positions = new int[staggeredGridLayoutManager.getSpanCount()]; 70 | 71 | if (findFirst) { 72 | staggeredGridLayoutManager.findFirstVisibleItemPositions(positions); 73 | Arrays.sort(positions); 74 | return positions[0]; 75 | } else { 76 | staggeredGridLayoutManager.findLastVisibleItemPositions(positions); 77 | Arrays.sort(positions); 78 | return positions[positions.length - 1]; 79 | } 80 | } 81 | 82 | public enum LAYOUT_MANAGER_TYPE { 83 | LINEAR, 84 | GRID, 85 | STAGGERED_GRID 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/listener/ItemClickSupport.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.listener; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | import com.zly.www.easyrecyclerview.R; 7 | 8 | /** 9 | * RecyclerView item click 10 | * Created by zly on 2016/11/22 0022. 11 | */ 12 | public class ItemClickSupport { 13 | private final RecyclerView mRecyclerView; 14 | private OnItemClickListener mOnItemClickListener; 15 | private OnItemLongClickListener mOnItemLongClickListener; 16 | 17 | private View.OnClickListener mOnClickListener = new View.OnClickListener() { 18 | @Override 19 | public void onClick(View v) { 20 | if (mOnItemClickListener != null) { 21 | RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v); 22 | mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v); 23 | } 24 | } 25 | }; 26 | 27 | private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() { 28 | @Override 29 | public boolean onLongClick(View v) { 30 | if (mOnItemLongClickListener != null) { 31 | RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v); 32 | return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v); 33 | } 34 | return false; 35 | } 36 | }; 37 | 38 | private RecyclerView.OnChildAttachStateChangeListener mAttachListener 39 | = new RecyclerView.OnChildAttachStateChangeListener() { 40 | @Override 41 | public void onChildViewAttachedToWindow(View view) { 42 | if (mOnItemClickListener != null) { 43 | view.setOnClickListener(mOnClickListener); 44 | } 45 | if (mOnItemLongClickListener != null) { 46 | view.setOnLongClickListener(mOnLongClickListener); 47 | } 48 | } 49 | 50 | @Override 51 | public void onChildViewDetachedFromWindow(View view) { 52 | } 53 | }; 54 | 55 | private ItemClickSupport(RecyclerView recyclerView) { 56 | mRecyclerView = recyclerView; 57 | mRecyclerView.setTag(R.id.item_click_support, this); 58 | mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener); 59 | } 60 | 61 | public static ItemClickSupport addTo(RecyclerView view) { 62 | ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support); 63 | if (support == null) { 64 | support = new ItemClickSupport(view); 65 | } 66 | return support; 67 | } 68 | 69 | public static ItemClickSupport removeFrom(RecyclerView view) { 70 | ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support); 71 | if (support != null) { 72 | support.detach(view); 73 | } 74 | return support; 75 | } 76 | 77 | public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) { 78 | mOnItemClickListener = listener; 79 | return this; 80 | } 81 | 82 | public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) { 83 | mOnItemLongClickListener = listener; 84 | return this; 85 | } 86 | 87 | private void detach(RecyclerView view) { 88 | view.removeOnChildAttachStateChangeListener(mAttachListener); 89 | view.setTag(R.id.item_click_support, null); 90 | } 91 | 92 | public interface OnItemClickListener { 93 | void onItemClicked(RecyclerView recyclerView, int position, View v); 94 | } 95 | 96 | public interface OnItemLongClickListener { 97 | boolean onItemLongClicked(RecyclerView recyclerView, int position, View v); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/indicator/PtrTensionIndicator.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib.indicator; 2 | 3 | public class PtrTensionIndicator extends PtrIndicator { 4 | 5 | private float DRAG_RATE = 0.5f; 6 | private float mDownY; 7 | private float mDownPos; 8 | private float mOneHeight = 0; 9 | 10 | private float mCurrentDragPercent; 11 | 12 | private int mReleasePos; 13 | private float mReleasePercent = -1; 14 | 15 | @Override 16 | public void onPressDown(float x, float y) { 17 | super.onPressDown(x, y); 18 | mDownY = y; 19 | mDownPos = getCurrentPosY(); 20 | } 21 | 22 | @Override 23 | public void onRelease() { 24 | super.onRelease(); 25 | mReleasePos = getCurrentPosY(); 26 | mReleasePercent = mCurrentDragPercent; 27 | } 28 | 29 | @Override 30 | public void onUIRefreshComplete() { 31 | mReleasePos = getCurrentPosY(); 32 | mReleasePercent = getOverDragPercent(); 33 | } 34 | 35 | @Override 36 | public void setHeaderHeight(int height) { 37 | super.setHeaderHeight(height); 38 | mOneHeight = height * 4f / 5; 39 | } 40 | 41 | @Override 42 | protected void processOnMove(float currentX, float currentY, float offsetX, float offsetY) { 43 | 44 | if (currentY < mDownY) { 45 | super.processOnMove(currentX, currentY, offsetX, offsetY); 46 | return; 47 | } 48 | 49 | // distance from top 50 | final float scrollTop = (currentY - mDownY) * DRAG_RATE + mDownPos; 51 | final float currentDragPercent = scrollTop / mOneHeight; 52 | 53 | if (currentDragPercent < 0) { 54 | setOffset(offsetX, 0); 55 | return; 56 | } 57 | 58 | mCurrentDragPercent = currentDragPercent; 59 | 60 | // 0 ~ 1 61 | float boundedDragPercent = Math.min(1f, Math.abs(currentDragPercent)); 62 | float extraOS = scrollTop - mOneHeight; 63 | 64 | // 0 ~ 2 65 | // if extraOS lower than 0, which means scrollTop lower than onHeight, tensionSlingshotPercent will be 0. 66 | float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, mOneHeight * 2) / mOneHeight); 67 | 68 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow((tensionSlingshotPercent / 4), 2)) * 2f; 69 | float extraMove = (mOneHeight) * tensionPercent / 2; 70 | int targetY = (int) ((mOneHeight * boundedDragPercent) + extraMove); 71 | int change = targetY - getCurrentPosY(); 72 | 73 | setOffset(currentX, change); 74 | } 75 | 76 | private float offsetToTarget(float scrollTop) { 77 | 78 | // distance from top 79 | final float currentDragPercent = scrollTop / mOneHeight; 80 | 81 | mCurrentDragPercent = currentDragPercent; 82 | 83 | // 0 ~ 1 84 | float boundedDragPercent = Math.min(1f, Math.abs(currentDragPercent)); 85 | float extraOS = scrollTop - mOneHeight; 86 | 87 | // 0 ~ 2 88 | // if extraOS lower than 0, which means scrollTop lower than mOneHeight, tensionSlingshotPercent will be 0. 89 | float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, mOneHeight * 2) / mOneHeight); 90 | 91 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow((tensionSlingshotPercent / 4), 2)) * 2f; 92 | float extraMove = (mOneHeight) * tensionPercent / 2; 93 | int targetY = (int) ((mOneHeight * boundedDragPercent) + extraMove); 94 | 95 | return 0; 96 | } 97 | 98 | @Override 99 | public int getOffsetToKeepHeaderWhileLoading() { 100 | return getOffsetToRefresh(); 101 | } 102 | 103 | @Override 104 | public int getOffsetToRefresh() { 105 | return (int) mOneHeight; 106 | } 107 | 108 | public float getOverDragPercent() { 109 | if (isUnderTouch()) { 110 | return mCurrentDragPercent; 111 | } else { 112 | if (mReleasePercent <= 0) { 113 | return 1.0f * getCurrentPosY() / getOffsetToKeepHeaderWhileLoading(); 114 | } 115 | // after release 116 | return mReleasePercent * getCurrentPosY() / mReleasePos; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/PtrUIHandlerHolder.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib; 2 | 3 | 4 | import com.zly.www.easyrecyclerview.ptrlib.indicator.PtrIndicator; 5 | 6 | /** 7 | * A single linked list to wrap PtrUIHandler 8 | */ 9 | class PtrUIHandlerHolder implements PtrUIHandler { 10 | 11 | private PtrUIHandler mHandler; 12 | private PtrUIHandlerHolder mNext; 13 | 14 | private boolean contains(PtrUIHandler handler) { 15 | return mHandler != null && mHandler == handler; 16 | } 17 | 18 | private PtrUIHandlerHolder() { 19 | 20 | } 21 | 22 | public boolean hasHandler() { 23 | return mHandler != null; 24 | } 25 | 26 | private PtrUIHandler getHandler() { 27 | return mHandler; 28 | } 29 | 30 | public static void addHandler(PtrUIHandlerHolder head, PtrUIHandler handler) { 31 | 32 | if (null == handler) { 33 | return; 34 | } 35 | if (head == null) { 36 | return; 37 | } 38 | if (null == head.mHandler) { 39 | head.mHandler = handler; 40 | return; 41 | } 42 | 43 | PtrUIHandlerHolder current = head; 44 | for (; ; current = current.mNext) { 45 | 46 | // duplicated 47 | if (current.contains(handler)) { 48 | return; 49 | } 50 | if (current.mNext == null) { 51 | break; 52 | } 53 | } 54 | 55 | PtrUIHandlerHolder newHolder = new PtrUIHandlerHolder(); 56 | newHolder.mHandler = handler; 57 | current.mNext = newHolder; 58 | } 59 | 60 | public static PtrUIHandlerHolder create() { 61 | return new PtrUIHandlerHolder(); 62 | } 63 | 64 | public static PtrUIHandlerHolder removeHandler(PtrUIHandlerHolder head, PtrUIHandler handler) { 65 | if (head == null || handler == null || null == head.mHandler) { 66 | return head; 67 | } 68 | 69 | PtrUIHandlerHolder current = head; 70 | PtrUIHandlerHolder pre = null; 71 | do { 72 | 73 | // delete current: link pre to next, unlink next from current; 74 | // pre will no change, current move to next element; 75 | if (current.contains(handler)) { 76 | 77 | // current is head 78 | if (pre == null) { 79 | 80 | head = current.mNext; 81 | current.mNext = null; 82 | 83 | current = head; 84 | } else { 85 | 86 | pre.mNext = current.mNext; 87 | current.mNext = null; 88 | current = pre.mNext; 89 | } 90 | } else { 91 | pre = current; 92 | current = current.mNext; 93 | } 94 | 95 | } while (current != null); 96 | 97 | if (head == null) { 98 | head = new PtrUIHandlerHolder(); 99 | } 100 | return head; 101 | } 102 | 103 | @Override 104 | public void onUIReset(PtrFrameLayout frame) { 105 | PtrUIHandlerHolder current = this; 106 | do { 107 | final PtrUIHandler handler = current.getHandler(); 108 | if (null != handler) { 109 | handler.onUIReset(frame); 110 | } 111 | } while ((current = current.mNext) != null); 112 | } 113 | 114 | @Override 115 | public void onUIRefreshPrepare(PtrFrameLayout frame) { 116 | if (!hasHandler()) { 117 | return; 118 | } 119 | PtrUIHandlerHolder current = this; 120 | do { 121 | final PtrUIHandler handler = current.getHandler(); 122 | if (null != handler) { 123 | handler.onUIRefreshPrepare(frame); 124 | } 125 | } while ((current = current.mNext) != null); 126 | } 127 | 128 | @Override 129 | public void onUIRefreshBegin(PtrFrameLayout frame) { 130 | PtrUIHandlerHolder current = this; 131 | do { 132 | final PtrUIHandler handler = current.getHandler(); 133 | if (null != handler) { 134 | handler.onUIRefreshBegin(frame); 135 | } 136 | } while ((current = current.mNext) != null); 137 | } 138 | 139 | @Override 140 | public void onUIRefreshComplete(PtrFrameLayout frame) { 141 | PtrUIHandlerHolder current = this; 142 | do { 143 | final PtrUIHandler handler = current.getHandler(); 144 | if (null != handler) { 145 | handler.onUIRefreshComplete(frame); 146 | } 147 | } while ((current = current.mNext) != null); 148 | } 149 | 150 | @Override 151 | public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { 152 | PtrUIHandlerHolder current = this; 153 | do { 154 | final PtrUIHandler handler = current.getHandler(); 155 | if (null != handler) { 156 | handler.onUIPositionChange(frame, isUnderTouch, status, ptrIndicator); 157 | } 158 | } while ((current = current.mNext) != null); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/indicator/PtrIndicator.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib.indicator; 2 | 3 | import android.graphics.PointF; 4 | 5 | public class PtrIndicator { 6 | 7 | public final static int POS_START = 0; 8 | protected int mOffsetToRefresh = 0; 9 | private PointF mPtLastMove = new PointF(); 10 | private float mOffsetX; 11 | private float mOffsetY; 12 | private int mCurrentPos = 0; 13 | private int mLastPos = 0; 14 | private int mHeaderHeight; 15 | private int mPressedPos = 0; 16 | 17 | private float mRatioOfHeaderHeightToRefresh = 1.2f; 18 | private float mResistance = 1.7f; 19 | private boolean mIsUnderTouch = false; 20 | private int mOffsetToKeepHeaderWhileLoading = -1; 21 | // record the refresh complete position 22 | private int mRefreshCompleteY = 0; 23 | 24 | public boolean isUnderTouch() { 25 | return mIsUnderTouch; 26 | } 27 | 28 | public float getResistance() { 29 | return mResistance; 30 | } 31 | 32 | public void setResistance(float resistance) { 33 | mResistance = resistance; 34 | } 35 | 36 | public void onRelease() { 37 | mIsUnderTouch = false; 38 | } 39 | 40 | public void onUIRefreshComplete() { 41 | mRefreshCompleteY = mCurrentPos; 42 | } 43 | 44 | public boolean goDownCrossFinishPosition() { 45 | return mCurrentPos >= mRefreshCompleteY; 46 | } 47 | 48 | protected void processOnMove(float currentX, float currentY, float offsetX, float offsetY) { 49 | setOffset(offsetX, offsetY / mResistance); 50 | } 51 | 52 | public void setRatioOfHeaderHeightToRefresh(float ratio) { 53 | mRatioOfHeaderHeightToRefresh = ratio; 54 | mOffsetToRefresh = (int) (mHeaderHeight * ratio); 55 | } 56 | 57 | public float getRatioOfHeaderToHeightRefresh() { 58 | return mRatioOfHeaderHeightToRefresh; 59 | } 60 | 61 | public int getOffsetToRefresh() { 62 | return mOffsetToRefresh; 63 | } 64 | 65 | public void setOffsetToRefresh(int offset) { 66 | mRatioOfHeaderHeightToRefresh = mHeaderHeight * 1f / offset; 67 | mOffsetToRefresh = offset; 68 | } 69 | 70 | public void onPressDown(float x, float y) { 71 | mIsUnderTouch = true; 72 | mPressedPos = mCurrentPos; 73 | mPtLastMove.set(x, y); 74 | } 75 | 76 | public final void onMove(float x, float y) { 77 | float offsetX = x - mPtLastMove.x; 78 | float offsetY = (y - mPtLastMove.y); 79 | processOnMove(x, y, offsetX, offsetY); 80 | mPtLastMove.set(x, y); 81 | } 82 | 83 | protected void setOffset(float x, float y) { 84 | mOffsetX = x; 85 | mOffsetY = y; 86 | } 87 | 88 | public float getOffsetX() { 89 | return mOffsetX; 90 | } 91 | 92 | public float getOffsetY() { 93 | return mOffsetY; 94 | } 95 | 96 | public int getLastPosY() { 97 | return mLastPos; 98 | } 99 | 100 | public int getCurrentPosY() { 101 | return mCurrentPos; 102 | } 103 | 104 | /** 105 | * Update current position before update the UI 106 | */ 107 | public final void setCurrentPos(int current) { 108 | mLastPos = mCurrentPos; 109 | mCurrentPos = current; 110 | onUpdatePos(current, mLastPos); 111 | } 112 | 113 | protected void onUpdatePos(int current, int last) { 114 | 115 | } 116 | 117 | public int getHeaderHeight() { 118 | return mHeaderHeight; 119 | } 120 | 121 | public void setHeaderHeight(int height) { 122 | mHeaderHeight = height; 123 | updateHeight(); 124 | } 125 | 126 | protected void updateHeight() { 127 | mOffsetToRefresh = (int) (mRatioOfHeaderHeightToRefresh * mHeaderHeight); 128 | } 129 | 130 | public void convertFrom(PtrIndicator ptrSlider) { 131 | mCurrentPos = ptrSlider.mCurrentPos; 132 | mLastPos = ptrSlider.mLastPos; 133 | mHeaderHeight = ptrSlider.mHeaderHeight; 134 | } 135 | 136 | public boolean hasLeftStartPosition() { 137 | return mCurrentPos > POS_START; 138 | } 139 | 140 | public boolean hasJustLeftStartPosition() { 141 | return mLastPos == POS_START && hasLeftStartPosition(); 142 | } 143 | 144 | public boolean hasJustBackToStartPosition() { 145 | return mLastPos != POS_START && isInStartPosition(); 146 | } 147 | 148 | public boolean isOverOffsetToRefresh() { 149 | return mCurrentPos >= getOffsetToRefresh(); 150 | } 151 | 152 | public boolean hasMovedAfterPressedDown() { 153 | return mCurrentPos != mPressedPos; 154 | } 155 | 156 | public boolean isInStartPosition() { 157 | return mCurrentPos == POS_START; 158 | } 159 | 160 | public boolean crossRefreshLineFromTopToBottom() { 161 | return mLastPos < getOffsetToRefresh() && mCurrentPos >= getOffsetToRefresh(); 162 | } 163 | 164 | public boolean hasJustReachedHeaderHeightFromTopToBottom() { 165 | return mLastPos < mHeaderHeight && mCurrentPos >= mHeaderHeight; 166 | } 167 | 168 | public boolean isOverOffsetToKeepHeaderWhileLoading() { 169 | return mCurrentPos > getOffsetToKeepHeaderWhileLoading(); 170 | } 171 | 172 | public void setOffsetToKeepHeaderWhileLoading(int offset) { 173 | mOffsetToKeepHeaderWhileLoading = offset; 174 | } 175 | 176 | public int getOffsetToKeepHeaderWhileLoading() { 177 | return mOffsetToKeepHeaderWhileLoading >= 0 ? mOffsetToKeepHeaderWhileLoading : mHeaderHeight; 178 | } 179 | 180 | public boolean isAlreadyHere(int to) { 181 | return mCurrentPos == to; 182 | } 183 | 184 | public float getLastPercent() { 185 | final float oldPercent = mHeaderHeight == 0 ? 0 : mLastPos * 1f / mHeaderHeight; 186 | return oldPercent; 187 | } 188 | 189 | public float getCurrentPercent() { 190 | final float currentPercent = mHeaderHeight == 0 ? 0 : mCurrentPos * 1f / mHeaderHeight; 191 | return currentPercent; 192 | } 193 | 194 | public boolean willOverTop(int to) { 195 | return to < POS_START; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyRecyclerView 2 | ## **描述** 3 | 这是一个下拉刷新上拉加载更多框架(ps:后期还会加入一些常用的功能.),头部用的秋哥的[android-Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh),底部和没有数据的状态自己实现的.其实刚刚开始我是想找个库直接用的,试了几个排名靠前的,感觉跟自己想要的不太一样,索性自己写了一个,当然这当中也遇到了问题,多亏[仲锦大师](https://github.com/chenzj-king)的帮助在此感谢. 4 | 5 | ### 特点: 6 | - 可定制的头部 (可以查看[android-Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh)文档) 7 | - 可定制的底部 (加载中/没有数据/加载失败 三种状态的定制) 8 | - 可定制的没有数据状态显示 (目前只有一个状态) 9 | - 目前提供一个实现好的ItemDecoration(头部吸附效果) 10 |
11 | 12 | ## **效果预览** 13 | ### 1.定制头部&定制脚步 14 | - 头部秋哥已经定制了很多样式就直接使用了 15 | - 脚部这边使用的已经实现好的ErvDefaultFooter 16 | 17 | 默认头部与顶部效果 18 | 19 | ![](http://of1ktyksz.bkt.clouddn.com/erv_custom_all.gif) 20 | 21 | material style头部 22 | 23 | ![](http://of1ktyksz.bkt.clouddn.com/erv_material_header.gif) 24 |
25 | 26 | ### 2.头部吸附 27 | ![](http://of1ktyksz.bkt.clouddn.com/erv_custom_decoration.gif) 28 |
29 | 30 | ## **使用方式** 31 | ### 依赖 32 | gradle 33 | 34 | compile 'com.yysauce:easyrecyclerview:1.0.0' 35 | 36 | ### 配置 37 | 目前有两个参数可以配置 38 | - app:emply_layout
39 | 没有数据时候布局 40 | 41 | - app:number_load_more
42 | 最后可见条目 + number_load_more > total 触发加载更多;默认值为4 43 | 44 | ### xml中配置示例 45 | 50 | 51 | 52 | 53 | 54 | 55 | ### activity代码配置 56 | 57 | erv.setAdapter(rvAdapter = new RvAdapter());//设置adapter 58 | erv.setLastUpdateTimeRelateObject(this);//传入参数类名作为记录刷新时间key 59 | erv.setOnRefreshListener(this);//设置刷新监听 60 | erv.setOnLoadListener(this);//设置加载更多监听 61 | 62 | 由于这里使用的EasyDefRecyclerView,头部就是默认经典样式所以需要调用,使用其他头部时不需要调用 63 | 64 | erv.setLastUpdateTimeRelateObject(this);//传入参数类名作为记录刷新时间key 65 | 66 | 67 | ### adapter代码配置 68 | 69 | adapter需要实现CommonAdapter或者MultipleAdapter抽象方法 70 | 71 | //创建ViewHolder 72 | public abstract VH createCustomViewHolder(ViewGroup parent, int viewType); 73 | //ViewHolder设置数据 74 | public abstract void bindCustomViewHolder(VH holder, T t, int position); 75 | 76 | MultipleAdapter多条目布局还多一个方法需要实现 77 | 78 | //返回多条目的type 79 | public abstract int customItemViewType(int position); 80 | 81 | 目前提供了下面这些方法操作adapter数据,具体实现可以在CommonAdapter中查看 82 | 83 | 新增数据 84 | - public void add(@NonNull T object) 85 | - public void addAll(@NonNull Collection collection) 86 | - public void addAll(@NonNull T... items) 87 | - public void insert(@NonNull T object, int index) 88 | - public void insertAll(@NonNull Collection collection, int index) 89 | 90 | 删除数据 91 | - public void remove(int index) 92 | - public boolean remove(@NonNull T object) 93 | - public void clear() 94 | 95 | 修改数据 96 | - public void update(@NonNull List mDatas) 97 | 98 | 查看数据 99 | - public T getItem(int position) 100 | - public int getPosition(T item) 101 | - public List getData() 102 | 103 | 排序 104 | - public void sort(Comparator comparator) 105 | 106 | 加载布局 107 | - public View inflateView(@LayoutRes int resId, ViewGroup parent) 108 | 109 | adapter中ViewHolder需要继承BaseViewHolder 110 |
111 | 112 | 113 | ## **其他配置** 114 | ### 头部吸附效果 115 | 116 | mItemDecoration = new StickItemDecoration(context,dataList) { 117 | @Override 118 | public String getTag(int position) { 119 | return "吸附头部显示的文字"; 120 | } 121 | } 122 | erv.addItemDecoration(mItemDecoration); 123 | 124 | 这里StickItemDecoration提供了如下方法来定制吸附效果 125 | 126 | //设置吸附条目高度 127 | public void setStickHeight(int mStickHeight) 128 | //设置吸附条目背景 129 | public void setStickBackgroundColor(int mStickBackgroundColor) 130 | //设置吸附文字颜色 131 | public void setStickTextColor(int mStickTextColor) 132 | //设置吸附文字大小 133 | public void setStickTextSize(int mStickTextSize) 134 | //设置吸附文字leftmargin 135 | public void setStickTextoffset(int mStickTextoffset) 136 | 137 | ## **自定义** 138 | 139 | 头部使用秋哥的[android-Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh) 140 | 秋哥默认已经实现了3个头部 141 | - MaterialHeader 142 | - PtrClassicDefaultHeader 143 | - StoreHouseHeader 144 | 145 | 一般情况下这些样式应该够了,如果有特殊需求可以自定义头部.然后erv.setHeaderView(view); 146 | 147 | 底部的话目前我只实现了一个ErvDefaultFooter,自定义的话需要实现ErvLoadUIHandle接口.写法可以参考ErvDefaultFooter 148 | 149 | public interface ErvLoadUIHandle { 150 | 151 | /** 152 | * 允许加载更多 153 | */ 154 | int LOAD = 1; 155 | 156 | /** 157 | * 暂无更多数据 158 | */ 159 | int NOMORE = 2; 160 | 161 | /** 162 | * 加载失败 163 | */ 164 | int LOADFAIL = 3; 165 | 166 | /** 167 | * @return 获取底部当前状态 168 | */ 169 | int getState(); 170 | 171 | void onLoading();//loading状态实现 172 | 173 | void onNoMore();//没有数据状态实现 174 | 175 | void onLoadFail(OnLoadListener listener);//加载失败实现 176 | 177 | 178 | } 179 | 180 | 实现后调用setFooterView()方法设置 181 |
182 | 183 | ## **总结** 184 | 目前还在EasyRecyclerView还在优化欢迎各位提出你们宝贵的意见,例子可以参考Sample 185 |
186 | 187 | ## **感谢** 188 | 秋哥的[android-Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh)
189 | [仲锦大师](https://github.com/chenzj-king)的帮助 190 |
191 | 192 | ## **联系方式** 193 | qq:1835556188
194 | blog:http://blog.csdn.net/zly921112
195 |
196 | 197 | ## **License** 198 | Copyright (c) 2016 zhuliyuan 199 | 200 | Licensed under the Apache License, Version 2.0 (the "License"); 201 | you may not use this file except in compliance with the License. 202 | You may obtain a copy of the License at 203 | 204 | http://www.apache.org/licenses/LICENSE-2.0 205 | 206 | Unless required by applicable law or agreed to in writing, software 207 | distributed under the License is distributed on an "AS IS" BASIS, 208 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 209 | See the License for the specific language governing permissions and 210 | limitations under the License. 211 | Come on, don't tell me you read that. 212 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/header/MaterialHeader.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib.header; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Rect; 7 | import android.graphics.drawable.Drawable; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.view.animation.Animation; 11 | import android.view.animation.Transformation; 12 | 13 | import com.zly.www.easyrecyclerview.ptrlib.PtrFrameLayout; 14 | import com.zly.www.easyrecyclerview.ptrlib.PtrUIHandler; 15 | import com.zly.www.easyrecyclerview.ptrlib.PtrUIHandlerHook; 16 | import com.zly.www.easyrecyclerview.ptrlib.indicator.PtrIndicator; 17 | 18 | 19 | public class MaterialHeader extends View implements PtrUIHandler { 20 | 21 | private MaterialProgressDrawable mDrawable; 22 | private float mScale = 1f; 23 | private PtrFrameLayout mPtrFrameLayout; 24 | 25 | private Animation mScaleAnimation = new Animation() { 26 | @Override 27 | public void applyTransformation(float interpolatedTime, Transformation t) { 28 | mScale = 1f - interpolatedTime; 29 | mDrawable.setAlpha((int) (255 * mScale)); 30 | invalidate(); 31 | } 32 | }; 33 | 34 | public MaterialHeader(Context context) { 35 | super(context); 36 | initView(); 37 | } 38 | 39 | public MaterialHeader(Context context, AttributeSet attrs) { 40 | super(context, attrs); 41 | initView(); 42 | } 43 | 44 | public MaterialHeader(Context context, AttributeSet attrs, int defStyleAttr) { 45 | super(context, attrs, defStyleAttr); 46 | initView(); 47 | } 48 | 49 | public void setPtrFrameLayout(PtrFrameLayout layout) { 50 | 51 | final PtrUIHandlerHook mPtrUIHandlerHook = new PtrUIHandlerHook() { 52 | @Override 53 | public void run() { 54 | startAnimation(mScaleAnimation); 55 | } 56 | }; 57 | 58 | mScaleAnimation.setDuration(200); 59 | mScaleAnimation.setAnimationListener(new Animation.AnimationListener() { 60 | @Override 61 | public void onAnimationStart(Animation animation) { 62 | 63 | } 64 | 65 | @Override 66 | public void onAnimationEnd(Animation animation) { 67 | mPtrUIHandlerHook.resume(); 68 | } 69 | 70 | @Override 71 | public void onAnimationRepeat(Animation animation) { 72 | 73 | } 74 | }); 75 | 76 | mPtrFrameLayout = layout; 77 | mPtrFrameLayout.setRefreshCompleteHook(mPtrUIHandlerHook); 78 | } 79 | 80 | private void initView() { 81 | mDrawable = new MaterialProgressDrawable(getContext(), this); 82 | mDrawable.setBackgroundColor(Color.WHITE); 83 | mDrawable.setCallback(this); 84 | } 85 | 86 | @Override 87 | public void invalidateDrawable(Drawable dr) { 88 | if (dr == mDrawable) { 89 | invalidate(); 90 | } else { 91 | super.invalidateDrawable(dr); 92 | } 93 | } 94 | 95 | public void setColorSchemeColors(int[] colors) { 96 | mDrawable.setColorSchemeColors(colors); 97 | invalidate(); 98 | } 99 | 100 | @Override 101 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 102 | int height = mDrawable.getIntrinsicHeight() + getPaddingTop() + getPaddingBottom(); 103 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 104 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 105 | } 106 | 107 | @Override 108 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 109 | final int size = mDrawable.getIntrinsicHeight(); 110 | mDrawable.setBounds(0, 0, size, size); 111 | } 112 | 113 | @Override 114 | protected void onDraw(Canvas canvas) { 115 | final int saveCount = canvas.save(); 116 | Rect rect = mDrawable.getBounds(); 117 | int l = getPaddingLeft() + (getMeasuredWidth() - mDrawable.getIntrinsicWidth()) / 2; 118 | canvas.translate(l, getPaddingTop()); 119 | canvas.scale(mScale, mScale, rect.exactCenterX(), rect.exactCenterY()); 120 | mDrawable.draw(canvas); 121 | canvas.restoreToCount(saveCount); 122 | } 123 | 124 | /** 125 | * When the content view has reached top and refresh has been completed, view will be reset. 126 | * 127 | * @param frame 128 | */ 129 | @Override 130 | public void onUIReset(PtrFrameLayout frame) { 131 | mScale = 1f; 132 | mDrawable.stop(); 133 | } 134 | 135 | /** 136 | * prepare for loading 137 | * 138 | * @param frame 139 | */ 140 | @Override 141 | public void onUIRefreshPrepare(PtrFrameLayout frame) { 142 | } 143 | 144 | /** 145 | * perform refreshing UI 146 | * 147 | * @param frame 148 | */ 149 | @Override 150 | public void onUIRefreshBegin(PtrFrameLayout frame) { 151 | mDrawable.setAlpha(255); 152 | mDrawable.start(); 153 | } 154 | 155 | /** 156 | * perform UI after refresh 157 | * 158 | * @param frame 159 | */ 160 | @Override 161 | public void onUIRefreshComplete(PtrFrameLayout frame) { 162 | mDrawable.stop(); 163 | } 164 | 165 | @Override 166 | public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { 167 | 168 | float percent = Math.min(1f, ptrIndicator.getCurrentPercent()); 169 | 170 | if (status == PtrFrameLayout.PTR_STATUS_PREPARE) { 171 | mDrawable.setAlpha((int) (255 * percent)); 172 | mDrawable.showArrow(true); 173 | 174 | float strokeStart = ((percent) * .8f); 175 | mDrawable.setStartEndTrim(0f, Math.min(0.8f, strokeStart)); 176 | mDrawable.setArrowScale(Math.min(1f, percent)); 177 | 178 | // magic 179 | float rotation = (-0.25f + .4f * percent + percent * 2) * .5f; 180 | mDrawable.setProgressRotation(rotation); 181 | invalidate(); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/decoration/StickItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.decoration; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.View; 11 | 12 | import com.zly.www.easyrecyclerview.utils.DisplayUtil; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * 顶部吸附ItemDecoration 18 | * Created by zly on 2016/11/28 0028. 19 | */ 20 | 21 | public abstract class StickItemDecoration extends RecyclerView.ItemDecoration { 22 | 23 | private Context mContext; 24 | private Paint mPaint; 25 | private List mList; 26 | 27 | private int mStickHeight; 28 | private int mStickBackgroundColor; 29 | private int mStickTextColor; 30 | private int mStickTextSize; 31 | private int mStickTextoffset; 32 | 33 | public StickItemDecoration(Context context, List mList) { 34 | this.mList = mList; 35 | this.mContext = context; 36 | this.mPaint = new Paint(); 37 | 38 | initProperty(); 39 | } 40 | 41 | private void initProperty() { 42 | mStickHeight = DisplayUtil.dip2px(mContext, 30); 43 | mStickTextSize = DisplayUtil.sp2px(mContext, 16); 44 | mStickTextoffset = DisplayUtil.dip2px(mContext, 2); 45 | 46 | mStickTextColor = Color.WHITE; 47 | mStickBackgroundColor = 0xffA3A3A3; 48 | 49 | mPaint.setTextSize(mStickTextSize); 50 | mPaint.setTextAlign(Paint.Align.LEFT); 51 | } 52 | 53 | @Override 54 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 55 | super.onDraw(c, parent, state); 56 | int left = parent.getPaddingLeft(); 57 | int right = parent.getWidth() - parent.getPaddingRight(); 58 | 59 | int childCount = parent.getChildCount(); 60 | for (int i = 0; i < childCount; i++) { 61 | View view = parent.getChildAt(i); 62 | int position = parent.getChildAdapterPosition(view); 63 | if (position != RecyclerView.NO_POSITION) { 64 | if (position == 0) { 65 | drawStickDecoration(left, view.getTop() - mStickHeight, right, view.getTop(), parent.getPaddingTop(), getTag(position), c); 66 | } else { 67 | if (position < mList.size() && !getTag(position).equals(getTag(position - 1))) { 68 | drawStickDecoration(left, view.getTop() - mStickHeight, right, view.getTop(), parent.getPaddingTop(), getTag(position), c); 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | @Override 76 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 77 | super.onDrawOver(c, parent, state); 78 | int position = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition(); 79 | int left = parent.getPaddingLeft(); 80 | int top = parent.getPaddingTop(); 81 | int right = parent.getWidth() - parent.getPaddingRight(); 82 | 83 | /** 84 | * 增加顶部动画效果 85 | */ 86 | View childView = parent.findViewHolderForLayoutPosition(position).itemView; 87 | if (position + 1 < mList.size()) { 88 | if (!getTag(position).equals(getTag(position + 1))) { 89 | if (childView.getHeight() + childView.getTop() < mStickHeight) { 90 | c.translate(0, childView.getHeight() + childView.getTop() - mStickHeight); 91 | } 92 | } 93 | } 94 | 95 | drawStickDecoration(left, top, right, top + mStickHeight, parent.getPaddingTop(), getTag(position), c); 96 | } 97 | 98 | @Override 99 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 100 | int position = parent.getChildAdapterPosition(view); 101 | if (position != RecyclerView.NO_POSITION) { 102 | if (position == 0) { 103 | outRect.set(0, mStickHeight, 0, 0); 104 | } else { 105 | if (position < mList.size() && !getTag(position).equals(getTag(position - 1))) { 106 | outRect.set(0, mStickHeight, 0, 0); 107 | } else { 108 | outRect.set(0, 0, 0, 0); 109 | } 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * 绘制stick 116 | */ 117 | private void drawStickDecoration(int left, int top, int right, int bottom, int parentTopPadding, String text, Canvas c) { 118 | if (top >= parentTopPadding) { 119 | Rect rect = new Rect(left, top, right, bottom); 120 | mPaint.setColor(mStickBackgroundColor); 121 | c.drawRect(rect, mPaint); 122 | 123 | mPaint.setColor(mStickTextColor); 124 | Paint.FontMetrics fontMetrics = mPaint.getFontMetrics(); 125 | float textTop = fontMetrics.top; 126 | float textBottom = fontMetrics.bottom; 127 | int textY = (int) (rect.centerY() - textTop / 2 - textBottom / 2); 128 | c.drawText(text, left + mStickTextoffset, textY, mPaint); 129 | } 130 | } 131 | 132 | public abstract String getTag(int position); 133 | 134 | 135 | public int getStickHeight() { 136 | return mStickHeight; 137 | } 138 | 139 | public void setStickHeight(int mStickHeight) { 140 | this.mStickHeight = mStickHeight; 141 | } 142 | 143 | public int getStickBackgroundColor() { 144 | return mStickBackgroundColor; 145 | } 146 | 147 | public void setStickBackgroundColor(int mStickBackgroundColor) { 148 | this.mStickBackgroundColor = mStickBackgroundColor; 149 | } 150 | 151 | public int getStickTextColor() { 152 | return mStickTextColor; 153 | } 154 | 155 | public void setStickTextColor(int mStickTextColor) { 156 | this.mStickTextColor = mStickTextColor; 157 | } 158 | 159 | public int getStickTextSize() { 160 | return mStickTextSize; 161 | } 162 | 163 | public void setStickTextSize(int mStickTextSize) { 164 | this.mStickTextSize = mStickTextSize; 165 | } 166 | 167 | public int getStickTextoffset() { 168 | return mStickTextoffset; 169 | } 170 | 171 | public void setStickTextoffset(int mStickTextoffset) { 172 | this.mStickTextoffset = mStickTextoffset; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/util/PtrCLog.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib.util; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * An encapsulation of {@link Log}, enable log level and print log with parameters. 7 | * 8 | * @author http://www.liaohuqiu.net/ 9 | */ 10 | public class PtrCLog { 11 | 12 | public static final int LEVEL_VERBOSE = 0; 13 | public static final int LEVEL_DEBUG = 1; 14 | public static final int LEVEL_INFO = 2; 15 | public static final int LEVEL_WARNING = 3; 16 | public static final int LEVEL_ERROR = 4; 17 | public static final int LEVEL_FATAL = 5; 18 | 19 | private static int sLevel = LEVEL_VERBOSE; 20 | 21 | /** 22 | * set log level, the level lower than this level will not be logged 23 | * 24 | * @param level 25 | */ 26 | public static void setLogLevel(int level) { 27 | sLevel = level; 28 | } 29 | 30 | /** 31 | * Send a VERBOSE log message. 32 | * 33 | * @param tag 34 | * @param msg 35 | */ 36 | public static void v(String tag, String msg) { 37 | if (sLevel > LEVEL_VERBOSE) { 38 | return; 39 | } 40 | Log.v(tag, msg); 41 | } 42 | 43 | /** 44 | * Send a VERBOSE log message. 45 | * 46 | * @param tag 47 | * @param msg 48 | * @param throwable 49 | */ 50 | public static void v(String tag, String msg, Throwable throwable) { 51 | if (sLevel > LEVEL_VERBOSE) { 52 | return; 53 | } 54 | Log.v(tag, msg, throwable); 55 | } 56 | 57 | /** 58 | * Send a VERBOSE log message. 59 | * 60 | * @param tag 61 | * @param msg 62 | * @param args 63 | */ 64 | public static void v(String tag, String msg, Object... args) { 65 | if (sLevel > LEVEL_VERBOSE) { 66 | return; 67 | } 68 | if (args.length > 0) { 69 | msg = String.format(msg, args); 70 | } 71 | Log.v(tag, msg); 72 | } 73 | 74 | /** 75 | * Send a DEBUG log message 76 | * 77 | * @param tag 78 | * @param msg 79 | */ 80 | public static void d(String tag, String msg) { 81 | if (sLevel > LEVEL_DEBUG) { 82 | return; 83 | } 84 | Log.d(tag, msg); 85 | } 86 | 87 | /** 88 | * Send a DEBUG log message 89 | * 90 | * @param tag 91 | * @param msg 92 | * @param args 93 | */ 94 | public static void d(String tag, String msg, Object... args) { 95 | if (sLevel > LEVEL_DEBUG) { 96 | return; 97 | } 98 | if (args.length > 0) { 99 | msg = String.format(msg, args); 100 | } 101 | Log.d(tag, msg); 102 | } 103 | 104 | /** 105 | * Send a DEBUG log message 106 | * 107 | * @param tag 108 | * @param msg 109 | * @param throwable 110 | */ 111 | public static void d(String tag, String msg, Throwable throwable) { 112 | if (sLevel > LEVEL_DEBUG) { 113 | return; 114 | } 115 | Log.d(tag, msg, throwable); 116 | } 117 | 118 | /** 119 | * Send an INFO log message 120 | * 121 | * @param tag 122 | * @param msg 123 | */ 124 | public static void i(String tag, String msg) { 125 | if (sLevel > LEVEL_INFO) { 126 | return; 127 | } 128 | Log.i(tag, msg); 129 | } 130 | 131 | /** 132 | * Send an INFO log message 133 | * 134 | * @param tag 135 | * @param msg 136 | * @param args 137 | */ 138 | public static void i(String tag, String msg, Object... args) { 139 | if (sLevel > LEVEL_INFO) { 140 | return; 141 | } 142 | if (args.length > 0) { 143 | msg = String.format(msg, args); 144 | } 145 | Log.i(tag, msg); 146 | } 147 | 148 | /** 149 | * Send an INFO log message 150 | * 151 | * @param tag 152 | * @param msg 153 | */ 154 | public static void i(String tag, String msg, Throwable throwable) { 155 | if (sLevel > LEVEL_INFO) { 156 | return; 157 | } 158 | Log.i(tag, msg, throwable); 159 | } 160 | 161 | /** 162 | * Send a WARNING log message 163 | * 164 | * @param tag 165 | * @param msg 166 | */ 167 | public static void w(String tag, String msg) { 168 | if (sLevel > LEVEL_WARNING) { 169 | return; 170 | } 171 | Log.w(tag, msg); 172 | } 173 | 174 | /** 175 | * Send a WARNING log message 176 | * 177 | * @param tag 178 | * @param msg 179 | * @param args 180 | */ 181 | public static void w(String tag, String msg, Object... args) { 182 | if (sLevel > LEVEL_WARNING) { 183 | return; 184 | } 185 | if (args.length > 0) { 186 | msg = String.format(msg, args); 187 | } 188 | Log.w(tag, msg); 189 | } 190 | 191 | /** 192 | * Send a WARNING log message 193 | * 194 | * @param tag 195 | * @param msg 196 | * @param throwable 197 | */ 198 | public static void w(String tag, String msg, Throwable throwable) { 199 | if (sLevel > LEVEL_WARNING) { 200 | return; 201 | } 202 | Log.w(tag, msg, throwable); 203 | } 204 | 205 | /** 206 | * Send an ERROR log message 207 | * 208 | * @param tag 209 | * @param msg 210 | */ 211 | public static void e(String tag, String msg) { 212 | if (sLevel > LEVEL_ERROR) { 213 | return; 214 | } 215 | Log.e(tag, msg); 216 | } 217 | 218 | /** 219 | * Send an ERROR log message 220 | * 221 | * @param tag 222 | * @param msg 223 | * @param args 224 | */ 225 | public static void e(String tag, String msg, Object... args) { 226 | if (sLevel > LEVEL_ERROR) { 227 | return; 228 | } 229 | if (args.length > 0) { 230 | msg = String.format(msg, args); 231 | } 232 | Log.e(tag, msg); 233 | } 234 | 235 | /** 236 | * Send an ERROR log message 237 | * 238 | * @param tag 239 | * @param msg 240 | * @param throwable 241 | */ 242 | public static void e(String tag, String msg, Throwable throwable) { 243 | if (sLevel > LEVEL_ERROR) { 244 | return; 245 | } 246 | Log.e(tag, msg, throwable); 247 | } 248 | 249 | /** 250 | * Send a FATAL ERROR log message 251 | * 252 | * @param tag 253 | * @param msg 254 | */ 255 | public static void f(String tag, String msg) { 256 | if (sLevel > LEVEL_FATAL) { 257 | return; 258 | } 259 | Log.wtf(tag, msg); 260 | } 261 | 262 | /** 263 | * Send a FATAL ERROR log message 264 | * 265 | * @param tag 266 | * @param msg 267 | * @param args 268 | */ 269 | public static void f(String tag, String msg, Object... args) { 270 | if (sLevel > LEVEL_FATAL) { 271 | return; 272 | } 273 | if (args.length > 0) { 274 | msg = String.format(msg, args); 275 | } 276 | Log.wtf(tag, msg); 277 | } 278 | 279 | /** 280 | * Send a FATAL ERROR log message 281 | * 282 | * @param tag 283 | * @param msg 284 | * @param throwable 285 | */ 286 | public static void f(String tag, String msg, Throwable throwable) { 287 | if (sLevel > LEVEL_FATAL) { 288 | return; 289 | } 290 | Log.wtf(tag, msg, throwable); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/adapter/CommonAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.LayoutRes; 5 | import android.support.annotation.NonNull; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import com.zly.www.easyrecyclerview.adapter.viewholder.BaseViewHolder; 13 | import com.zly.www.easyrecyclerview.adapter.viewholder.FooterViewHolder; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | import java.util.Collections; 18 | import java.util.Comparator; 19 | import java.util.List; 20 | 21 | /** 22 | * 通用adapter 23 | * Created by zly on 2016/10/27 0027. 24 | */ 25 | 26 | public abstract class CommonAdapter extends RecyclerView.Adapter { 27 | 28 | public static final int TYPE_FOOTER = 233333; 29 | private final String TAG = "CommonAdapter"; 30 | private final Object mLock = new Object(); 31 | private Context mContext; 32 | 33 | private View mFooter; 34 | private RecyclerView mRecyclerView; 35 | private List mDatas = new ArrayList<>(); 36 | 37 | @Override 38 | public int getItemCount() { 39 | return mDatas == null || mDatas.size() == 0 ? 0 : mFooter == null ? mDatas.size() : mDatas.size() + 1; 40 | } 41 | 42 | @Override 43 | public int getItemViewType(int position) { 44 | if (getFooter() != null && getItemCount() - 1 == position) { 45 | return TYPE_FOOTER; 46 | } else { 47 | return super.getItemViewType(position); 48 | } 49 | } 50 | 51 | @Override 52 | public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 53 | if (mContext == null) 54 | mContext = parent.getContext(); 55 | if (mRecyclerView == null) 56 | mRecyclerView = (RecyclerView) parent; 57 | 58 | if (viewType == TYPE_FOOTER) { 59 | return new FooterViewHolder(mFooter); 60 | } 61 | return createCustomViewHolder(parent, viewType); 62 | } 63 | 64 | @Override 65 | public void onBindViewHolder(BaseViewHolder holder, int position) { 66 | if (getItemViewType(position) != TYPE_FOOTER) 67 | bindCustomViewHolder((VH) holder, mDatas.get(position), position); 68 | } 69 | 70 | public View inflateView(@LayoutRes int resId, ViewGroup parent) { 71 | return LayoutInflater.from(mContext).inflate(resId, parent, false); 72 | } 73 | 74 | public Context getContext() { 75 | return mContext; 76 | } 77 | 78 | public View getFooter() { 79 | return mFooter; 80 | } 81 | 82 | public void setFooter(View footer) { 83 | mFooter = footer; 84 | } 85 | 86 | public void removeFooter() { 87 | mFooter = null; 88 | } 89 | 90 | public void add(@NonNull T object) { 91 | synchronized (mLock) { 92 | if (null != mDatas) { 93 | mDatas.add(object); 94 | notifyItemInserted(mDatas.size() - 1); 95 | } 96 | } 97 | } 98 | 99 | public void addAll(@NonNull Collection collection) { 100 | synchronized (mLock) { 101 | if (null != mDatas) { 102 | mDatas.addAll(collection); 103 | 104 | if (mDatas.size() - collection.size() != 0) { 105 | notifyItemRangeInserted(mDatas.size() - collection.size(), collection.size()); 106 | } else { 107 | notifyDataSetChanged(); 108 | } 109 | } 110 | } 111 | 112 | } 113 | 114 | @SafeVarargs 115 | public final void addAll(@NonNull T... items) { 116 | synchronized (mLock) { 117 | if (null != mDatas) { 118 | Collections.addAll(mDatas, items); 119 | if (mDatas.size() - items.length != 0) { 120 | notifyItemRangeInserted(mDatas.size() - items.length, items.length); 121 | } else { 122 | notifyDataSetChanged(); 123 | } 124 | } 125 | } 126 | } 127 | 128 | public void insert(@NonNull T object, int index) { 129 | if (mDatas == null || index < 0 || index > mDatas.size()) { 130 | Log.i(TAG, "insert: index error"); 131 | return; 132 | } 133 | synchronized (mLock) { 134 | if (null != mDatas) { 135 | mDatas.add(index, object); 136 | notifyItemInserted(index); 137 | 138 | if (index == 0) 139 | scrollTop(); 140 | } 141 | } 142 | } 143 | 144 | public void insertAll(@NonNull Collection collection, int index) { 145 | if (mDatas == null || index < 0 || index > mDatas.size()) { 146 | Log.i(TAG, "insertAll: index error"); 147 | return; 148 | } 149 | synchronized (mLock) { 150 | if (null != mDatas) { 151 | mDatas.addAll(index, collection); 152 | notifyItemRangeInserted(index, collection.size()); 153 | 154 | if (index == 0) 155 | scrollTop(); 156 | } 157 | } 158 | } 159 | 160 | public void remove(int index) { 161 | if (mDatas == null || index < 0 || index > mDatas.size() - 1) { 162 | Log.i(TAG, "remove: index error"); 163 | return; 164 | } 165 | synchronized (mLock) { 166 | mDatas.remove(index); 167 | notifyItemRemoved(index); 168 | } 169 | } 170 | 171 | public boolean remove(@NonNull T object) { 172 | int removeIndex = -1; 173 | boolean removeSuccess = false; 174 | 175 | if (mDatas == null || mDatas.size() == 0) { 176 | Log.i(TAG, "remove fail datas emply"); 177 | return false; 178 | } 179 | 180 | synchronized (mLock) { 181 | removeIndex = mDatas.indexOf(object); 182 | removeSuccess = mDatas.remove(object); 183 | } 184 | 185 | if (removeSuccess) { 186 | notifyItemRemoved(removeIndex); 187 | return true; 188 | } 189 | return false; 190 | } 191 | 192 | public void clear() { 193 | synchronized (mLock) { 194 | if (mDatas != null) { 195 | mDatas.clear(); 196 | } 197 | } 198 | notifyDataSetChanged(); 199 | } 200 | 201 | public void sort(Comparator comparator) { 202 | synchronized (mLock) { 203 | if (mDatas != null) { 204 | Collections.sort(mDatas, comparator); 205 | } 206 | } 207 | notifyDataSetChanged(); 208 | } 209 | 210 | public void update(@NonNull List mDatas) { 211 | synchronized (mLock) { 212 | this.mDatas = mDatas; 213 | } 214 | notifyDataSetChanged(); 215 | } 216 | 217 | public T getItem(int position) { 218 | return mDatas.get(position); 219 | } 220 | 221 | public int getPosition(T item) { 222 | return mDatas.indexOf(item); 223 | } 224 | 225 | public List getData() { 226 | if (null == mDatas) 227 | mDatas = new ArrayList<>(); 228 | return mDatas; 229 | } 230 | 231 | /** 232 | * 处理insert index为0时新数据未显示问题 233 | */ 234 | private void scrollTop() { 235 | if (null != mRecyclerView) 236 | mRecyclerView.scrollToPosition(0); 237 | } 238 | 239 | public abstract VH createCustomViewHolder(ViewGroup parent, int viewType); 240 | 241 | public abstract void bindCustomViewHolder(VH holder, T t, int position); 242 | 243 | } 244 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/PtrClassicDefaultHeader.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.content.res.TypedArray; 6 | import android.text.TextUtils; 7 | import android.util.AttributeSet; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.animation.LinearInterpolator; 11 | import android.view.animation.RotateAnimation; 12 | import android.widget.FrameLayout; 13 | import android.widget.TextView; 14 | 15 | import com.zly.www.easyrecyclerview.R; 16 | import com.zly.www.easyrecyclerview.ptrlib.indicator.PtrIndicator; 17 | 18 | import java.text.SimpleDateFormat; 19 | import java.util.Date; 20 | 21 | 22 | public class PtrClassicDefaultHeader extends FrameLayout implements PtrUIHandler { 23 | 24 | private final static String KEY_SharedPreferences = "cube_ptr_classic_last_update"; 25 | private static SimpleDateFormat sDataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 26 | private int mRotateAniTime = 150; 27 | private RotateAnimation mFlipAnimation; 28 | private RotateAnimation mReverseFlipAnimation; 29 | private TextView mTitleTextView; 30 | private View mRotateView; 31 | private View mProgressBar; 32 | private long mLastUpdateTime = -1; 33 | private TextView mLastUpdateTextView; 34 | private String mLastUpdateTimeKey; 35 | private boolean mShouldShowLastUpdate; 36 | 37 | private LastUpdateTimeUpdater mLastUpdateTimeUpdater = new LastUpdateTimeUpdater(); 38 | 39 | public PtrClassicDefaultHeader(Context context) { 40 | super(context); 41 | initViews(null); 42 | } 43 | 44 | public PtrClassicDefaultHeader(Context context, AttributeSet attrs) { 45 | super(context, attrs); 46 | initViews(attrs); 47 | } 48 | 49 | public PtrClassicDefaultHeader(Context context, AttributeSet attrs, int defStyle) { 50 | super(context, attrs, defStyle); 51 | initViews(attrs); 52 | } 53 | 54 | protected void initViews(AttributeSet attrs) { 55 | TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.PtrClassicHeader, 0, 0); 56 | if (arr != null) { 57 | mRotateAniTime = arr.getInt(R.styleable.PtrClassicHeader_ptr_rotate_ani_time, mRotateAniTime); 58 | } 59 | buildAnimation(); 60 | View header = LayoutInflater.from(getContext()).inflate(R.layout.cube_ptr_classic_default_header, this); 61 | 62 | mRotateView = header.findViewById(R.id.ptr_classic_header_rotate_view); 63 | 64 | mTitleTextView = (TextView) header.findViewById(R.id.ptr_classic_header_rotate_view_header_title); 65 | mLastUpdateTextView = (TextView) header.findViewById(R.id.ptr_classic_header_rotate_view_header_last_update); 66 | mProgressBar = header.findViewById(R.id.ptr_classic_header_rotate_view_progressbar); 67 | 68 | resetView(); 69 | } 70 | 71 | @Override 72 | protected void onDetachedFromWindow() { 73 | super.onDetachedFromWindow(); 74 | if (mLastUpdateTimeUpdater != null) { 75 | mLastUpdateTimeUpdater.stop(); 76 | } 77 | } 78 | 79 | public void setRotateAniTime(int time) { 80 | if (time == mRotateAniTime || time == 0) { 81 | return; 82 | } 83 | mRotateAniTime = time; 84 | buildAnimation(); 85 | } 86 | 87 | /** 88 | * Specify the last update time by this key string 89 | * 90 | * @param key 91 | */ 92 | public void setLastUpdateTimeKey(String key) { 93 | if (TextUtils.isEmpty(key)) { 94 | return; 95 | } 96 | mLastUpdateTimeKey = key; 97 | } 98 | 99 | /** 100 | * Using an object to specify the last update time. 101 | * 102 | * @param object 103 | */ 104 | public void setLastUpdateTimeRelateObject(Object object) { 105 | setLastUpdateTimeKey(object.getClass().getName()); 106 | } 107 | 108 | private void buildAnimation() { 109 | mFlipAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); 110 | mFlipAnimation.setInterpolator(new LinearInterpolator()); 111 | mFlipAnimation.setDuration(mRotateAniTime); 112 | mFlipAnimation.setFillAfter(true); 113 | 114 | mReverseFlipAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); 115 | mReverseFlipAnimation.setInterpolator(new LinearInterpolator()); 116 | mReverseFlipAnimation.setDuration(mRotateAniTime); 117 | mReverseFlipAnimation.setFillAfter(true); 118 | } 119 | 120 | private void resetView() { 121 | hideRotateView(); 122 | mProgressBar.setVisibility(INVISIBLE); 123 | } 124 | 125 | private void hideRotateView() { 126 | mRotateView.clearAnimation(); 127 | mRotateView.setVisibility(INVISIBLE); 128 | } 129 | 130 | @Override 131 | public void onUIReset(PtrFrameLayout frame) { 132 | resetView(); 133 | mShouldShowLastUpdate = true; 134 | tryUpdateLastUpdateTime(); 135 | } 136 | 137 | @Override 138 | public void onUIRefreshPrepare(PtrFrameLayout frame) { 139 | 140 | mShouldShowLastUpdate = true; 141 | tryUpdateLastUpdateTime(); 142 | mLastUpdateTimeUpdater.start(); 143 | 144 | mProgressBar.setVisibility(INVISIBLE); 145 | 146 | mRotateView.setVisibility(VISIBLE); 147 | mTitleTextView.setVisibility(VISIBLE); 148 | if (frame.isPullToRefresh()) { 149 | mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down_to_refresh)); 150 | } else { 151 | mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down)); 152 | } 153 | } 154 | 155 | @Override 156 | public void onUIRefreshBegin(PtrFrameLayout frame) { 157 | mShouldShowLastUpdate = false; 158 | hideRotateView(); 159 | mProgressBar.setVisibility(VISIBLE); 160 | mTitleTextView.setVisibility(VISIBLE); 161 | mTitleTextView.setText(R.string.cube_ptr_refreshing); 162 | 163 | tryUpdateLastUpdateTime(); 164 | mLastUpdateTimeUpdater.stop(); 165 | } 166 | 167 | @Override 168 | public void onUIRefreshComplete(PtrFrameLayout frame) { 169 | 170 | hideRotateView(); 171 | mProgressBar.setVisibility(INVISIBLE); 172 | 173 | mTitleTextView.setVisibility(VISIBLE); 174 | mTitleTextView.setText(getResources().getString(R.string.cube_ptr_refresh_complete)); 175 | 176 | // update last update time 177 | SharedPreferences sharedPreferences = getContext().getSharedPreferences(KEY_SharedPreferences, 0); 178 | if (!TextUtils.isEmpty(mLastUpdateTimeKey)) { 179 | mLastUpdateTime = new Date().getTime(); 180 | sharedPreferences.edit().putLong(mLastUpdateTimeKey, mLastUpdateTime).commit(); 181 | } 182 | } 183 | 184 | private void tryUpdateLastUpdateTime() { 185 | if (TextUtils.isEmpty(mLastUpdateTimeKey) || !mShouldShowLastUpdate) { 186 | mLastUpdateTextView.setVisibility(GONE); 187 | } else { 188 | String time = getLastUpdateTime(); 189 | if (TextUtils.isEmpty(time)) { 190 | mLastUpdateTextView.setVisibility(GONE); 191 | } else { 192 | mLastUpdateTextView.setVisibility(VISIBLE); 193 | mLastUpdateTextView.setText(time); 194 | } 195 | } 196 | } 197 | 198 | private String getLastUpdateTime() { 199 | 200 | if (mLastUpdateTime == -1 && !TextUtils.isEmpty(mLastUpdateTimeKey)) { 201 | mLastUpdateTime = getContext().getSharedPreferences(KEY_SharedPreferences, 0).getLong(mLastUpdateTimeKey, -1); 202 | } 203 | if (mLastUpdateTime == -1) { 204 | return null; 205 | } 206 | long diffTime = new Date().getTime() - mLastUpdateTime; 207 | int seconds = (int) (diffTime / 1000); 208 | if (diffTime < 0) { 209 | return null; 210 | } 211 | if (seconds <= 0) { 212 | return null; 213 | } 214 | StringBuilder sb = new StringBuilder(); 215 | sb.append(getContext().getString(R.string.cube_ptr_last_update)); 216 | 217 | if (seconds < 60) { 218 | sb.append(seconds + getContext().getString(R.string.cube_ptr_seconds_ago)); 219 | } else { 220 | int minutes = (seconds / 60); 221 | if (minutes > 60) { 222 | int hours = minutes / 60; 223 | if (hours > 24) { 224 | Date date = new Date(mLastUpdateTime); 225 | sb.append(sDataFormat.format(date)); 226 | } else { 227 | sb.append(hours + getContext().getString(R.string.cube_ptr_hours_ago)); 228 | } 229 | 230 | } else { 231 | sb.append(minutes + getContext().getString(R.string.cube_ptr_minutes_ago)); 232 | } 233 | } 234 | return sb.toString(); 235 | } 236 | 237 | @Override 238 | public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { 239 | 240 | final int mOffsetToRefresh = frame.getOffsetToRefresh(); 241 | final int currentPos = ptrIndicator.getCurrentPosY(); 242 | final int lastPos = ptrIndicator.getLastPosY(); 243 | 244 | if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) { 245 | if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) { 246 | crossRotateLineFromBottomUnderTouch(frame); 247 | if (mRotateView != null) { 248 | mRotateView.clearAnimation(); 249 | mRotateView.startAnimation(mReverseFlipAnimation); 250 | } 251 | } 252 | } else if (currentPos > mOffsetToRefresh && lastPos <= mOffsetToRefresh) { 253 | if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) { 254 | crossRotateLineFromTopUnderTouch(frame); 255 | if (mRotateView != null) { 256 | mRotateView.clearAnimation(); 257 | mRotateView.startAnimation(mFlipAnimation); 258 | } 259 | } 260 | } 261 | } 262 | 263 | private void crossRotateLineFromTopUnderTouch(PtrFrameLayout frame) { 264 | if (!frame.isPullToRefresh()) { 265 | mTitleTextView.setVisibility(VISIBLE); 266 | mTitleTextView.setText(R.string.cube_ptr_release_to_refresh); 267 | } 268 | } 269 | 270 | private void crossRotateLineFromBottomUnderTouch(PtrFrameLayout frame) { 271 | mTitleTextView.setVisibility(VISIBLE); 272 | if (frame.isPullToRefresh()) { 273 | mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down_to_refresh)); 274 | } else { 275 | mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down)); 276 | } 277 | } 278 | 279 | private class LastUpdateTimeUpdater implements Runnable { 280 | 281 | private boolean mRunning = false; 282 | 283 | private void start() { 284 | if (TextUtils.isEmpty(mLastUpdateTimeKey)) { 285 | return; 286 | } 287 | mRunning = true; 288 | run(); 289 | } 290 | 291 | private void stop() { 292 | mRunning = false; 293 | removeCallbacks(this); 294 | } 295 | 296 | @Override 297 | public void run() { 298 | tryUpdateLastUpdateTime(); 299 | if (mRunning) { 300 | postDelayed(this, 1000); 301 | } 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/header/StoreHouseHeader.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib.header; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Matrix; 7 | import android.graphics.PointF; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.view.animation.Transformation; 11 | 12 | import com.zly.www.easyrecyclerview.ptrlib.PtrFrameLayout; 13 | import com.zly.www.easyrecyclerview.ptrlib.PtrUIHandler; 14 | import com.zly.www.easyrecyclerview.ptrlib.indicator.PtrIndicator; 15 | import com.zly.www.easyrecyclerview.ptrlib.util.PtrLocalDisplay; 16 | 17 | import java.util.ArrayList; 18 | 19 | 20 | public class StoreHouseHeader extends View implements PtrUIHandler { 21 | 22 | public ArrayList mItemList = new ArrayList(); 23 | 24 | private int mLineWidth = -1; 25 | private float mScale = 1; 26 | private int mDropHeight = -1; 27 | private float mInternalAnimationFactor = 0.7f; 28 | private int mHorizontalRandomness = -1; 29 | 30 | private float mProgress = 0; 31 | 32 | private int mDrawZoneWidth = 0; 33 | private int mDrawZoneHeight = 0; 34 | private int mOffsetX = 0; 35 | private int mOffsetY = 0; 36 | private float mBarDarkAlpha = 0.4f; 37 | private float mFromAlpha = 1.0f; 38 | private float mToAlpha = 0.4f; 39 | 40 | private int mLoadingAniDuration = 1000; 41 | private int mLoadingAniSegDuration = 1000; 42 | private int mLoadingAniItemDuration = 400; 43 | 44 | private Transformation mTransformation = new Transformation(); 45 | private boolean mIsInLoading = false; 46 | private AniController mAniController = new AniController(); 47 | private int mTextColor = Color.WHITE; 48 | 49 | public StoreHouseHeader(Context context) { 50 | super(context); 51 | initView(); 52 | } 53 | 54 | public StoreHouseHeader(Context context, AttributeSet attrs) { 55 | super(context, attrs); 56 | initView(); 57 | } 58 | 59 | public StoreHouseHeader(Context context, AttributeSet attrs, int defStyleAttr) { 60 | super(context, attrs, defStyleAttr); 61 | initView(); 62 | } 63 | 64 | private void initView() { 65 | PtrLocalDisplay.init(getContext()); 66 | mLineWidth = PtrLocalDisplay.dp2px(1); 67 | mDropHeight = PtrLocalDisplay.dp2px(40); 68 | mHorizontalRandomness = PtrLocalDisplay.SCREEN_WIDTH_PIXELS / 2; 69 | } 70 | 71 | private void setProgress(float progress) { 72 | mProgress = progress; 73 | } 74 | 75 | public int getLoadingAniDuration() { 76 | return mLoadingAniDuration; 77 | } 78 | 79 | public void setLoadingAniDuration(int duration) { 80 | mLoadingAniDuration = duration; 81 | mLoadingAniSegDuration = duration; 82 | } 83 | 84 | public StoreHouseHeader setLineWidth(int width) { 85 | mLineWidth = width; 86 | for (int i = 0; i < mItemList.size(); i++) { 87 | mItemList.get(i).setLineWidth(width); 88 | } 89 | return this; 90 | } 91 | 92 | public StoreHouseHeader setTextColor(int color) { 93 | mTextColor = color; 94 | for (int i = 0; i < mItemList.size(); i++) { 95 | mItemList.get(i).setColor(color); 96 | } 97 | return this; 98 | } 99 | 100 | public StoreHouseHeader setDropHeight(int height) { 101 | mDropHeight = height; 102 | return this; 103 | } 104 | 105 | @Override 106 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 107 | int height = getTopOffset() + mDrawZoneHeight + getBottomOffset(); 108 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 109 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 110 | 111 | mOffsetX = (getMeasuredWidth() - mDrawZoneWidth) / 2; 112 | mOffsetY = getTopOffset(); 113 | mDropHeight = getTopOffset(); 114 | } 115 | 116 | private int getTopOffset() { 117 | return getPaddingTop() + PtrLocalDisplay.dp2px(10); 118 | } 119 | 120 | private int getBottomOffset() { 121 | return getPaddingBottom() + PtrLocalDisplay.dp2px(10); 122 | } 123 | 124 | public void initWithString(String str) { 125 | initWithString(str, 25); 126 | } 127 | 128 | public void initWithString(String str, int fontSize) { 129 | ArrayList pointList = StoreHousePath.getPath(str, fontSize * 0.01f, 14); 130 | initWithPointList(pointList); 131 | } 132 | 133 | public void initWithStringArray(int id) { 134 | String[] points = getResources().getStringArray(id); 135 | ArrayList pointList = new ArrayList(); 136 | for (int i = 0; i < points.length; i++) { 137 | String[] x = points[i].split(","); 138 | float[] f = new float[4]; 139 | for (int j = 0; j < 4; j++) { 140 | f[j] = Float.parseFloat(x[j]); 141 | } 142 | pointList.add(f); 143 | } 144 | initWithPointList(pointList); 145 | } 146 | 147 | public float getScale() { 148 | return mScale; 149 | } 150 | 151 | public void setScale(float scale) { 152 | mScale = scale; 153 | } 154 | 155 | public void initWithPointList(ArrayList pointList) { 156 | 157 | float drawWidth = 0; 158 | float drawHeight = 0; 159 | boolean shouldLayout = mItemList.size() > 0; 160 | mItemList.clear(); 161 | for (int i = 0; i < pointList.size(); i++) { 162 | float[] line = pointList.get(i); 163 | PointF startPoint = new PointF(PtrLocalDisplay.dp2px(line[0]) * mScale, PtrLocalDisplay.dp2px(line[1]) * mScale); 164 | PointF endPoint = new PointF(PtrLocalDisplay.dp2px(line[2]) * mScale, PtrLocalDisplay.dp2px(line[3]) * mScale); 165 | 166 | drawWidth = Math.max(drawWidth, startPoint.x); 167 | drawWidth = Math.max(drawWidth, endPoint.x); 168 | 169 | drawHeight = Math.max(drawHeight, startPoint.y); 170 | drawHeight = Math.max(drawHeight, endPoint.y); 171 | 172 | StoreHouseBarItem item = new StoreHouseBarItem(i, startPoint, endPoint, mTextColor, mLineWidth); 173 | item.resetPosition(mHorizontalRandomness); 174 | mItemList.add(item); 175 | } 176 | mDrawZoneWidth = (int) Math.ceil(drawWidth); 177 | mDrawZoneHeight = (int) Math.ceil(drawHeight); 178 | if (shouldLayout) { 179 | requestLayout(); 180 | } 181 | } 182 | 183 | private void beginLoading() { 184 | mIsInLoading = true; 185 | mAniController.start(); 186 | invalidate(); 187 | } 188 | 189 | private void loadFinish() { 190 | mIsInLoading = false; 191 | mAniController.stop(); 192 | } 193 | 194 | @Override 195 | public void onDraw(Canvas canvas) { 196 | super.onDraw(canvas); 197 | float progress = mProgress; 198 | int c1 = canvas.save(); 199 | int len = mItemList.size(); 200 | 201 | for (int i = 0; i < len; i++) { 202 | 203 | canvas.save(); 204 | StoreHouseBarItem storeHouseBarItem = mItemList.get(i); 205 | float offsetX = mOffsetX + storeHouseBarItem.midPoint.x; 206 | float offsetY = mOffsetY + storeHouseBarItem.midPoint.y; 207 | 208 | if (mIsInLoading) { 209 | storeHouseBarItem.getTransformation(getDrawingTime(), mTransformation); 210 | canvas.translate(offsetX, offsetY); 211 | } else { 212 | 213 | if (progress == 0) { 214 | storeHouseBarItem.resetPosition(mHorizontalRandomness); 215 | continue; 216 | } 217 | 218 | float startPadding = (1 - mInternalAnimationFactor) * i / len; 219 | float endPadding = 1 - mInternalAnimationFactor - startPadding; 220 | 221 | // done 222 | if (progress == 1 || progress >= 1 - endPadding) { 223 | canvas.translate(offsetX, offsetY); 224 | storeHouseBarItem.setAlpha(mBarDarkAlpha); 225 | } else { 226 | float realProgress; 227 | if (progress <= startPadding) { 228 | realProgress = 0; 229 | } else { 230 | realProgress = Math.min(1, (progress - startPadding) / mInternalAnimationFactor); 231 | } 232 | offsetX += storeHouseBarItem.translationX * (1 - realProgress); 233 | offsetY += -mDropHeight * (1 - realProgress); 234 | Matrix matrix = new Matrix(); 235 | matrix.postRotate(360 * realProgress); 236 | matrix.postScale(realProgress, realProgress); 237 | matrix.postTranslate(offsetX, offsetY); 238 | storeHouseBarItem.setAlpha(mBarDarkAlpha * realProgress); 239 | canvas.concat(matrix); 240 | } 241 | } 242 | storeHouseBarItem.draw(canvas); 243 | canvas.restore(); 244 | } 245 | if (mIsInLoading) { 246 | invalidate(); 247 | } 248 | canvas.restoreToCount(c1); 249 | } 250 | 251 | @Override 252 | public void onUIReset(PtrFrameLayout frame) { 253 | loadFinish(); 254 | for (int i = 0; i < mItemList.size(); i++) { 255 | mItemList.get(i).resetPosition(mHorizontalRandomness); 256 | 257 | } 258 | } 259 | 260 | @Override 261 | public void onUIRefreshPrepare(PtrFrameLayout frame) { 262 | 263 | } 264 | 265 | @Override 266 | public void onUIRefreshBegin(PtrFrameLayout frame) { 267 | beginLoading(); 268 | } 269 | 270 | @Override 271 | public void onUIRefreshComplete(PtrFrameLayout frame) { 272 | loadFinish(); 273 | } 274 | 275 | @Override 276 | public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { 277 | 278 | float currentPercent = Math.min(1f, ptrIndicator.getCurrentPercent()); 279 | setProgress(currentPercent); 280 | invalidate(); 281 | } 282 | 283 | private class AniController implements Runnable { 284 | 285 | private int mTick = 0; 286 | private int mCountPerSeg = 0; 287 | private int mSegCount = 0; 288 | private int mInterval = 0; 289 | private boolean mRunning = true; 290 | 291 | private void start() { 292 | mRunning = true; 293 | mTick = 0; 294 | 295 | mInterval = mLoadingAniDuration / mItemList.size(); 296 | mCountPerSeg = mLoadingAniSegDuration / mInterval; 297 | mSegCount = mItemList.size() / mCountPerSeg + 1; 298 | run(); 299 | } 300 | 301 | @Override 302 | public void run() { 303 | 304 | int pos = mTick % mCountPerSeg; 305 | for (int i = 0; i < mSegCount; i++) { 306 | 307 | int index = i * mCountPerSeg + pos; 308 | if (index > mTick) { 309 | continue; 310 | } 311 | 312 | index = index % mItemList.size(); 313 | StoreHouseBarItem item = mItemList.get(index); 314 | 315 | item.setFillAfter(false); 316 | item.setFillEnabled(true); 317 | item.setFillBefore(false); 318 | item.setDuration(mLoadingAniItemDuration); 319 | item.start(mFromAlpha, mToAlpha); 320 | } 321 | 322 | mTick++; 323 | if (mRunning) { 324 | postDelayed(this, mInterval); 325 | } 326 | } 327 | 328 | private void stop() { 329 | mRunning = false; 330 | removeCallbacks(this); 331 | } 332 | } 333 | } -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/header/StoreHousePath.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview.ptrlib.header; 2 | 3 | import android.util.SparseArray; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Created by srain on 11/7/14. 9 | */ 10 | public class StoreHousePath { 11 | 12 | private static final SparseArray sPointList; 13 | 14 | static { 15 | sPointList = new SparseArray(); 16 | float[][] LETTERS = new float[][]{ 17 | new float[]{ 18 | // A 19 | 24, 0, 1, 22, 20 | 1, 22, 1, 72, 21 | 24, 0, 47, 22, 22 | 47, 22, 47, 72, 23 | 1, 48, 47, 48 24 | }, 25 | 26 | new float[]{ 27 | // B 28 | 0, 0, 0, 72, 29 | 0, 0, 37, 0, 30 | 37, 0, 47, 11, 31 | 47, 11, 47, 26, 32 | 47, 26, 38, 36, 33 | 38, 36, 0, 36, 34 | 38, 36, 47, 46, 35 | 47, 46, 47, 61, 36 | 47, 61, 38, 71, 37 | 37, 72, 0, 72, 38 | }, 39 | 40 | new float[]{ 41 | // C 42 | 47, 0, 0, 0, 43 | 0, 0, 0, 72, 44 | 0, 72, 47, 72, 45 | }, 46 | 47 | new float[]{ 48 | // D 49 | 0, 0, 0, 72, 50 | 0, 0, 24, 0, 51 | 24, 0, 47, 22, 52 | 47, 22, 47, 48, 53 | 47, 48, 23, 72, 54 | 23, 72, 0, 72, 55 | }, 56 | 57 | new float[]{ 58 | // E 59 | 0, 0, 0, 72, 60 | 0, 0, 47, 0, 61 | 0, 36, 37, 36, 62 | 0, 72, 47, 72, 63 | }, 64 | 65 | new float[]{ 66 | // F 67 | 0, 0, 0, 72, 68 | 0, 0, 47, 0, 69 | 0, 36, 37, 36, 70 | }, 71 | 72 | new float[]{ 73 | // G 74 | 47, 23, 47, 0, 75 | 47, 0, 0, 0, 76 | 0, 0, 0, 72, 77 | 0, 72, 47, 72, 78 | 47, 72, 47, 48, 79 | 47, 48, 24, 48, 80 | }, 81 | 82 | new float[]{ 83 | // H 84 | 0, 0, 0, 72, 85 | 0, 36, 47, 36, 86 | 47, 0, 47, 72, 87 | }, 88 | 89 | new float[]{ 90 | // I 91 | 0, 0, 47, 0, 92 | 24, 0, 24, 72, 93 | 0, 72, 47, 72, 94 | }, 95 | 96 | new float[]{ 97 | // J 98 | 47, 0, 47, 72, 99 | 47, 72, 24, 72, 100 | 24, 72, 0, 48, 101 | }, 102 | 103 | new float[]{ 104 | // K 105 | 0, 0, 0, 72, 106 | 47, 0, 3, 33, 107 | 3, 38, 47, 72, 108 | }, 109 | 110 | new float[]{ 111 | // L 112 | 0, 0, 0, 72, 113 | 0, 72, 47, 72, 114 | }, 115 | 116 | new float[]{ 117 | // M 118 | 0, 0, 0, 72, 119 | 0, 0, 24, 23, 120 | 24, 23, 47, 0, 121 | 47, 0, 47, 72, 122 | }, 123 | 124 | new float[]{ 125 | // N 126 | 0, 0, 0, 72, 127 | 0, 0, 47, 72, 128 | 47, 72, 47, 0, 129 | }, 130 | 131 | new float[]{ 132 | // O 133 | 0, 0, 0, 72, 134 | 0, 72, 47, 72, 135 | 47, 72, 47, 0, 136 | 47, 0, 0, 0, 137 | }, 138 | 139 | new float[]{ 140 | // P 141 | 0, 0, 0, 72, 142 | 0, 0, 47, 0, 143 | 47, 0, 47, 36, 144 | 47, 36, 0, 36, 145 | }, 146 | 147 | new float[]{ 148 | // Q 149 | 0, 0, 0, 72, 150 | 0, 72, 23, 72, 151 | 23, 72, 47, 48, 152 | 47, 48, 47, 0, 153 | 47, 0, 0, 0, 154 | 24, 28, 47, 71, 155 | }, 156 | 157 | new float[]{ 158 | // R 159 | 0, 0, 0, 72, 160 | 0, 0, 47, 0, 161 | 47, 0, 47, 36, 162 | 47, 36, 0, 36, 163 | 0, 37, 47, 72, 164 | }, 165 | 166 | new float[]{ 167 | // S 168 | 47, 0, 0, 0, 169 | 0, 0, 0, 36, 170 | 0, 36, 47, 36, 171 | 47, 36, 47, 72, 172 | 47, 72, 0, 72, 173 | }, 174 | 175 | new float[]{ 176 | // T 177 | 0, 0, 47, 0, 178 | 24, 0, 24, 72, 179 | }, 180 | 181 | new float[]{ 182 | // U 183 | 0, 0, 0, 72, 184 | 0, 72, 47, 72, 185 | 47, 72, 47, 0, 186 | }, 187 | 188 | new float[]{ 189 | // V 190 | 0, 0, 24, 72, 191 | 24, 72, 47, 0, 192 | }, 193 | 194 | new float[]{ 195 | // W 196 | 0, 0, 0, 72, 197 | 0, 72, 24, 49, 198 | 24, 49, 47, 72, 199 | 47, 72, 47, 0 200 | }, 201 | 202 | new float[]{ 203 | // X 204 | 0, 0, 47, 72, 205 | 47, 0, 0, 72 206 | }, 207 | 208 | new float[]{ 209 | // Y 210 | 0, 0, 24, 23, 211 | 47, 0, 24, 23, 212 | 24, 23, 24, 72 213 | }, 214 | 215 | new float[]{ 216 | // Z 217 | 0, 0, 47, 0, 218 | 47, 0, 0, 72, 219 | 0, 72, 47, 72 220 | }, 221 | }; 222 | final float[][] NUMBERS = new float[][]{ 223 | new float[]{ 224 | // 0 225 | 0, 0, 0, 72, 226 | 0, 72, 47, 72, 227 | 47, 72, 47, 0, 228 | 47, 0, 0, 0, 229 | }, 230 | new float[]{ 231 | // 1 232 | 24, 0, 24, 72, 233 | }, 234 | 235 | new float[]{ 236 | // 2 237 | 0, 0, 47, 0, 238 | 47, 0, 47, 36, 239 | 47, 36, 0, 36, 240 | 0, 36, 0, 72, 241 | 0, 72, 47, 72 242 | }, 243 | 244 | new float[]{ 245 | // 3 246 | 0, 0, 47, 0, 247 | 47, 0, 47, 36, 248 | 47, 36, 0, 36, 249 | 47, 36, 47, 72, 250 | 47, 72, 0, 72, 251 | }, 252 | 253 | new float[]{ 254 | // 4 255 | 0, 0, 0, 36, 256 | 0, 36, 47, 36, 257 | 47, 0, 47, 72, 258 | }, 259 | 260 | new float[]{ 261 | // 5 262 | 0, 0, 0, 36, 263 | 0, 36, 47, 36, 264 | 47, 36, 47, 72, 265 | 47, 72, 0, 72, 266 | 0, 0, 47, 0 267 | }, 268 | 269 | new float[]{ 270 | // 6 271 | 0, 0, 0, 72, 272 | 0, 72, 47, 72, 273 | 47, 72, 47, 36, 274 | 47, 36, 0, 36 275 | }, 276 | 277 | new float[]{ 278 | // 7 279 | 0, 0, 47, 0, 280 | 47, 0, 47, 72 281 | }, 282 | 283 | new float[]{ 284 | // 8 285 | 0, 0, 0, 72, 286 | 0, 72, 47, 72, 287 | 47, 72, 47, 0, 288 | 47, 0, 0, 0, 289 | 0, 36, 47, 36 290 | }, 291 | 292 | new float[]{ 293 | // 9 294 | 47, 0, 0, 0, 295 | 0, 0, 0, 36, 296 | 0, 36, 47, 36, 297 | 47, 0, 47, 72, 298 | } 299 | }; 300 | // A - Z 301 | for (int i = 0; i < LETTERS.length; i++) { 302 | sPointList.append(i + 65, LETTERS[i]); 303 | } 304 | // a - z 305 | for (int i = 0; i < LETTERS.length; i++) { 306 | sPointList.append(i + 65 + 32, LETTERS[i]); 307 | } 308 | // 0 - 9 309 | for (int i = 0; i < NUMBERS.length; i++) { 310 | sPointList.append(i + 48, NUMBERS[i]); 311 | } 312 | // blank 313 | addChar(' ', new float[]{}); 314 | // - 315 | addChar('-', new float[]{ 316 | 0, 36, 47, 36 317 | }); 318 | // . 319 | addChar('.', new float[]{ 320 | 24, 60, 24, 72 321 | }); 322 | } 323 | 324 | public static void addChar(char c, float[] points) { 325 | sPointList.append(c, points); 326 | } 327 | 328 | public static ArrayList getPath(String str) { 329 | return getPath(str, 1, 14); 330 | } 331 | 332 | /** 333 | * @param str 334 | * @param scale 335 | * @param gapBetweenLetter 336 | * @return ArrayList of float[] {x1, y1, x2, y2} 337 | */ 338 | public static ArrayList getPath(String str, float scale, int gapBetweenLetter) { 339 | ArrayList list = new ArrayList(); 340 | float offsetForWidth = 0; 341 | for (int i = 0; i < str.length(); i++) { 342 | int pos = str.charAt(i); 343 | int key = sPointList.indexOfKey(pos); 344 | if (key == -1) { 345 | continue; 346 | } 347 | float[] points = sPointList.get(pos); 348 | int pointCount = points.length / 4; 349 | 350 | for (int j = 0; j < pointCount; j++) { 351 | float[] line = new float[4]; 352 | for (int k = 0; k < 4; k++) { 353 | float l = points[j * 4 + k]; 354 | // x 355 | if (k % 2 == 0) { 356 | line[k] = (l + offsetForWidth) * scale; 357 | } 358 | // y 359 | else { 360 | line[k] = l * scale; 361 | } 362 | } 363 | list.add(line); 364 | } 365 | offsetForWidth += 57 + gapBetweenLetter; 366 | } 367 | return list; 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/EasyRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.zly.www.easyrecyclerview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.v7.widget.GridLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewStub; 12 | import android.widget.FrameLayout; 13 | 14 | import com.zly.www.easyrecyclerview.adapter.CommonAdapter; 15 | import com.zly.www.easyrecyclerview.footer.ErvLoadUIHandle; 16 | import com.zly.www.easyrecyclerview.listener.OnLoadListener; 17 | import com.zly.www.easyrecyclerview.listener.OnRefreshListener; 18 | import com.zly.www.easyrecyclerview.ptrlib.PtrDefaultHandler; 19 | import com.zly.www.easyrecyclerview.ptrlib.PtrFrameLayout; 20 | import com.zly.www.easyrecyclerview.ptrlib.PtrHandler; 21 | import com.zly.www.easyrecyclerview.ptrlib.PtrUIHandler; 22 | import com.zly.www.easyrecyclerview.utils.LayoutManagerUtil; 23 | 24 | 25 | /** 26 | * 刷新 27 | * Created by zly on 2016/10/20 0020. 28 | */ 29 | 30 | public class EasyRecyclerView extends FrameLayout { 31 | 32 | private final String TAG = getClass().getSimpleName(); 33 | //Erv属性 34 | private int mNumLoadMore;//最后可见条目 + mNumLoadMore > total 触发加载更多 35 | 36 | //Ptr属性 37 | private float mResistance; 38 | private int mDurationToClose; 39 | private int mDurationToCloseHeader; 40 | private float mRatio; 41 | private boolean mKeepHeaderWhenRefresh; 42 | private boolean mPullToRefresh; 43 | 44 | private boolean mLoadingMore = false; 45 | private boolean mReviseMovePoi = false;//是否需要修正滑动位置 46 | private int mMovePoi = 0; 47 | 48 | private PtrFrameLayout mPtrFrame; 49 | private RecyclerView mRecyclerView; 50 | private OnRefreshListener mRefreshListener; 51 | private OnLoadListener mOnLoadListener; 52 | private View mFooterView; 53 | private CommonAdapter mAdapter; 54 | private ErvLoadUIHandle mLoadUIHandler; 55 | private View mEmptyView; 56 | private ViewStub mStubEmpty; 57 | private int mEmptyRes; 58 | 59 | public EasyRecyclerView(Context context) { 60 | this(context, null); 61 | } 62 | 63 | public EasyRecyclerView(Context context, AttributeSet attrs) { 64 | this(context, attrs, 0); 65 | } 66 | 67 | public EasyRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { 68 | super(context, attrs, defStyleAttr); 69 | initAttr(context, attrs); 70 | initView(context); 71 | } 72 | 73 | public void initAttr(Context context, AttributeSet attrs) { 74 | TypedArray PtrAttr = context.obtainStyledAttributes(attrs, R.styleable.PtrFrameLayout); 75 | TypedArray ErvAttr = context.obtainStyledAttributes(attrs, R.styleable.EasyRecyclerView); 76 | 77 | //初始化Erv 78 | mNumLoadMore = ErvAttr.getInteger(R.styleable.EasyRecyclerView_number_load_more, 4); 79 | mEmptyRes = ErvAttr.getResourceId(R.styleable.EasyRecyclerView_emply_layout, 0); 80 | 81 | //初始化Ptr 82 | mResistance = PtrAttr.getFloat(R.styleable.PtrFrameLayout_ptr_resistance, 1.7f); 83 | mRatio = PtrAttr.getFloat(R.styleable.PtrFrameLayout_ptr_ratio_of_header_height_to_refresh, 1.2f); 84 | mDurationToClose = PtrAttr.getInt(R.styleable.PtrFrameLayout_ptr_duration_to_close, 200); 85 | mDurationToCloseHeader = PtrAttr.getInt(R.styleable.PtrFrameLayout_ptr_duration_to_close_header, 1000); 86 | mKeepHeaderWhenRefresh = PtrAttr.getBoolean(R.styleable.PtrFrameLayout_ptr_keep_header_when_refresh, true); 87 | mPullToRefresh = PtrAttr.getBoolean(R.styleable.PtrFrameLayout_ptr_pull_to_fresh, false); 88 | 89 | ErvAttr.recycle(); 90 | PtrAttr.recycle(); 91 | } 92 | 93 | private void initView(Context context) { 94 | View view = LayoutInflater.from(context).inflate(R.layout.erv_layout, this); 95 | mPtrFrame = (PtrFrameLayout) view.findViewById(R.id.ptr); 96 | mRecyclerView = (RecyclerView) view.findViewById(R.id.list); 97 | mStubEmpty = (ViewStub) view.findViewById(R.id.stubEmpty); 98 | 99 | if (mEmptyRes != 0) {//加载没有数据状态下布局 100 | mStubEmpty.setLayoutResource(mEmptyRes); 101 | mEmptyView = mStubEmpty.inflate(); 102 | } 103 | 104 | initPtr(); 105 | initRecyclerView(); 106 | } 107 | 108 | 109 | /** 110 | * 初始化ptr属性 111 | */ 112 | private void initPtr() { 113 | mPtrFrame.setResistance(mResistance); 114 | mPtrFrame.setRatioOfHeaderHeightToRefresh(mRatio); 115 | mPtrFrame.setDurationToClose(mDurationToClose); 116 | mPtrFrame.setDurationToCloseHeader(mDurationToCloseHeader); 117 | mPtrFrame.setKeepHeaderWhenRefresh(mKeepHeaderWhenRefresh); 118 | mPtrFrame.setPullToRefresh(mPullToRefresh); 119 | 120 | 121 | mPtrFrame.setPtrHandler(new PtrHandler() { 122 | @Override 123 | public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) { 124 | return PtrDefaultHandler.checkContentCanBePulledDown(frame, mRecyclerView, header); 125 | } 126 | 127 | @Override 128 | public void onRefreshBegin(PtrFrameLayout frame) { 129 | if (mRefreshListener != null) { 130 | mRefreshListener.onRefreshListener(); 131 | } 132 | } 133 | }); 134 | } 135 | 136 | 137 | private void initRecyclerView() { 138 | mRecyclerView.addOnScrollListener(new ReviseMoveListener()); 139 | mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 140 | @Override 141 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 142 | super.onScrolled(recyclerView, dx, dy); 143 | if (mRecyclerView.getAdapter() != null && getLayoutManager() != null && mLoadUIHandler != null) { 144 | int itemCount = mRecyclerView.getAdapter().getItemCount(); 145 | int lastVisibleItemPosition = LayoutManagerUtil.getLastVisibleItemPosition(getLayoutManager()); 146 | if (dy > 0 && itemCount != 0 && lastVisibleItemPosition + mNumLoadMore > itemCount - 1 && 147 | !mLoadingMore && mLoadUIHandler.getState() == ErvLoadUIHandle.LOAD) { 148 | mLoadingMore = true; 149 | if (mOnLoadListener != null) { 150 | mOnLoadListener.onLoadListener(); 151 | } 152 | } 153 | } 154 | 155 | } 156 | 157 | @Override 158 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 159 | super.onScrollStateChanged(recyclerView, newState); 160 | } 161 | }); 162 | } 163 | 164 | public void setAdapter(CommonAdapter adapter) { 165 | if (adapter == null) { 166 | throw new NullPointerException("adapter 不能为空"); 167 | } 168 | this.mAdapter = adapter; 169 | mRecyclerView.setAdapter(mAdapter); 170 | mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { 171 | @Override 172 | public void onChanged() { 173 | super.onChanged(); 174 | dataComplete(); 175 | } 176 | 177 | @Override 178 | public void onItemRangeChanged(int positionStart, int itemCount) { 179 | super.onItemRangeChanged(positionStart, itemCount); 180 | dataComplete(); 181 | } 182 | 183 | @Override 184 | public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 185 | super.onItemRangeChanged(positionStart, itemCount, payload); 186 | dataComplete(); 187 | } 188 | 189 | @Override 190 | public void onItemRangeInserted(int positionStart, int itemCount) { 191 | super.onItemRangeInserted(positionStart, itemCount); 192 | dataComplete(); 193 | } 194 | 195 | @Override 196 | public void onItemRangeRemoved(int positionStart, int itemCount) { 197 | super.onItemRangeRemoved(positionStart, itemCount); 198 | dataComplete(); 199 | } 200 | 201 | @Override 202 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 203 | super.onItemRangeMoved(fromPosition, toPosition, itemCount); 204 | dataComplete(); 205 | } 206 | 207 | private void dataComplete() { 208 | refreshComplete(); 209 | loadComplete(); 210 | if (mEmptyView != null) { 211 | if (mAdapter.getItemCount() == 0) { 212 | showEmptyView(); 213 | } else { 214 | hideEmptyView(); 215 | } 216 | } 217 | 218 | } 219 | }); 220 | 221 | addFooter(); 222 | } 223 | 224 | public void showEmptyView() { 225 | mRecyclerView.setVisibility(View.GONE); 226 | mStubEmpty.setVisibility(View.VISIBLE); 227 | } 228 | 229 | public void hideEmptyView() { 230 | mRecyclerView.setVisibility(View.VISIBLE); 231 | mStubEmpty.setVisibility(View.GONE); 232 | } 233 | 234 | public void setFooterView(View footerView) { 235 | if (!(footerView instanceof ErvLoadUIHandle)) { 236 | throw new RuntimeException("footerView 必须实现ErvLoadUIHandler"); 237 | } 238 | this.mFooterView = footerView; 239 | this.mLoadUIHandler = (ErvLoadUIHandle) footerView; 240 | addFooter(); 241 | } 242 | 243 | private void addFooter() { 244 | if (mAdapter != null && mFooterView != null) { 245 | mAdapter.setFooter(mFooterView); 246 | } 247 | } 248 | 249 | public void removeFooter() { 250 | if (mAdapter != null) { 251 | mAdapter.removeFooter(); 252 | } 253 | } 254 | 255 | public void loading() { 256 | if (mLoadUIHandler != null) { 257 | mLoadUIHandler.onLoading(); 258 | } 259 | } 260 | 261 | public void noMore() { 262 | if (mLoadUIHandler != null) { 263 | mLoadUIHandler.onNoMore(); 264 | } 265 | } 266 | 267 | public void loadFail() { 268 | if (mOnLoadListener == null) { 269 | throw new RuntimeException("OnLoadListener 还未设置"); 270 | } 271 | 272 | if (mLoadUIHandler != null) { 273 | mLoadUIHandler.onLoadFail(mOnLoadListener); 274 | } 275 | } 276 | 277 | public void setHeaderView(View headerView) { 278 | if (!(headerView instanceof PtrUIHandler)) { 279 | throw new RuntimeException("headerView 必须实现PtrUIHandler"); 280 | } 281 | mPtrFrame.setHeaderView(headerView); 282 | mPtrFrame.addPtrUIHandler((PtrUIHandler) headerView); 283 | } 284 | 285 | public void setPtrHandler(PtrHandler ptrHandler) { 286 | mPtrFrame.setPtrHandler(ptrHandler); 287 | } 288 | 289 | public void autoRefresh() { 290 | mPtrFrame.autoRefresh(true, mDurationToCloseHeader); 291 | } 292 | 293 | public void autoRefresh(boolean atOnce) { 294 | mPtrFrame.autoRefresh(atOnce, mDurationToCloseHeader); 295 | } 296 | 297 | public PtrFrameLayout getPtrFrame() { 298 | return mPtrFrame; 299 | } 300 | 301 | public void setResistance(float resistance) { 302 | mPtrFrame.setResistance(resistance); 303 | } 304 | 305 | public void setRatioOfHeaderHeightToRefresh(float ratio) { 306 | mPtrFrame.setRatioOfHeaderHeightToRefresh(ratio); 307 | } 308 | 309 | public void setDurationToClose(int duration) { 310 | mPtrFrame.setDurationToClose(duration); 311 | } 312 | 313 | public void setDurationToCloseHeader(int duration) { 314 | mPtrFrame.setDurationToCloseHeader(duration); 315 | } 316 | 317 | public void setKeepHeaderWhenRefresh(boolean keepOrNot) { 318 | mPtrFrame.setKeepHeaderWhenRefresh(keepOrNot); 319 | } 320 | 321 | public void setPullToRefresh(boolean pullToRefresh) { 322 | mPtrFrame.setPullToRefresh(pullToRefresh); 323 | } 324 | 325 | public void setPinContent(boolean pinContent) { 326 | mPtrFrame.setPinContent(pinContent); 327 | } 328 | 329 | public RecyclerView getRecyclerView() { 330 | return mRecyclerView; 331 | } 332 | 333 | public void addItemDecoration(RecyclerView.ItemDecoration itemDecoration) { 334 | mRecyclerView.addItemDecoration(itemDecoration); 335 | } 336 | 337 | public void setLayoutManager(final RecyclerView.LayoutManager manager) { 338 | if (manager instanceof GridLayoutManager) { 339 | ((GridLayoutManager) manager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 340 | @Override 341 | public int getSpanSize(int position) { 342 | switch (mAdapter.getItemViewType(position)) { 343 | case CommonAdapter.TYPE_FOOTER: 344 | return ((GridLayoutManager) manager).getSpanCount(); 345 | default: 346 | return 1; 347 | } 348 | } 349 | }); 350 | } 351 | mRecyclerView.setLayoutManager(manager); 352 | } 353 | 354 | public RecyclerView.LayoutManager getLayoutManager() { 355 | return mRecyclerView.getLayoutManager(); 356 | } 357 | 358 | public void refreshComplete() { 359 | mPtrFrame.refreshComplete(); 360 | } 361 | 362 | public void loadComplete() { 363 | mLoadingMore = false; 364 | } 365 | 366 | public void setOnRefreshListener(OnRefreshListener listener) { 367 | this.mRefreshListener = listener; 368 | } 369 | 370 | public void setOnLoadListener(OnLoadListener listener) { 371 | this.mOnLoadListener = listener; 372 | } 373 | 374 | public void setOnEmptyViewClick(OnClickListener listener) { 375 | if (mEmptyView != null) { 376 | mEmptyView.setOnClickListener(listener); 377 | } 378 | } 379 | 380 | 381 | public void move(int position) { 382 | move(position, true); 383 | } 384 | 385 | public void move(int position, boolean isSmooth) { 386 | if (position < 0 || position >= mAdapter.getItemCount()) { 387 | Log.e(TAG, "move positon error"); 388 | return; 389 | } 390 | mMovePoi = position; 391 | 392 | if (isSmooth) { 393 | smoothScrollPosition(position); 394 | } else { 395 | scrollPosition(position); 396 | } 397 | } 398 | 399 | 400 | private void scrollPosition(int position) { 401 | int firstVisibleItemPosition = LayoutManagerUtil.getFirstVisibleItemPosition(getLayoutManager()); 402 | int lastVisibleItemPosition = LayoutManagerUtil.getLastVisibleItemPosition(getLayoutManager()); 403 | 404 | if (position < firstVisibleItemPosition) { 405 | mRecyclerView.scrollToPosition(position); 406 | } else if (position < lastVisibleItemPosition) { 407 | int top = mRecyclerView.getChildAt(position - firstVisibleItemPosition).getTop(); 408 | mRecyclerView.scrollBy(0, top); 409 | } else { 410 | mReviseMovePoi = true; 411 | //Todo scrollToPosition不触发RecyclerView.OnScrollListener监听所以没校正位置 改成smoothScrollToPosition()方法即可 这个问题后期google应该会修复所以这里未改成smoothScrollToPosition() 412 | mRecyclerView.scrollToPosition(position); 413 | } 414 | } 415 | 416 | private void smoothScrollPosition(int position) { 417 | int firstVisibleItemPosition = LayoutManagerUtil.getFirstVisibleItemPosition(getLayoutManager()); 418 | int lastVisibleItemPosition = LayoutManagerUtil.getLastVisibleItemPosition(getLayoutManager()); 419 | 420 | if (position < firstVisibleItemPosition) { 421 | mRecyclerView.smoothScrollToPosition(position); 422 | } else if (position < lastVisibleItemPosition) { 423 | int top = mRecyclerView.getChildAt(position - firstVisibleItemPosition).getTop(); 424 | mRecyclerView.smoothScrollBy(0, top); 425 | } else { 426 | mReviseMovePoi = true; 427 | mRecyclerView.smoothScrollToPosition(position); 428 | } 429 | } 430 | 431 | class ReviseMoveListener extends RecyclerView.OnScrollListener { 432 | @Override 433 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 434 | if (mReviseMovePoi && newState == RecyclerView.SCROLL_STATE_IDLE) { 435 | mReviseMovePoi = false; 436 | int n = mMovePoi - LayoutManagerUtil.getFirstVisibleItemPosition(getLayoutManager()); 437 | if (n >= 0 && n < mRecyclerView.getChildCount()) { 438 | int top = mRecyclerView.getChildAt(n).getTop(); 439 | mRecyclerView.scrollBy(0, top); 440 | } 441 | } 442 | } 443 | 444 | @Override 445 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 446 | } 447 | } 448 | 449 | } 450 | -------------------------------------------------------------------------------- /ErvLibrary/src/main/java/com/zly/www/easyrecyclerview/ptrlib/header/MaterialProgressDrawable.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 | package com.zly.www.easyrecyclerview.ptrlib.header; 17 | 18 | import android.content.Context; 19 | import android.content.res.Resources; 20 | import android.graphics.Canvas; 21 | import android.graphics.Color; 22 | import android.graphics.ColorFilter; 23 | import android.graphics.Paint; 24 | import android.graphics.Paint.Style; 25 | import android.graphics.Path; 26 | import android.graphics.PixelFormat; 27 | import android.graphics.RadialGradient; 28 | import android.graphics.Rect; 29 | import android.graphics.RectF; 30 | import android.graphics.Shader; 31 | import android.graphics.drawable.Animatable; 32 | import android.graphics.drawable.Drawable; 33 | import android.graphics.drawable.ShapeDrawable; 34 | import android.graphics.drawable.shapes.OvalShape; 35 | import android.os.Build; 36 | import android.util.DisplayMetrics; 37 | import android.view.View; 38 | import android.view.animation.AccelerateDecelerateInterpolator; 39 | import android.view.animation.Animation; 40 | import android.view.animation.Interpolator; 41 | import android.view.animation.LinearInterpolator; 42 | import android.view.animation.Transformation; 43 | 44 | import com.zly.www.easyrecyclerview.ptrlib.util.PtrLocalDisplay; 45 | 46 | import java.util.ArrayList; 47 | 48 | 49 | /** 50 | * Fancy progress indicator for Material theme. 51 | * It's taken from {@link android.support.v4.widget}. 52 | * I've done some slight changes. 53 | * 54 | * 55 | */ 56 | public class MaterialProgressDrawable extends Drawable implements Animatable { 57 | 58 | // Maps to ProgressBar.Large style 59 | public static final int LARGE = 0; 60 | // Maps to ProgressBar default style 61 | public static final int DEFAULT = 1; 62 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 63 | private static final Interpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); 64 | private static final Interpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); 65 | private static final Interpolator EASE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); 66 | // Maps to ProgressBar default style 67 | private static final int CIRCLE_DIAMETER = 40; 68 | private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 69 | private static final float STROKE_WIDTH = 2.5f; 70 | // Maps to ProgressBar.Large style 71 | private static final int CIRCLE_DIAMETER_LARGE = 56; 72 | private static final float CENTER_RADIUS_LARGE = 12.5f; 73 | private static final float STROKE_WIDTH_LARGE = 3f; 74 | /** 75 | * The duration of a single progress spin in milliseconds. 76 | */ 77 | private static final int ANIMATION_DURATION = 1000 * 80 / 60; 78 | /** 79 | * The number of points in the progress "star". 80 | */ 81 | private static final float NUM_POINTS = 5f; 82 | /** 83 | * Layout info for the arrowhead in dp 84 | */ 85 | private static final int ARROW_WIDTH = 10; 86 | private static final int ARROW_HEIGHT = 5; 87 | private static final float ARROW_OFFSET_ANGLE = 5; 88 | /** 89 | * Layout info for the arrowhead for the large spinner in dp 90 | */ 91 | private static final int ARROW_WIDTH_LARGE = 12; 92 | private static final int ARROW_HEIGHT_LARGE = 6; 93 | private static final float MAX_PROGRESS_ARC = .8f; 94 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 95 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 96 | private static final float SHADOW_RADIUS = 3.5f; 97 | private static final float X_OFFSET = 0f; 98 | private static final float Y_OFFSET = 1.75f; 99 | private final int[] COLORS = new int[]{ 100 | 0xFFC93437, 101 | 0xFF375BF1, 102 | 0xFFF7D23E, 103 | 0xFF34A350 104 | }; 105 | /** 106 | * The list of animators operating on this drawable. 107 | */ 108 | private final ArrayList mAnimators = new ArrayList(); 109 | /** 110 | * The indicator ring, used to manage animation state. 111 | */ 112 | private final Ring mRing; 113 | private final Callback mCallback = new Callback() { 114 | @Override 115 | public void invalidateDrawable(Drawable d) { 116 | invalidateSelf(); 117 | } 118 | 119 | @Override 120 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 121 | scheduleSelf(what, when); 122 | } 123 | 124 | @Override 125 | public void unscheduleDrawable(Drawable d, Runnable what) { 126 | unscheduleSelf(what); 127 | } 128 | }; 129 | /** 130 | * Canvas rotation in degrees. 131 | */ 132 | private float mRotation; 133 | private Resources mResources; 134 | private View mParent; 135 | private Animation mAnimation; 136 | private float mRotationCount; 137 | private double mWidth; 138 | private double mHeight; 139 | private Animation mFinishAnimation; 140 | private int mBackgroundColor; 141 | private ShapeDrawable mShadow; 142 | 143 | public MaterialProgressDrawable(Context context, View parent) { 144 | mParent = parent; 145 | mResources = context.getResources(); 146 | mRing = new Ring(mCallback); 147 | mRing.setColors(COLORS); 148 | updateSizes(DEFAULT); 149 | setupAnimators(); 150 | } 151 | 152 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 153 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 154 | final Ring ring = mRing; 155 | final DisplayMetrics metrics = mResources.getDisplayMetrics(); 156 | final float screenDensity = metrics.density; 157 | mWidth = progressCircleWidth * screenDensity; 158 | mHeight = progressCircleHeight * screenDensity; 159 | ring.setStrokeWidth((float) strokeWidth * screenDensity); 160 | ring.setCenterRadius(centerRadius * screenDensity); 161 | ring.setColorIndex(0); 162 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 163 | ring.setInsets((int) mWidth, (int) mHeight); 164 | setUp(mWidth); 165 | } 166 | 167 | private void setUp(final double diameter) { 168 | PtrLocalDisplay.init(mParent.getContext()); 169 | final int shadowYOffset = PtrLocalDisplay.dp2px(Y_OFFSET); 170 | final int shadowXOffset = PtrLocalDisplay.dp2px(X_OFFSET); 171 | int mShadowRadius = PtrLocalDisplay.dp2px(SHADOW_RADIUS); 172 | OvalShape oval = new OvalShadow(mShadowRadius, (int) diameter); 173 | mShadow = new ShapeDrawable(oval); 174 | if (Build.VERSION.SDK_INT >= 11) { 175 | mParent.setLayerType(View.LAYER_TYPE_SOFTWARE, mShadow.getPaint()); 176 | } 177 | mShadow.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, KEY_SHADOW_COLOR); 178 | } 179 | 180 | /** 181 | * Set the overall size for the progress spinner. This updates the radius 182 | * and stroke width of the ring. 183 | * 184 | */ 185 | public void updateSizes(int size) { 186 | if (size == LARGE) { 187 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 188 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 189 | } else { 190 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 191 | ARROW_WIDTH, ARROW_HEIGHT); 192 | } 193 | } 194 | 195 | /** 196 | * @param show Set to true to display the arrowhead on the progress spinner. 197 | */ 198 | public void showArrow(boolean show) { 199 | mRing.setShowArrow(show); 200 | } 201 | 202 | /** 203 | * @param scale Set the scale of the arrowhead for the spinner. 204 | */ 205 | public void setArrowScale(float scale) { 206 | mRing.setArrowScale(scale); 207 | } 208 | 209 | /** 210 | * Set the start and end trim for the progress spinner arc. 211 | * 212 | * @param startAngle start angle 213 | * @param endAngle end angle 214 | */ 215 | public void setStartEndTrim(float startAngle, float endAngle) { 216 | mRing.setStartTrim(startAngle); 217 | mRing.setEndTrim(endAngle); 218 | } 219 | 220 | /** 221 | * Set the amount of rotation to apply to the progress spinner. 222 | * 223 | * @param rotation Rotation is from [0..1] 224 | */ 225 | public void setProgressRotation(float rotation) { 226 | mRing.setRotation(rotation); 227 | } 228 | 229 | /** 230 | * Update the background color of the circle image view. 231 | */ 232 | public void setBackgroundColor(int color) { 233 | mBackgroundColor = color; 234 | mRing.setBackgroundColor(color); 235 | } 236 | 237 | /** 238 | * Set the colors used in the progress animation from color resources. 239 | * The first color will also be the color of the bar that grows in response 240 | * to a user swipe gesture. 241 | * 242 | * @param colors 243 | */ 244 | public void setColorSchemeColors(int... colors) { 245 | mRing.setColors(colors); 246 | mRing.setColorIndex(0); 247 | } 248 | 249 | @Override 250 | public int getIntrinsicHeight() { 251 | return (int) mHeight; 252 | } 253 | 254 | @Override 255 | public int getIntrinsicWidth() { 256 | return (int) mWidth; 257 | } 258 | 259 | @Override 260 | public void draw(Canvas c) { 261 | if (mShadow != null) { 262 | mShadow.getPaint().setColor(mBackgroundColor); 263 | mShadow.draw(c); 264 | } 265 | 266 | final Rect bounds = getBounds(); 267 | final int saveCount = c.save(); 268 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 269 | mRing.draw(c, bounds); 270 | c.restoreToCount(saveCount); 271 | } 272 | 273 | public int getAlpha() { 274 | return mRing.getAlpha(); 275 | } 276 | 277 | @Override 278 | public void setAlpha(int alpha) { 279 | mRing.setAlpha(alpha); 280 | } 281 | 282 | @Override 283 | public void setColorFilter(ColorFilter colorFilter) { 284 | mRing.setColorFilter(colorFilter); 285 | } 286 | 287 | @SuppressWarnings("unused") 288 | private float getRotation() { 289 | return mRotation; 290 | } 291 | 292 | @SuppressWarnings("unused") 293 | void setRotation(float rotation) { 294 | mRotation = rotation; 295 | invalidateSelf(); 296 | } 297 | 298 | @Override 299 | public int getOpacity() { 300 | return PixelFormat.TRANSLUCENT; 301 | } 302 | 303 | @Override 304 | public boolean isRunning() { 305 | final ArrayList animators = mAnimators; 306 | final int N = animators.size(); 307 | for (int i = 0; i < N; i++) { 308 | final Animation animator = animators.get(i); 309 | if (animator.hasStarted() && !animator.hasEnded()) { 310 | return true; 311 | } 312 | } 313 | return false; 314 | } 315 | 316 | @Override 317 | public void start() { 318 | mAnimation.reset(); 319 | mRing.storeOriginals(); 320 | // Already showing some part of the ring 321 | if (mRing.getEndTrim() != mRing.getStartTrim()) { 322 | mParent.startAnimation(mFinishAnimation); 323 | } else { 324 | mRing.setColorIndex(0); 325 | mRing.resetOriginals(); 326 | mParent.startAnimation(mAnimation); 327 | } 328 | } 329 | 330 | @Override 331 | public void stop() { 332 | mParent.clearAnimation(); 333 | setRotation(0); 334 | mRing.setShowArrow(false); 335 | mRing.setColorIndex(0); 336 | mRing.resetOriginals(); 337 | } 338 | 339 | private void setupAnimators() { 340 | final Ring ring = mRing; 341 | final Animation finishRingAnimation = new Animation() { 342 | public void applyTransformation(float interpolatedTime, Transformation t) { 343 | // shrink back down and complete a full rotation before starting other circles 344 | // Rotation goes between [0..1]. 345 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() 346 | / MAX_PROGRESS_ARC) + 1f); 347 | final float startTrim = ring.getStartingStartTrim() 348 | + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) 349 | * interpolatedTime; 350 | ring.setStartTrim(startTrim); 351 | final float rotation = ring.getStartingRotation() 352 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 353 | ring.setRotation(rotation); 354 | ring.setArrowScale(1 - interpolatedTime); 355 | } 356 | }; 357 | finishRingAnimation.setInterpolator(EASE_INTERPOLATOR); 358 | finishRingAnimation.setDuration(ANIMATION_DURATION / 2); 359 | finishRingAnimation.setAnimationListener(new Animation.AnimationListener() { 360 | @Override 361 | public void onAnimationStart(Animation animation) { 362 | } 363 | 364 | @Override 365 | public void onAnimationEnd(Animation animation) { 366 | ring.goToNextColor(); 367 | ring.storeOriginals(); 368 | ring.setShowArrow(false); 369 | mParent.startAnimation(mAnimation); 370 | } 371 | 372 | @Override 373 | public void onAnimationRepeat(Animation animation) { 374 | } 375 | }); 376 | final Animation animation = new Animation() { 377 | @Override 378 | public void applyTransformation(float interpolatedTime, Transformation t) { 379 | // The minProgressArc is calculated from 0 to create an angle that 380 | // matches the stroke width. 381 | final float minProgressArc = (float) Math.toRadians(ring.getStrokeWidth() 382 | / (2 * Math.PI * ring.getCenterRadius())); 383 | final float startingEndTrim = ring.getStartingEndTrim(); 384 | final float startingTrim = ring.getStartingStartTrim(); 385 | final float startingRotation = ring.getStartingRotation(); 386 | // Offset the minProgressArc to where the endTrim is located. 387 | final float minArc = MAX_PROGRESS_ARC - minProgressArc; 388 | final float endTrim = startingEndTrim 389 | + (minArc * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime)); 390 | ring.setEndTrim(endTrim); 391 | final float startTrim = startingTrim 392 | + (MAX_PROGRESS_ARC * END_CURVE_INTERPOLATOR 393 | .getInterpolation(interpolatedTime)); 394 | ring.setStartTrim(startTrim); 395 | final float rotation = startingRotation + (0.25f * interpolatedTime); 396 | ring.setRotation(rotation); 397 | float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime) 398 | + (720.0f * (mRotationCount / NUM_POINTS)); 399 | setRotation(groupRotation); 400 | } 401 | }; 402 | animation.setRepeatCount(Animation.INFINITE); 403 | animation.setRepeatMode(Animation.RESTART); 404 | animation.setInterpolator(LINEAR_INTERPOLATOR); 405 | animation.setDuration(ANIMATION_DURATION); 406 | animation.setAnimationListener(new Animation.AnimationListener() { 407 | @Override 408 | public void onAnimationStart(Animation animation) { 409 | mRotationCount = 0; 410 | } 411 | 412 | @Override 413 | public void onAnimationEnd(Animation animation) { 414 | // do nothing 415 | } 416 | 417 | @Override 418 | public void onAnimationRepeat(Animation animation) { 419 | ring.storeOriginals(); 420 | ring.goToNextColor(); 421 | ring.setStartTrim(ring.getEndTrim()); 422 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 423 | } 424 | }); 425 | mFinishAnimation = finishRingAnimation; 426 | mAnimation = animation; 427 | } 428 | 429 | private static class Ring { 430 | private final RectF mTempBounds = new RectF(); 431 | private final Paint mArcPaint = new Paint(); 432 | private final Paint mArrowPaint = new Paint(); 433 | private final Callback mRingCallback; 434 | private final Paint mCirclePaint = new Paint(); 435 | private float mStartTrim = 0.0f; 436 | private float mEndTrim = 0.0f; 437 | private float mRotation = 0.0f; 438 | private float mStrokeWidth = 5.0f; 439 | private float mStrokeInset = 2.5f; 440 | private int[] mColors; 441 | // mColorIndex represents the offset into the available mColors that the 442 | // progress circle should currently display. As the progress circle is 443 | // animating, the mColorIndex moves by one to the next available color. 444 | private int mColorIndex; 445 | private float mStartingStartTrim; 446 | private float mStartingEndTrim; 447 | private float mStartingRotation; 448 | private boolean mShowArrow; 449 | private Path mArrow; 450 | private float mArrowScale; 451 | private double mRingCenterRadius; 452 | private int mArrowWidth; 453 | private int mArrowHeight; 454 | private int mAlpha; 455 | private int mBackgroundColor; 456 | 457 | public Ring(Callback callback) { 458 | mRingCallback = callback; 459 | mArcPaint.setStrokeCap(Paint.Cap.SQUARE); 460 | mArcPaint.setAntiAlias(true); 461 | mArcPaint.setStyle(Style.STROKE); 462 | mArrowPaint.setStyle(Paint.Style.FILL); 463 | mArrowPaint.setAntiAlias(true); 464 | 465 | mCirclePaint.setAntiAlias(true); 466 | } 467 | 468 | public void setBackgroundColor(int color) { 469 | mBackgroundColor = color; 470 | } 471 | 472 | /** 473 | * Set the dimensions of the arrowhead. 474 | * 475 | * @param width Width of the hypotenuse of the arrow head 476 | * @param height Height of the arrow point 477 | */ 478 | public void setArrowDimensions(float width, float height) { 479 | mArrowWidth = (int) width; 480 | mArrowHeight = (int) height; 481 | } 482 | 483 | /** 484 | * Draw the progress spinner 485 | */ 486 | public void draw(Canvas c, Rect bounds) { 487 | 488 | mCirclePaint.setColor(mBackgroundColor); 489 | mCirclePaint.setAlpha(mAlpha); 490 | 491 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, mCirclePaint); 492 | 493 | final RectF arcBounds = mTempBounds; 494 | arcBounds.set(bounds); 495 | arcBounds.inset(mStrokeInset, mStrokeInset); 496 | final float startAngle = (mStartTrim + mRotation) * 360; 497 | final float endAngle = (mEndTrim + mRotation) * 360; 498 | float sweepAngle = endAngle - startAngle; 499 | mArcPaint.setColor(mColors[mColorIndex]); 500 | mArcPaint.setAlpha(mAlpha); 501 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mArcPaint); 502 | drawTriangle(c, startAngle, sweepAngle, bounds); 503 | } 504 | 505 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 506 | if (mShowArrow) { 507 | if (mArrow == null) { 508 | mArrow = new android.graphics.Path(); 509 | mArrow.setFillType(android.graphics.Path.FillType.EVEN_ODD); 510 | } else { 511 | mArrow.reset(); 512 | } 513 | // Adjust the position of the triangle so that it is inset as 514 | // much as the arc, but also centered on the arc. 515 | float inset = (int) mStrokeInset / 2 * mArrowScale; 516 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 517 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 518 | // Update the path each time. This works around an issue in SKIA 519 | // where concatenating a rotation matrix to a scale matrix 520 | // ignored a starting negative rotation. This appears to have 521 | // been fixed as of API 21. 522 | mArrow.moveTo(0, 0); 523 | mArrow.lineTo(mArrowWidth * mArrowScale, 0); 524 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 525 | * mArrowScale)); 526 | mArrow.offset(x - inset, y); 527 | mArrow.close(); 528 | // draw a triangle 529 | mArrowPaint.setColor(mColors[mColorIndex]); 530 | mArrowPaint.setAlpha(mAlpha); 531 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 532 | bounds.exactCenterY()); 533 | c.drawPath(mArrow, mArrowPaint); 534 | } 535 | } 536 | 537 | /** 538 | * Set the colors the progress spinner alternates between. 539 | * 540 | * @param colors Array of integers describing the colors. Must be non-null. 541 | */ 542 | public void setColors(int[] colors) { 543 | mColors = colors; 544 | // if colors are reset, make sure to reset the color index as well 545 | setColorIndex(0); 546 | } 547 | 548 | /** 549 | * @param index Index into the color array of the color to display in 550 | * the progress spinner. 551 | */ 552 | public void setColorIndex(int index) { 553 | mColorIndex = index; 554 | } 555 | 556 | /** 557 | * Proceed to the next available ring color. This will automatically 558 | * wrap back to the beginning of colors. 559 | */ 560 | public void goToNextColor() { 561 | mColorIndex = (mColorIndex + 1) % (mColors.length); 562 | } 563 | 564 | public void setColorFilter(ColorFilter filter) { 565 | mArcPaint.setColorFilter(filter); 566 | invalidateSelf(); 567 | } 568 | 569 | /** 570 | * @return Current alpha of the progress spinner and arrowhead. 571 | */ 572 | public int getAlpha() { 573 | return mAlpha; 574 | } 575 | 576 | /** 577 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 578 | */ 579 | public void setAlpha(int alpha) { 580 | mAlpha = alpha; 581 | } 582 | 583 | @SuppressWarnings("unused") 584 | public float getStrokeWidth() { 585 | return mStrokeWidth; 586 | } 587 | 588 | /** 589 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 590 | */ 591 | public void setStrokeWidth(float strokeWidth) { 592 | mStrokeWidth = strokeWidth; 593 | mArcPaint.setStrokeWidth(strokeWidth); 594 | invalidateSelf(); 595 | } 596 | 597 | @SuppressWarnings("unused") 598 | public float getStartTrim() { 599 | return mStartTrim; 600 | } 601 | 602 | @SuppressWarnings("unused") 603 | public void setStartTrim(float startTrim) { 604 | mStartTrim = startTrim; 605 | invalidateSelf(); 606 | } 607 | 608 | public float getStartingStartTrim() { 609 | return mStartingStartTrim; 610 | } 611 | 612 | public float getStartingEndTrim() { 613 | return mStartingEndTrim; 614 | } 615 | 616 | @SuppressWarnings("unused") 617 | public float getEndTrim() { 618 | return mEndTrim; 619 | } 620 | 621 | @SuppressWarnings("unused") 622 | public void setEndTrim(float endTrim) { 623 | mEndTrim = endTrim; 624 | invalidateSelf(); 625 | } 626 | 627 | @SuppressWarnings("unused") 628 | public float getRotation() { 629 | return mRotation; 630 | } 631 | 632 | @SuppressWarnings("unused") 633 | public void setRotation(float rotation) { 634 | mRotation = rotation; 635 | invalidateSelf(); 636 | } 637 | 638 | public void setInsets(int width, int height) { 639 | final float minEdge = (float) Math.min(width, height); 640 | float insets; 641 | if (mRingCenterRadius <= 0 || minEdge < 0) { 642 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 643 | } else { 644 | insets = (float) (minEdge / 2.0f - mRingCenterRadius); 645 | } 646 | mStrokeInset = insets; 647 | } 648 | 649 | @SuppressWarnings("unused") 650 | public float getInsets() { 651 | return mStrokeInset; 652 | } 653 | 654 | public double getCenterRadius() { 655 | return mRingCenterRadius; 656 | } 657 | 658 | /** 659 | * @param centerRadius Inner radius in px of the circle the progress 660 | * spinner arc traces. 661 | */ 662 | public void setCenterRadius(double centerRadius) { 663 | mRingCenterRadius = centerRadius; 664 | } 665 | 666 | /** 667 | * @param show Set to true to show the arrow head on the progress spinner. 668 | */ 669 | public void setShowArrow(boolean show) { 670 | if (mShowArrow != show) { 671 | mShowArrow = show; 672 | invalidateSelf(); 673 | } 674 | } 675 | 676 | /** 677 | * @param scale Set the scale of the arrowhead for the spinner. 678 | */ 679 | public void setArrowScale(float scale) { 680 | if (scale != mArrowScale) { 681 | mArrowScale = scale; 682 | invalidateSelf(); 683 | } 684 | } 685 | 686 | /** 687 | * @return The amount the progress spinner is currently rotated, between [0..1]. 688 | */ 689 | public float getStartingRotation() { 690 | return mStartingRotation; 691 | } 692 | 693 | /** 694 | * If the start / end trim are offset to begin with, store them so that 695 | * animation starts from that offset. 696 | */ 697 | public void storeOriginals() { 698 | mStartingStartTrim = mStartTrim; 699 | mStartingEndTrim = mEndTrim; 700 | mStartingRotation = mRotation; 701 | } 702 | 703 | /** 704 | * Reset the progress spinner to default rotation, start and end angles. 705 | */ 706 | public void resetOriginals() { 707 | mStartingStartTrim = 0; 708 | mStartingEndTrim = 0; 709 | mStartingRotation = 0; 710 | setStartTrim(0); 711 | setEndTrim(0); 712 | setRotation(0); 713 | } 714 | 715 | private void invalidateSelf() { 716 | mRingCallback.invalidateDrawable(null); 717 | } 718 | } 719 | 720 | /** 721 | * Squishes the interpolation curve into the second half of the animation. 722 | */ 723 | private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator { 724 | @Override 725 | public float getInterpolation(float input) { 726 | return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f)); 727 | } 728 | } 729 | 730 | /** 731 | * Squishes the interpolation curve into the first half of the animation. 732 | */ 733 | private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator { 734 | @Override 735 | public float getInterpolation(float input) { 736 | return super.getInterpolation(Math.min(1, input * 2.0f)); 737 | } 738 | } 739 | 740 | /** 741 | * Taken from {@link package android.support.v4.widget} 742 | */ 743 | private class OvalShadow extends OvalShape { 744 | private RadialGradient mRadialGradient; 745 | private int mShadowRadius; 746 | private Paint mShadowPaint; 747 | private int mCircleDiameter; 748 | 749 | public OvalShadow(int shadowRadius, int circleDiameter) { 750 | super(); 751 | mShadowPaint = new Paint(); 752 | mShadowRadius = shadowRadius; 753 | mCircleDiameter = circleDiameter; 754 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, 755 | mShadowRadius, new int[]{ 756 | FILL_SHADOW_COLOR, Color.TRANSPARENT 757 | }, null, Shader.TileMode.CLAMP); 758 | mShadowPaint.setShader(mRadialGradient); 759 | } 760 | 761 | @Override 762 | public void draw(Canvas canvas, Paint paint) { 763 | final int viewWidth = getBounds().width(); 764 | final int viewHeight = getBounds().height(); 765 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius), 766 | mShadowPaint); 767 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint); 768 | } 769 | } 770 | } --------------------------------------------------------------------------------