├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── app ├── .gitignore ├── build.gradle ├── libs │ └── universal-image-loader-1.9.5.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── rxjava_retrofit_mvp_md │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── rxjava_retrofit_mvp_md │ │ │ ├── application │ │ │ └── MyApplication.java │ │ │ ├── article │ │ │ ├── ArticleAdapter.java │ │ │ ├── model │ │ │ │ ├── ArticleModel.java │ │ │ │ └── ArticleModelImpl.java │ │ │ ├── presenter │ │ │ │ ├── ArticlePresenter.java │ │ │ │ └── ArticlePresenterImpl.java │ │ │ ├── view │ │ │ │ └── ArticleView.java │ │ │ └── widget │ │ │ │ └── ArticleFragment.java │ │ │ ├── base │ │ │ ├── BaseActivity.java │ │ │ ├── BaseFragment.java │ │ │ └── BaseView.java │ │ │ ├── beans │ │ │ ├── Article.java │ │ │ ├── ArticleApi.java │ │ │ └── ArticleBody.java │ │ │ ├── behavior │ │ │ └── ScrollingFABBehavior.java │ │ │ ├── http │ │ │ ├── HttpManager.java │ │ │ ├── HttpService.java │ │ │ ├── HttpTimeException.java │ │ │ ├── ProgressSubscriber.java │ │ │ ├── api │ │ │ │ └── BaseApi.java │ │ │ └── listener │ │ │ │ └── HttpOnNextListener.java │ │ │ ├── main │ │ │ ├── presenter │ │ │ │ ├── MainPresenter.java │ │ │ │ └── MainPresenterImpl.java │ │ │ ├── view │ │ │ │ └── MainView.java │ │ │ └── widget │ │ │ │ └── MainActivity.java │ │ │ ├── refresh │ │ │ ├── CircleImageView.java │ │ │ ├── MaterialProgressDrawable.java │ │ │ ├── RefreshLayout.java │ │ │ ├── SpaceItemDecoration.java │ │ │ └── SwipeRefreshLayoutDirection.java │ │ │ └── utils │ │ │ └── Utils.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_article.xml │ │ ├── head_view.xml │ │ ├── item_article.xml │ │ └── main_view.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── fab_editor.png │ │ ├── guanyu_4.png │ │ ├── ic_launcher.png │ │ ├── jiazaishibai.png │ │ ├── liulan_5.png │ │ ├── loading.jpg │ │ ├── shequ_4.png │ │ ├── shouye.png │ │ ├── xihuan_4.png │ │ └── yijianfankui.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── rxjava_retrofit_mvp_md │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.1" 6 | defaultConfig { 7 | applicationId "com.example.rxjava_retrofit_mvp_md" 8 | minSdkVersion 21 9 | targetSdkVersion 25 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 | configurations.all { 22 | resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9' 23 | } 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:25.2.0' 30 | compile 'com.android.support:recyclerview-v7:25.2.0' 31 | compile 'com.android.support:cardview-v7:25.2.0' 32 | compile 'com.android.support:design:25.2.0' 33 | testCompile 'junit:junit:4.12' 34 | /*注解框架*/ 35 | compile 'com.jakewharton:butterknife:8.5.1' 36 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' 37 | /*rx-android-java*/ 38 | compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' 39 | compile 'com.trello:rxlifecycle:1.0' 40 | compile 'com.trello:rxlifecycle-components:1.0' 41 | /*rotrofit*/ 42 | compile 'com.squareup.retrofit2:retrofit:2.1.0' 43 | compile 'com.squareup.retrofit2:converter-gson:2.1.0' 44 | /*数据库*/ 45 | compile 'com.github.satyan:sugar:1.4' 46 | 47 | compile files('libs/universal-image-loader-1.9.5.jar') 48 | } 49 | -------------------------------------------------------------------------------- /app/libs/universal-image-loader-1.9.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/libs/universal-image-loader-1.9.5.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\wz\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/rxjava_retrofit_mvp_md/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md; 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.example.rxjava_retrofit_mvp_md", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/application/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.application; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.nostra13.universalimageloader.core.ImageLoader; 7 | import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; 8 | 9 | /** 10 | * Created by KomoriWu 11 | * on 2017-03-25. 12 | */ 13 | 14 | public class MyApplication extends Application { 15 | private static ImageLoader mImageLoader; 16 | 17 | @Override 18 | public void onCreate() { 19 | super.onCreate(); 20 | 21 | } 22 | 23 | public static ImageLoader getImageLoader(Context context) { 24 | if (mImageLoader == null) { 25 | synchronized (ImageLoader.class) { 26 | if (mImageLoader == null) { 27 | mImageLoader = ImageLoader.getInstance(); 28 | mImageLoader.init(ImageLoaderConfiguration.createDefault(context. 29 | getApplicationContext())); 30 | } 31 | } 32 | } 33 | return mImageLoader; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/article/ArticleAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.article; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.text.TextUtils; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.example.rxjava_retrofit_mvp_md.R; 13 | import com.example.rxjava_retrofit_mvp_md.application.MyApplication; 14 | import com.example.rxjava_retrofit_mvp_md.beans.Article; 15 | import com.example.rxjava_retrofit_mvp_md.utils.Utils; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import butterknife.BindView; 21 | import butterknife.ButterKnife; 22 | 23 | /** 24 | * Created by KomoriWu 25 | * on 2017-03-24. 26 | */ 27 | 28 | public class ArticleAdapter extends RecyclerView.Adapter { 29 | private Context mContext; 30 | private List
mArticleArrayList; 31 | 32 | public ArticleAdapter(Context mContext) { 33 | this.mContext = mContext; 34 | mArticleArrayList = new ArrayList<>(); 35 | } 36 | 37 | public void addArticleList(List
articleList, boolean isClearList) { 38 | if (articleList != null) { 39 | if (isClearList) { 40 | mArticleArrayList.clear(); 41 | } 42 | mArticleArrayList.addAll(articleList); 43 | notifyDataSetChanged(); 44 | } 45 | } 46 | 47 | @Override 48 | public ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 49 | View view = LayoutInflater.from(mContext).inflate(R.layout.item_article, null); 50 | return new ArticleViewHolder(view); 51 | } 52 | 53 | @Override 54 | public void onBindViewHolder(ArticleViewHolder holder, int position) { 55 | Article article = mArticleArrayList.get(position); 56 | MyApplication.getImageLoader(mContext).displayImage(article.getCoverUrl(), holder.ivCover, 57 | Utils.getImageOptions(R.mipmap.loading)); 58 | MyApplication.getImageLoader(mContext).displayImage(article.getHeadImgUrl(), holder.ivHeadImg, 59 | Utils.getImageOptions(true)); 60 | holder.tvViews.setText(article.getViews()); 61 | holder.tvUsername.setText(article.getUserName()); 62 | holder.tvLikes.setText(article.getLikes()); 63 | holder.tvTitle.setText(article.getTitle()); 64 | holder.tvContent.setText(article.getBriefContent()); 65 | holder.tvTime.setText(article.getTime()); 66 | } 67 | 68 | @Override 69 | public int getItemCount() { 70 | return mArticleArrayList == null ? 0 : mArticleArrayList.size(); 71 | } 72 | 73 | public class ArticleViewHolder extends RecyclerView.ViewHolder { 74 | @BindView(R.id.iv_article_cover) 75 | ImageView ivCover; 76 | @BindView(R.id.iv_article_head_img) 77 | ImageView ivHeadImg; 78 | @BindView(R.id.tv_article_views) 79 | TextView tvViews; 80 | @BindView(R.id.tv_username) 81 | TextView tvUsername; 82 | @BindView(R.id.tv_article_likes) 83 | TextView tvLikes; 84 | @BindView(R.id.tv_article_title) 85 | TextView tvTitle; 86 | @BindView(R.id.tv_article_content) 87 | TextView tvContent; 88 | @BindView(R.id.tv_article_time) 89 | TextView tvTime; 90 | 91 | public ArticleViewHolder(View itemView) { 92 | super(itemView); 93 | ButterKnife.bind(this, itemView); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/article/model/ArticleModel.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.article.model; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Created by KomoriWu 7 | * on 2017-03-24. 8 | */ 9 | 10 | public interface ArticleModel { 11 | void lodeArticles(Context context,String url, String page, String size, ArticleModelImpl. 12 | OnLoadArticleListListener onLoadArticleListListener); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/article/model/ArticleModelImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.article.model; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.example.rxjava_retrofit_mvp_md.beans.ArticleApi; 7 | import com.example.rxjava_retrofit_mvp_md.beans.ArticleBody; 8 | import com.example.rxjava_retrofit_mvp_md.http.HttpManager; 9 | import com.example.rxjava_retrofit_mvp_md.http.HttpService; 10 | import com.example.rxjava_retrofit_mvp_md.http.listener.HttpOnNextListener; 11 | import com.example.rxjava_retrofit_mvp_md.utils.Utils; 12 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import okhttp3.OkHttpClient; 19 | import retrofit2.Retrofit; 20 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 21 | import retrofit2.converter.gson.GsonConverterFactory; 22 | import rx.Observable; 23 | import rx.Subscriber; 24 | import rx.android.schedulers.AndroidSchedulers; 25 | import rx.schedulers.Schedulers; 26 | 27 | /** 28 | * Created by KomoriWu 29 | * on 2017-03-24. 30 | */ 31 | 32 | public class ArticleModelImpl implements ArticleModel { 33 | public static final String TAG = ArticleModelImpl.class.getSimpleName(); 34 | 35 | @Override 36 | public void lodeArticles(Context context, String url, String page, String size, 37 | final OnLoadArticleListListener onLoadArticleListListener) { 38 | 39 | ArticleApi articleApi = new ArticleApi((RxAppCompatActivity) context, page, size, 40 | new HttpOnNextListener() { 41 | @Override 42 | public void onNext(ArticleBody articleBody) { 43 | onLoadArticleListListener.onSuccess(articleBody); 44 | } 45 | 46 | @Override 47 | public void onError(Throwable e) { 48 | super.onError(e); 49 | onLoadArticleListListener.onFailure(e); 50 | } 51 | }); 52 | HttpManager.getInstance().doHttpDeal(articleApi); 53 | 54 | } 55 | 56 | 57 | public interface OnLoadArticleListListener { 58 | void onSuccess(ArticleBody articleBody); 59 | 60 | void onFailure(Throwable e); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/article/presenter/ArticlePresenter.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.article.presenter; 2 | 3 | /** 4 | * Created by KomoriWu 5 | * on 2017-03-24. 6 | */ 7 | 8 | public interface ArticlePresenter { 9 | void LodeArticles(String page,String size); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/article/presenter/ArticlePresenterImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.article.presenter; 2 | 3 | import android.content.Context; 4 | 5 | import com.example.rxjava_retrofit_mvp_md.article.model.ArticleModel; 6 | import com.example.rxjava_retrofit_mvp_md.article.model.ArticleModelImpl; 7 | import com.example.rxjava_retrofit_mvp_md.article.view.ArticleView; 8 | import com.example.rxjava_retrofit_mvp_md.beans.ArticleBody; 9 | import com.example.rxjava_retrofit_mvp_md.utils.Utils; 10 | 11 | /** 12 | * Created by KomoriWu 13 | * on 2017-03-24. 14 | */ 15 | 16 | public class ArticlePresenterImpl implements ArticlePresenter, ArticleModelImpl. 17 | OnLoadArticleListListener { 18 | private Context mContext; 19 | private ArticleModel mArticleModel; 20 | private ArticleView mArticleView; 21 | 22 | public ArticlePresenterImpl(Context context, ArticleView articleView) { 23 | this.mContext = context; 24 | this.mArticleView = articleView; 25 | mArticleModel = new ArticleModelImpl(); 26 | } 27 | 28 | @Override 29 | public void LodeArticles(String page, String size) { 30 | mArticleView.showProgress(); 31 | mArticleModel.lodeArticles(mContext, Utils.URL, page, size, this); 32 | } 33 | 34 | 35 | @Override 36 | public void onSuccess(ArticleBody articleBody) { 37 | mArticleView.hideProgress(); 38 | mArticleView.addArticles(articleBody); 39 | } 40 | 41 | @Override 42 | public void onFailure(Throwable e) { 43 | mArticleView.hideProgress(); 44 | mArticleView.showLoadFailMsg(e); 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/article/view/ArticleView.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.article.view; 2 | 3 | import com.example.rxjava_retrofit_mvp_md.base.BaseView; 4 | import com.example.rxjava_retrofit_mvp_md.beans.Article; 5 | import com.example.rxjava_retrofit_mvp_md.beans.ArticleBody; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by KomoriWu 11 | * on 2017-03-24. 12 | */ 13 | 14 | public interface ArticleView extends BaseView { 15 | void addArticles(ArticleBody articleBody); 16 | 17 | @Override 18 | void showProgress(); 19 | 20 | @Override 21 | void hideProgress(); 22 | 23 | @Override 24 | void showLoadFailMsg(Throwable e); 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/article/widget/ArticleFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.article.widget; 2 | 3 | 4 | import android.os.Bundle; 5 | import android.support.v7.widget.DefaultItemAnimator; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.LinearLayout; 12 | 13 | import com.example.rxjava_retrofit_mvp_md.R; 14 | import com.example.rxjava_retrofit_mvp_md.article.ArticleAdapter; 15 | import com.example.rxjava_retrofit_mvp_md.article.presenter.ArticlePresenter; 16 | import com.example.rxjava_retrofit_mvp_md.article.presenter.ArticlePresenterImpl; 17 | import com.example.rxjava_retrofit_mvp_md.article.view.ArticleView; 18 | import com.example.rxjava_retrofit_mvp_md.base.BaseFragment; 19 | import com.example.rxjava_retrofit_mvp_md.beans.ArticleBody; 20 | import com.example.rxjava_retrofit_mvp_md.refresh.RefreshLayout; 21 | import com.example.rxjava_retrofit_mvp_md.refresh.SpaceItemDecoration; 22 | import com.example.rxjava_retrofit_mvp_md.refresh.SwipeRefreshLayoutDirection; 23 | import com.example.rxjava_retrofit_mvp_md.utils.Utils; 24 | 25 | import butterknife.BindView; 26 | import butterknife.ButterKnife; 27 | 28 | /** 29 | * Created by KomoriWu 30 | * on 2017-03-24. 31 | */ 32 | 33 | public class ArticleFragment extends BaseFragment implements ArticleView, 34 | RefreshLayout.OnRefreshListener { 35 | public static final String TAG = ArticleFragment.class.getSimpleName(); 36 | @BindView(R.id.recycler_view) 37 | RecyclerView recyclerView; 38 | @BindView(R.id.layout_refresh) 39 | RefreshLayout refreshLayout; 40 | @BindView(R.id.layout_article) 41 | LinearLayout layoutArticle; 42 | private int mPage; 43 | private int mSize = 8; 44 | private ArticleAdapter mArticleAdapter; 45 | private ArticlePresenter mArticlePresenter; 46 | 47 | @Override 48 | public View initView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 49 | View view = inflater.inflate(R.layout.fragment_article, null); 50 | ButterKnife.bind(this, view); 51 | initData(); 52 | return view; 53 | } 54 | 55 | private void initData() { 56 | mArticlePresenter = new ArticlePresenterImpl(getActivity(), this); 57 | refreshLayout.setDirection(SwipeRefreshLayoutDirection.BOTH); 58 | refreshLayout.setOnRefreshListener(this); 59 | refreshLayout.setColorScheme(R.color.colorPrimary, R.color.colorAccent, 60 | R.color.light_primary_color); 61 | onRefresh(SwipeRefreshLayoutDirection.TOP); 62 | recyclerView.setHasFixedSize(true); 63 | recyclerView.setItemAnimator(new DefaultItemAnimator()); 64 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 65 | int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.item_space); 66 | recyclerView.addItemDecoration(new SpaceItemDecoration(spacingInPixels)); 67 | mArticleAdapter = new ArticleAdapter(getActivity()); 68 | recyclerView.setAdapter(mArticleAdapter); 69 | } 70 | 71 | @Override 72 | public void showProgress() { 73 | refreshLayout.post(new Runnable() { 74 | @Override 75 | public void run() { 76 | refreshLayout.setRefreshing(true); 77 | } 78 | }); 79 | } 80 | 81 | @Override 82 | public void addArticles(ArticleBody articleBody) { 83 | mArticleAdapter.addArticleList(articleBody.getArticleArrayList(), 84 | mPage == 1 ? true : false); 85 | if (articleBody.getCount() <= mPage * mSize) { 86 | Utils.showSnackBar(layoutArticle, getString(R.string.the_last_page)); 87 | hideProgress(); 88 | refreshLayout.setDirection(SwipeRefreshLayoutDirection.TOP); 89 | } 90 | } 91 | 92 | @Override 93 | public void hideProgress() { 94 | refreshLayout.post(new Runnable() { 95 | @Override 96 | public void run() { 97 | refreshLayout.setRefreshing(false); 98 | } 99 | }); 100 | } 101 | 102 | @Override 103 | public void showLoadFailMsg(Throwable e) { 104 | // Utils.showSnackBar(layoutArticle, e.getMessage()); 105 | } 106 | 107 | 108 | @Override 109 | public void onRefresh(SwipeRefreshLayoutDirection direction) { 110 | if (direction == SwipeRefreshLayoutDirection.TOP) { 111 | mPage = 1; 112 | refreshLayout.setDirection(SwipeRefreshLayoutDirection.BOTH); 113 | } else { 114 | mPage++; 115 | } 116 | 117 | mArticlePresenter.LodeArticles(mPage + "", mSize + ""); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity; 8 | 9 | import butterknife.ButterKnife; 10 | 11 | /** 12 | * Created by KomoriWu 13 | * on 2017-03-24. 14 | */ 15 | 16 | public abstract class BaseActivity extends RxAppCompatActivity { 17 | 18 | public abstract void init(); 19 | 20 | @Override 21 | protected void onCreate(@Nullable Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | init(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/base/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import butterknife.ButterKnife; 11 | 12 | /** 13 | * Created by KomoriWu 14 | * on 2017-03-24. 15 | */ 16 | 17 | public abstract class BaseFragment extends Fragment { 18 | public abstract View initView(LayoutInflater inflater, ViewGroup container, 19 | Bundle savedInstanceState); 20 | 21 | @Nullable 22 | @Override 23 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 24 | @Nullable Bundle savedInstanceState) { 25 | View view = initView(inflater, container, savedInstanceState); 26 | return view; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/base/BaseView.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.base; 2 | 3 | /** 4 | * Created by KomoriWu 5 | * on 2017-03-26. 6 | */ 7 | 8 | public interface BaseView { 9 | void showProgress(); 10 | 11 | void hideProgress(); 12 | 13 | void showLoadFailMsg(Throwable e); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/beans/Article.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.beans; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * Created by KomoriWu 9 | * on 2017-03-24. 10 | */ 11 | 12 | public class Article implements Serializable { 13 | @SerializedName("coll_time") 14 | private String collectionTime; 15 | @SerializedName("id") 16 | private String id; 17 | @SerializedName("userAccount") 18 | private String userAccount; 19 | @SerializedName("userName") 20 | private String userName; 21 | @SerializedName("headImg") 22 | private String headImgUrl; 23 | @SerializedName("cover") 24 | private String coverUrl; 25 | @SerializedName("views") 26 | private String views; 27 | @SerializedName("likes") 28 | private String likes; 29 | @SerializedName("time") 30 | private String time; 31 | @SerializedName("titel") 32 | private String title; 33 | @SerializedName("brief_content") 34 | private String briefContent; 35 | @SerializedName("detail_content") 36 | private String detailContent; 37 | @SerializedName("album_500_500") 38 | private String albumUrl; 39 | @SerializedName("file_link") 40 | private String fileLink; 41 | 42 | public Article() { 43 | } 44 | 45 | public String getCollectionTime() { 46 | return collectionTime; 47 | } 48 | 49 | public void setCollectionTime(String collectionTime) { 50 | this.collectionTime = collectionTime; 51 | } 52 | 53 | public String getId() { 54 | return id; 55 | } 56 | 57 | public void setId(String id) { 58 | this.id = id; 59 | } 60 | 61 | public String getUserAccount() { 62 | return userAccount; 63 | } 64 | 65 | public void setUserAccount(String userAccount) { 66 | this.userAccount = userAccount; 67 | } 68 | 69 | public String getUserName() { 70 | return userName; 71 | } 72 | 73 | public void setUserName(String userName) { 74 | this.userName = userName; 75 | } 76 | 77 | public String getHeadImgUrl() { 78 | return headImgUrl; 79 | } 80 | 81 | public void setHeadImgUrl(String headImgUrl) { 82 | this.headImgUrl = headImgUrl; 83 | } 84 | 85 | public String getCoverUrl() { 86 | return coverUrl; 87 | } 88 | 89 | public void setCoverUrl(String coverUrl) { 90 | this.coverUrl = coverUrl; 91 | } 92 | 93 | public String getViews() { 94 | return views; 95 | } 96 | 97 | public void setViews(String views) { 98 | this.views = views; 99 | } 100 | 101 | public String getLikes() { 102 | return likes; 103 | } 104 | 105 | public void setLikes(String likes) { 106 | this.likes = likes; 107 | } 108 | 109 | public String getTime() { 110 | return time; 111 | } 112 | 113 | public void setTime(String time) { 114 | this.time = time; 115 | } 116 | 117 | public String getTitle() { 118 | return title; 119 | } 120 | 121 | public void setTitle(String title) { 122 | this.title = title; 123 | } 124 | 125 | public String getBriefContent() { 126 | return briefContent; 127 | } 128 | 129 | public void setBriefContent(String briefContent) { 130 | this.briefContent = briefContent; 131 | } 132 | 133 | public String getDetailContent() { 134 | return detailContent; 135 | } 136 | 137 | public void setDetailContent(String detailContent) { 138 | this.detailContent = detailContent; 139 | } 140 | 141 | public String getAlbumUrl() { 142 | return albumUrl; 143 | } 144 | 145 | public void setAlbumUrl(String albumUrl) { 146 | this.albumUrl = albumUrl; 147 | } 148 | 149 | public String getFileLink() { 150 | return fileLink; 151 | } 152 | 153 | public void setFileLink(String fileLink) { 154 | this.fileLink = fileLink; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/beans/ArticleApi.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.beans; 2 | 3 | import com.example.rxjava_retrofit_mvp_md.http.HttpService; 4 | import com.example.rxjava_retrofit_mvp_md.http.api.BaseApi; 5 | import com.example.rxjava_retrofit_mvp_md.http.listener.HttpOnNextListener; 6 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import retrofit2.Retrofit; 12 | import rx.Observable; 13 | 14 | /** 15 | * Created by KomoriWu 16 | * on 2017-03-26. 17 | */ 18 | 19 | public class ArticleApi extends BaseApi { 20 | private String mPage; 21 | private String mSize; 22 | 23 | public ArticleApi(RxAppCompatActivity rxAppCompatActivity, String page, String size, 24 | HttpOnNextListener listener) { 25 | super(rxAppCompatActivity, listener); 26 | this.mPage = page; 27 | this.mSize = size; 28 | setShowProgress(false); 29 | } 30 | 31 | @Override 32 | public Observable getObservable(Retrofit retrofit) { 33 | Map map = new HashMap<>(); 34 | map.put("page", mPage); 35 | map.put("size", mSize); 36 | HttpService service = retrofit.create(HttpService.class); 37 | return service.getAllArticles(map); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/beans/ArticleBody.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.beans; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Created by KomoriWu 9 | * on 2017-03-25. 10 | */ 11 | 12 | public class ArticleBody { 13 | @SerializedName("page") 14 | int page; 15 | @SerializedName("size") 16 | int size; 17 | @SerializedName("count") 18 | int count; 19 | @SerializedName("article") 20 | ArrayList
articleArrayList; 21 | 22 | public int getPage() { 23 | return page; 24 | } 25 | 26 | public void setPage(int page) { 27 | this.page = page; 28 | } 29 | 30 | public int getSize() { 31 | return size; 32 | } 33 | 34 | public void setSize(int size) { 35 | this.size = size; 36 | } 37 | 38 | public int getCount() { 39 | return count; 40 | } 41 | 42 | public void setCount(int count) { 43 | this.count = count; 44 | } 45 | 46 | public ArrayList
getArticleArrayList() { 47 | return articleArrayList; 48 | } 49 | 50 | public void setArticleArrayList(ArrayList
articleArrayList) { 51 | this.articleArrayList = articleArrayList; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/behavior/ScrollingFABBehavior.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.behavior; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.AppBarLayout; 5 | import android.support.design.widget.CoordinatorLayout; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import com.example.rxjava_retrofit_mvp_md.utils.Utils; 11 | 12 | 13 | public class ScrollingFABBehavior extends FloatingActionButton.Behavior { 14 | private int toolbarHeight; 15 | 16 | public ScrollingFABBehavior(Context context, AttributeSet attrs) { 17 | super(); 18 | this.toolbarHeight = Utils.getToolbarHeight(context); 19 | } 20 | 21 | @Override 22 | public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) { 23 | return super.layoutDependsOn(parent, fab, dependency) || (dependency instanceof AppBarLayout); 24 | } 25 | 26 | @Override 27 | public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) { 28 | boolean returnValue = super.onDependentViewChanged(parent, fab, dependency); 29 | if (dependency instanceof AppBarLayout) { 30 | CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams(); 31 | int fabBottomMargin = lp.bottomMargin; 32 | int distanceToScroll = fab.getHeight() + fabBottomMargin; 33 | float ratio = (float) dependency.getY() / (float) toolbarHeight; 34 | fab.setTranslationY(-distanceToScroll * ratio); 35 | } 36 | return returnValue; 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/http/HttpManager.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.http; 2 | 3 | import com.example.rxjava_retrofit_mvp_md.http.api.BaseApi; 4 | import com.trello.rxlifecycle.android.ActivityEvent; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import okhttp3.OkHttpClient; 9 | import retrofit2.Retrofit; 10 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 11 | import retrofit2.converter.gson.GsonConverterFactory; 12 | import rx.Observable; 13 | import rx.Subscriber; 14 | import rx.android.schedulers.AndroidSchedulers; 15 | import rx.schedulers.Schedulers; 16 | 17 | /** 18 | * Created by KomoriWu 19 | * on 2017-03-26. 20 | */ 21 | 22 | public class HttpManager { 23 | private static HttpManager mHttpManager; 24 | 25 | public HttpManager() { 26 | } 27 | 28 | public static HttpManager getInstance() { 29 | if (mHttpManager == null) { 30 | synchronized (HttpManager.class) { 31 | if (mHttpManager == null) { 32 | mHttpManager = new HttpManager(); 33 | } 34 | } 35 | } 36 | return mHttpManager; 37 | } 38 | 39 | public void doHttpDeal(BaseApi baseApi) { 40 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 41 | builder.connectTimeout(baseApi.getConnectionTime(), TimeUnit.SECONDS); 42 | Retrofit retrofit = new Retrofit.Builder() 43 | .client(builder.build()) 44 | .addConverterFactory(GsonConverterFactory.create()) 45 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 46 | .baseUrl(baseApi.getBaseUrl()) 47 | .build(); 48 | ProgressSubscriber subscriber = new ProgressSubscriber(baseApi); 49 | Observable observable = baseApi.getObservable(retrofit) 50 | .subscribeOn(Schedulers.io()) 51 | .unsubscribeOn(Schedulers.io()) 52 | .observeOn(AndroidSchedulers.mainThread()) 53 | .compose(baseApi.getRxAppCompatActivity().bindUntilEvent(ActivityEvent.DESTROY)); 54 | observable.subscribe(subscriber); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/http/HttpService.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.http; 2 | 3 | import com.example.rxjava_retrofit_mvp_md.beans.ArticleBody; 4 | import com.example.rxjava_retrofit_mvp_md.utils.Utils; 5 | 6 | import java.util.Map; 7 | 8 | import retrofit2.http.GET; 9 | import retrofit2.http.QueryMap; 10 | import rx.Observable; 11 | 12 | /** 13 | * Created by KomoriWu 14 | * on 2017-03-24. 15 | */ 16 | 17 | public interface HttpService { 18 | @GET(Utils.GET_ALL_ARTICLES) 19 | Observable getAllArticles(@QueryMap Map map); 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/http/HttpTimeException.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.http; 2 | 3 | /** 4 | * Created by KomoriWu 5 | * on 2017-03-26. 6 | */ 7 | 8 | public class HttpTimeException extends RuntimeException { 9 | 10 | public static final int NO_DATA = 0x2; 11 | 12 | public HttpTimeException(int resultCode) { 13 | this(getApiExceptionMessage(resultCode)); 14 | } 15 | 16 | public HttpTimeException(String detailMessage) { 17 | super(detailMessage); 18 | } 19 | 20 | private static String getApiExceptionMessage(int code) { 21 | String message = ""; 22 | switch (code) { 23 | case NO_DATA: 24 | message = "无数据"; 25 | break; 26 | default: 27 | message = "error"; 28 | break; 29 | 30 | } 31 | return message; 32 | } 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/http/ProgressSubscriber.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.http; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.DialogInterface; 5 | 6 | import com.example.rxjava_retrofit_mvp_md.http.api.BaseApi; 7 | import com.example.rxjava_retrofit_mvp_md.http.listener.HttpOnNextListener; 8 | import com.example.rxjava_retrofit_mvp_md.utils.Utils; 9 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity; 10 | 11 | import rx.Subscriber; 12 | 13 | /** 14 | * Created by KomoriWu 15 | * on 2017-03-26. 16 | */ 17 | 18 | public class ProgressSubscriber extends Subscriber { 19 | private boolean mIsShowProgress; 20 | private HttpOnNextListener mHttpOnNextListener; 21 | private RxAppCompatActivity mRxAppCompatActivity; 22 | private ProgressDialog mProgressDialog; 23 | private BaseApi mApi; 24 | 25 | public ProgressSubscriber(BaseApi api) { 26 | this.mApi = api; 27 | this.mIsShowProgress = api.isShowProgress(); 28 | this.mHttpOnNextListener = api.getListener(); 29 | this.mRxAppCompatActivity = api.getRxAppCompatActivity(); 30 | if (api.isShowProgress()) { 31 | initProgressDialog(api.isIsCancel()); 32 | } 33 | } 34 | 35 | private void initProgressDialog(boolean cancel) { 36 | if (mProgressDialog == null && mRxAppCompatActivity != null) { 37 | mProgressDialog = new ProgressDialog(mRxAppCompatActivity); 38 | mProgressDialog.setCancelable(cancel); 39 | if (cancel) { 40 | mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 41 | @Override 42 | public void onCancel(DialogInterface dialogInterface) { 43 | if (mHttpOnNextListener != null) { 44 | mHttpOnNextListener.onCancel(); 45 | } 46 | onCancelProgress(); 47 | } 48 | }); 49 | } 50 | } 51 | } 52 | 53 | public void onCancelProgress() { 54 | if (!this.isUnsubscribed()) { 55 | this.unsubscribe(); 56 | } 57 | } 58 | 59 | private void showProgressDialog() { 60 | if (!mIsShowProgress) return; 61 | if (mProgressDialog == null || mRxAppCompatActivity == null) return; 62 | if (!mProgressDialog.isShowing()) { 63 | mProgressDialog.show(); 64 | } 65 | } 66 | 67 | private void dismissProgressDialog() { 68 | if (!mIsShowProgress) return; 69 | if (mProgressDialog != null && mProgressDialog.isShowing()) { 70 | mProgressDialog.dismiss(); 71 | } 72 | } 73 | 74 | @Override 75 | public void onStart() { 76 | showProgressDialog(); 77 | } 78 | 79 | @Override 80 | public void onCompleted() { 81 | dismissProgressDialog(); 82 | } 83 | 84 | @Override 85 | public void onError(Throwable e) { 86 | dismissProgressDialog(); 87 | if (mHttpOnNextListener != null) { 88 | mHttpOnNextListener.onError(e); 89 | Utils.showAlertDialog(mRxAppCompatActivity, e.getMessage()); 90 | } 91 | } 92 | 93 | @Override 94 | public void onNext(T t) { 95 | if (mHttpOnNextListener != null) { 96 | mHttpOnNextListener.onNext(t); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/http/api/BaseApi.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.http.api; 2 | 3 | import com.example.rxjava_retrofit_mvp_md.http.listener.HttpOnNextListener; 4 | import com.example.rxjava_retrofit_mvp_md.utils.Utils; 5 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity; 6 | 7 | import retrofit2.Retrofit; 8 | import rx.Observable; 9 | import rx.functions.Func0; 10 | 11 | /** 12 | * Created by KomoriWu 13 | * on 2017-03-25. 14 | */ 15 | 16 | public abstract class BaseApi implements Func0 { 17 | private RxAppCompatActivity mRxAppCompatActivity; 18 | private HttpOnNextListener mListener; 19 | private boolean mIsShowProgress; 20 | private boolean mIsCancel; 21 | private String mBaseUrl; 22 | private int mConnectionTime; 23 | 24 | public abstract Observable getObservable(Retrofit retrofit); 25 | 26 | public BaseApi(RxAppCompatActivity rxAppCompatActivity, HttpOnNextListener listener) { 27 | this.mRxAppCompatActivity = rxAppCompatActivity; 28 | this.mListener = listener; 29 | setBaseUrl(Utils.URL); 30 | setConnectionTime(6); 31 | setShowProgress(true); 32 | } 33 | 34 | public RxAppCompatActivity getRxAppCompatActivity() { 35 | return mRxAppCompatActivity; 36 | } 37 | 38 | public void setRxAppCompatActivity(RxAppCompatActivity mRxAppCompatActivity) { 39 | this.mRxAppCompatActivity = mRxAppCompatActivity; 40 | } 41 | 42 | public HttpOnNextListener getListener() { 43 | return mListener; 44 | } 45 | 46 | public void setListener(HttpOnNextListener mListener) { 47 | this.mListener = mListener; 48 | } 49 | 50 | public boolean isShowProgress() { 51 | return mIsShowProgress; 52 | } 53 | 54 | public void setShowProgress(boolean showProgress) { 55 | mIsShowProgress = showProgress; 56 | } 57 | 58 | public String getBaseUrl() { 59 | return mBaseUrl; 60 | } 61 | 62 | public void setBaseUrl(String baseUrl) { 63 | this.mBaseUrl = baseUrl; 64 | } 65 | 66 | public int getConnectionTime() { 67 | return mConnectionTime; 68 | } 69 | 70 | public void setConnectionTime(int connectionTime) { 71 | this.mConnectionTime = connectionTime; 72 | } 73 | 74 | public boolean isIsCancel() { 75 | return mIsCancel; 76 | } 77 | 78 | public void setIsCancel(boolean mIsCancel) { 79 | this.mIsCancel = mIsCancel; 80 | } 81 | 82 | @Override 83 | public T call() { 84 | return null; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/http/listener/HttpOnNextListener.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.http.listener; 2 | 3 | /** 4 | * Created by KomoriWu 5 | * on 2017-03-26. 6 | */ 7 | 8 | public abstract class HttpOnNextListener { 9 | public abstract void onNext(T t); 10 | 11 | public void onError(Throwable e) { 12 | 13 | } 14 | 15 | public void onCancel() { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/main/presenter/MainPresenter.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.main.presenter; 2 | 3 | /** 4 | * Created by KomoriWu 5 | * on 2017-03-24. 6 | */ 7 | 8 | 9 | public interface MainPresenter { 10 | void switchNavigation(int id); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/main/presenter/MainPresenterImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.main.presenter; 2 | 3 | 4 | import com.example.rxjava_retrofit_mvp_md.R; 5 | import com.example.rxjava_retrofit_mvp_md.main.view.MainView; 6 | 7 | /** 8 | * Created by KomoriWu 9 | * on 2017-03-24. 10 | */ 11 | 12 | 13 | public class MainPresenterImpl implements MainPresenter { 14 | private MainView mMainView; 15 | 16 | public MainPresenterImpl(MainView mMainView) { 17 | this.mMainView = mMainView; 18 | } 19 | 20 | @Override 21 | public void switchNavigation(int id) { 22 | switch (id) { 23 | case R.id.fab: 24 | 25 | break; 26 | case R.id.nav_home: 27 | mMainView.switchHome(); 28 | break; 29 | case R.id.nav_community: 30 | mMainView.switchCommunity(); 31 | break; 32 | case R.id.nav_instructions: 33 | mMainView.switchInstructions(); 34 | break; 35 | case R.id.nav_about_us: 36 | mMainView.switchAboutUs(); 37 | break; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/main/view/MainView.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.main.view; 2 | 3 | /** 4 | * Created by KomoriWu 5 | * on 2017-03-24. 6 | */ 7 | 8 | 9 | public interface MainView { 10 | 11 | void switchHome(); 12 | 13 | void switchCommunity(); 14 | 15 | void switchInstructions(); 16 | 17 | void switchAboutUs(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/main/widget/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.main.widget; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.NavigationView; 6 | import android.support.design.widget.TabLayout; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v4.app.FragmentManager; 9 | import android.support.v4.app.FragmentPagerAdapter; 10 | import android.support.v4.view.PagerAdapter; 11 | import android.support.v4.view.ViewPager; 12 | import android.support.v4.widget.DrawerLayout; 13 | import android.support.v7.app.ActionBarDrawerToggle; 14 | import android.support.v7.app.AppCompatActivity; 15 | import android.support.v7.widget.Toolbar; 16 | import android.view.MenuItem; 17 | import android.widget.TextView; 18 | 19 | import com.example.rxjava_retrofit_mvp_md.R; 20 | import com.example.rxjava_retrofit_mvp_md.article.widget.ArticleFragment; 21 | import com.example.rxjava_retrofit_mvp_md.base.BaseActivity; 22 | import com.example.rxjava_retrofit_mvp_md.main.presenter.MainPresenter; 23 | import com.example.rxjava_retrofit_mvp_md.main.presenter.MainPresenterImpl; 24 | import com.example.rxjava_retrofit_mvp_md.main.view.MainView; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | import butterknife.BindView; 30 | import butterknife.ButterKnife; 31 | 32 | /** 33 | * Created by KomoriWu 34 | * on 2017-03-24. 35 | */ 36 | 37 | 38 | public class MainActivity extends BaseActivity implements MainView { 39 | @BindView(R.id.layout_drawer) 40 | DrawerLayout drawerLayout; 41 | @BindView(R.id.navigation_view) 42 | NavigationView navigationView; 43 | @BindView(R.id.toolbar) 44 | Toolbar toolbar; 45 | @BindView(R.id.fab) 46 | FloatingActionButton floatingActionButton; 47 | private MainPresenter mMainPresenter; 48 | private PagerAdapter mPagerAdapter; 49 | @Override 50 | public void init() { 51 | setContentView(R.layout.activity_main); 52 | ButterKnife.bind(this); 53 | mMainPresenter = new MainPresenterImpl(this); 54 | initToolbar(); 55 | initListener(); 56 | initViewPagerAndTabs(); 57 | } 58 | 59 | private void initListener() { 60 | navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { 61 | @Override 62 | public boolean onNavigationItemSelected(MenuItem menuItem) { 63 | menuItem.setChecked(true); 64 | int id = menuItem.getItemId(); 65 | mMainPresenter.switchNavigation(id); 66 | return true; 67 | } 68 | }); 69 | } 70 | private void initToolbar() { 71 | setSupportActionBar(toolbar); 72 | toolbar.setTitle(getString(R.string.app_name)); 73 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( 74 | this, drawerLayout, toolbar, R.string.navigation_drawer_open, 75 | R.string.navigation_drawer_close); 76 | 77 | drawerLayout.setDrawerListener(toggle); 78 | toggle.syncState(); 79 | } 80 | 81 | private void initViewPagerAndTabs() { 82 | ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager); 83 | mPagerAdapter = new PagerAdapter(getSupportFragmentManager()); 84 | mPagerAdapter.addFragment(new ArticleFragment(), getString(R.string.article)); 85 | mPagerAdapter.addFragment(new ArticleFragment(), getString(R.string.song)); 86 | mPagerAdapter.addFragment(new ArticleFragment(), getString(R.string.my)); 87 | viewPager.setAdapter(mPagerAdapter); 88 | TabLayout tabLayout = (TabLayout) findViewById(R.id.layout_tab); 89 | tabLayout.setupWithViewPager(viewPager); 90 | } 91 | static class PagerAdapter extends FragmentPagerAdapter { 92 | 93 | private List fragmentList = new ArrayList<>(); 94 | private List fragmentTitleList = new ArrayList<>(); 95 | 96 | public PagerAdapter(FragmentManager fragmentManager) { 97 | super(fragmentManager); 98 | } 99 | 100 | public void addFragment(Fragment fragment, String title) { 101 | fragmentList.add(fragment); 102 | fragmentTitleList.add(title); 103 | } 104 | 105 | @Override 106 | public Fragment getItem(int position) { 107 | return fragmentList.get(position); 108 | } 109 | 110 | @Override 111 | public int getCount() { 112 | return fragmentList.size(); 113 | } 114 | 115 | @Override 116 | public CharSequence getPageTitle(int position) { 117 | return fragmentTitleList.get(position); 118 | } 119 | } 120 | 121 | @Override 122 | public void switchHome() { 123 | 124 | } 125 | 126 | @Override 127 | public void switchCommunity() { 128 | 129 | } 130 | 131 | @Override 132 | public void switchInstructions() { 133 | 134 | } 135 | 136 | @Override 137 | public void switchAboutUs() { 138 | 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/refresh/CircleImageView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.rxjava_retrofit_mvp_md.refresh; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.graphics.Canvas; 22 | import android.graphics.Color; 23 | import android.graphics.Paint; 24 | import android.graphics.RadialGradient; 25 | import android.graphics.Shader; 26 | import android.graphics.drawable.ShapeDrawable; 27 | import android.graphics.drawable.shapes.OvalShape; 28 | import android.support.v4.view.ViewCompat; 29 | import android.view.animation.Animation; 30 | import android.widget.ImageView; 31 | 32 | /** 33 | * Private class created to work around issues with AnimationListeners being 34 | * called before the animation is actually complete and support shadows on older 35 | * platforms. 36 | * 37 | * @hide 38 | */ 39 | class CircleImageView extends ImageView { 40 | 41 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 42 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 43 | // PX 44 | private static final float X_OFFSET = 0f; 45 | private static final float Y_OFFSET = 1.75f; 46 | private static final float SHADOW_RADIUS = 3.5f; 47 | private static final int SHADOW_ELEVATION = 4; 48 | 49 | private Animation.AnimationListener mListener; 50 | private int mShadowRadius; 51 | 52 | public CircleImageView(Context context, int color, final float radius) { 53 | super(context); 54 | final float density = getContext().getResources().getDisplayMetrics().density; 55 | final int diameter = (int) (radius * density * 2); 56 | final int shadowYOffset = (int) (density * Y_OFFSET); 57 | final int shadowXOffset = (int) (density * X_OFFSET); 58 | 59 | mShadowRadius = (int) (density * SHADOW_RADIUS); 60 | 61 | ShapeDrawable circle; 62 | if (elevationSupported()) { 63 | circle = new ShapeDrawable(new OvalShape()); 64 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 65 | } else { 66 | OvalShape oval = new OvalShadow(mShadowRadius, diameter); 67 | circle = new ShapeDrawable(oval); 68 | ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); 69 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 70 | KEY_SHADOW_COLOR); 71 | final int padding = (int) mShadowRadius; 72 | // set padding so the inner image sits correctly within the shadow. 73 | setPadding(padding, padding, padding, padding); 74 | } 75 | circle.getPaint().setColor(color); 76 | setBackgroundDrawable(circle); 77 | } 78 | 79 | private boolean elevationSupported() { 80 | return android.os.Build.VERSION.SDK_INT >= 21; 81 | } 82 | 83 | @Override 84 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 85 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 86 | if (!elevationSupported()) { 87 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight() 88 | + mShadowRadius*2); 89 | } 90 | } 91 | 92 | public void setAnimationListener(Animation.AnimationListener listener) { 93 | mListener = listener; 94 | } 95 | 96 | @Override 97 | public void onAnimationStart() { 98 | super.onAnimationStart(); 99 | if (mListener != null) { 100 | mListener.onAnimationStart(getAnimation()); 101 | } 102 | } 103 | 104 | @Override 105 | public void onAnimationEnd() { 106 | super.onAnimationEnd(); 107 | if (mListener != null) { 108 | mListener.onAnimationEnd(getAnimation()); 109 | } 110 | } 111 | 112 | /** 113 | * Update the background color of the circle image view. 114 | */ 115 | public void setBackgroundColor(int colorRes) { 116 | if (getBackground() instanceof ShapeDrawable) { 117 | final Resources res = getResources(); 118 | ((ShapeDrawable) getBackground()).getPaint().setColor(res.getColor(colorRes)); 119 | } 120 | } 121 | 122 | private class OvalShadow extends OvalShape { 123 | private RadialGradient mRadialGradient; 124 | private int mShadowRadius; 125 | private Paint mShadowPaint; 126 | private int mCircleDiameter; 127 | 128 | public OvalShadow(int shadowRadius, int circleDiameter) { 129 | super(); 130 | mShadowPaint = new Paint(); 131 | mShadowRadius = shadowRadius; 132 | mCircleDiameter = circleDiameter; 133 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, 134 | mShadowRadius, new int[] { 135 | FILL_SHADOW_COLOR, Color.TRANSPARENT 136 | }, null, Shader.TileMode.CLAMP); 137 | mShadowPaint.setShader(mRadialGradient); 138 | } 139 | 140 | @Override 141 | public void draw(Canvas canvas, Paint paint) { 142 | final int viewWidth = CircleImageView.this.getWidth(); 143 | final int viewHeight = CircleImageView.this.getHeight(); 144 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius), 145 | mShadowPaint); 146 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/refresh/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 | 17 | package com.example.rxjava_retrofit_mvp_md.refresh; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.graphics.Canvas; 22 | import android.graphics.Color; 23 | import android.graphics.ColorFilter; 24 | import android.graphics.Paint; 25 | import android.graphics.Paint.Style; 26 | import android.graphics.Path; 27 | import android.graphics.PixelFormat; 28 | import android.graphics.Rect; 29 | import android.graphics.RectF; 30 | import android.graphics.drawable.Animatable; 31 | import android.graphics.drawable.Drawable; 32 | import android.support.annotation.IntDef; 33 | import android.support.annotation.NonNull; 34 | import android.util.DisplayMetrics; 35 | import android.view.View; 36 | import android.view.animation.AccelerateDecelerateInterpolator; 37 | import android.view.animation.Animation; 38 | import android.view.animation.Interpolator; 39 | import android.view.animation.LinearInterpolator; 40 | import android.view.animation.Transformation; 41 | 42 | import java.lang.annotation.Retention; 43 | import java.lang.annotation.RetentionPolicy; 44 | import java.util.ArrayList; 45 | 46 | /** 47 | * Fancy progress indicator for Material theme. 48 | * 49 | * @hide 50 | */ 51 | class MaterialProgressDrawable extends Drawable implements Animatable { 52 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 53 | private static final Interpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); 54 | private static final Interpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); 55 | private static final Interpolator EASE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); 56 | 57 | @Retention(RetentionPolicy.CLASS) 58 | @IntDef({LARGE, DEFAULT}) 59 | public @interface ProgressDrawableSize {} 60 | // Maps to ProgressBar.Large style 61 | static final int LARGE = 0; 62 | // Maps to ProgressBar default style 63 | static final int DEFAULT = 1; 64 | 65 | // Maps to ProgressBar default style 66 | private static final int CIRCLE_DIAMETER = 40; 67 | private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 68 | private static final float STROKE_WIDTH = 2.5f; 69 | 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 | private final int[] COLORS = new int[] { 76 | Color.BLACK 77 | }; 78 | 79 | /** The duration of a single progress spin in milliseconds. */ 80 | private static final int ANIMATION_DURATION = 1000 * 80 / 60; 81 | 82 | /** The number of points in the progress "star". */ 83 | private static final float NUM_POINTS = 5f; 84 | /** The list of animators operating on this drawable. */ 85 | private final ArrayList mAnimators = new ArrayList(); 86 | 87 | /** The indicator ring, used to manage animation state. */ 88 | private final Ring mRing; 89 | 90 | /** Canvas rotation in degrees. */ 91 | private float mRotation; 92 | 93 | /** Layout info for the arrowhead in dp */ 94 | private static final int ARROW_WIDTH = 10; 95 | private static final int ARROW_HEIGHT = 5; 96 | private static final float ARROW_OFFSET_ANGLE = 5; 97 | 98 | /** Layout info for the arrowhead for the large spinner in dp */ 99 | private static final int ARROW_WIDTH_LARGE = 12; 100 | private static final int ARROW_HEIGHT_LARGE = 6; 101 | private static final float MAX_PROGRESS_ARC = .8f; 102 | 103 | private Resources mResources; 104 | private View mParent; 105 | private Animation mAnimation; 106 | private float mRotationCount; 107 | private double mWidth; 108 | private double mHeight; 109 | private Animation mFinishAnimation; 110 | 111 | public MaterialProgressDrawable(Context context, View parent) { 112 | mParent = parent; 113 | mResources = context.getResources(); 114 | 115 | mRing = new Ring(mCallback); 116 | mRing.setColors(COLORS); 117 | 118 | updateSizes(DEFAULT); 119 | setupAnimators(); 120 | } 121 | 122 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 123 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 124 | final Ring ring = mRing; 125 | final DisplayMetrics metrics = mResources.getDisplayMetrics(); 126 | final float screenDensity = metrics.density; 127 | 128 | mWidth = progressCircleWidth * screenDensity; 129 | mHeight = progressCircleHeight * screenDensity; 130 | ring.setStrokeWidth((float) strokeWidth * screenDensity); 131 | ring.setCenterRadius(centerRadius * screenDensity); 132 | ring.setColorIndex(0); 133 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 134 | ring.setInsets((int) mWidth, (int) mHeight); 135 | } 136 | 137 | /** 138 | * Set the overall size for the progress spinner. This updates the radius 139 | * and stroke width of the ring. 140 | * 141 | * @param size One of {@link com.orangegangsters.github.swiperefreshlayout.MaterialProgressDrawable.LARGE} or 142 | * {@link com.orangegangsters.github.swiperefreshlayout.MaterialProgressDrawable.DEFAULT} 143 | */ 144 | public void updateSizes(@ProgressDrawableSize int size) { 145 | if (size == LARGE) { 146 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 147 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 148 | } else { 149 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 150 | ARROW_WIDTH, ARROW_HEIGHT); 151 | } 152 | } 153 | 154 | /** 155 | * @param show Set to true to display the arrowhead on the progress spinner. 156 | */ 157 | public void showArrow(boolean show) { 158 | mRing.setShowArrow(show); 159 | } 160 | 161 | /** 162 | * @param scale Set the scale of the arrowhead for the spinner. 163 | */ 164 | public void setArrowScale(float scale) { 165 | mRing.setArrowScale(scale); 166 | } 167 | 168 | /** 169 | * Set the start and end trim for the progress spinner arc. 170 | * 171 | * @param startAngle start angle 172 | * @param endAngle end angle 173 | */ 174 | public void setStartEndTrim(float startAngle, float endAngle) { 175 | mRing.setStartTrim(startAngle); 176 | mRing.setEndTrim(endAngle); 177 | } 178 | 179 | /** 180 | * Set the amount of rotation to apply to the progress spinner. 181 | * 182 | * @param rotation Rotation is from [0..1] 183 | */ 184 | public void setProgressRotation(float rotation) { 185 | mRing.setRotation(rotation); 186 | } 187 | 188 | /** 189 | * Update the background color of the circle image view. 190 | */ 191 | public void setBackgroundColor(int color) { 192 | mRing.setBackgroundColor(color); 193 | } 194 | 195 | /** 196 | * Set the colors used in the progress animation from color resources. 197 | * The first color will also be the color of the bar that grows in response 198 | * to a user swipe gesture. 199 | * 200 | * @param colors 201 | */ 202 | public void setColorSchemeColors(int... colors) { 203 | mRing.setColors(colors); 204 | mRing.setColorIndex(0); 205 | } 206 | 207 | @Override 208 | public int getIntrinsicHeight() { 209 | return (int) mHeight; 210 | } 211 | 212 | @Override 213 | public int getIntrinsicWidth() { 214 | return (int) mWidth; 215 | } 216 | 217 | @Override 218 | public void draw(Canvas c) { 219 | final Rect bounds = getBounds(); 220 | final int saveCount = c.save(); 221 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 222 | mRing.draw(c, bounds); 223 | c.restoreToCount(saveCount); 224 | } 225 | 226 | @Override 227 | public void setAlpha(int alpha) { 228 | mRing.setAlpha(alpha); 229 | } 230 | 231 | public int getAlpha() { 232 | return mRing.getAlpha(); 233 | } 234 | 235 | @Override 236 | public void setColorFilter(ColorFilter colorFilter) { 237 | mRing.setColorFilter(colorFilter); 238 | } 239 | 240 | @SuppressWarnings("unused") 241 | void setRotation(float rotation) { 242 | mRotation = rotation; 243 | invalidateSelf(); 244 | } 245 | 246 | @SuppressWarnings("unused") 247 | private float getRotation() { 248 | return mRotation; 249 | } 250 | 251 | @Override 252 | public int getOpacity() { 253 | return PixelFormat.TRANSLUCENT; 254 | } 255 | 256 | @Override 257 | public boolean isRunning() { 258 | final ArrayList animators = mAnimators; 259 | final int N = animators.size(); 260 | for (int i = 0; i < N; i++) { 261 | final Animation animator = animators.get(i); 262 | if (animator.hasStarted() && !animator.hasEnded()) { 263 | return true; 264 | } 265 | } 266 | return false; 267 | } 268 | 269 | @Override 270 | public void start() { 271 | mAnimation.reset(); 272 | mRing.storeOriginals(); 273 | // Already showing some part of the ring 274 | if (mRing.getEndTrim() != mRing.getStartTrim()) { 275 | mParent.startAnimation(mFinishAnimation); 276 | } else { 277 | mRing.setColorIndex(0); 278 | mRing.resetOriginals(); 279 | mParent.startAnimation(mAnimation); 280 | } 281 | } 282 | 283 | @Override 284 | public void stop() { 285 | mParent.clearAnimation(); 286 | setRotation(0); 287 | mRing.setShowArrow(false); 288 | mRing.setColorIndex(0); 289 | mRing.resetOriginals(); 290 | } 291 | 292 | private void setupAnimators() { 293 | final Ring ring = mRing; 294 | final Animation finishRingAnimation = new Animation() { 295 | public void applyTransformation(float interpolatedTime, Transformation t) { 296 | // shrink back down and complete a full rotation before starting other circles 297 | // Rotation goes between [0..1]. 298 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() 299 | / MAX_PROGRESS_ARC) + 1f); 300 | final float startTrim = ring.getStartingStartTrim() 301 | + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) 302 | * interpolatedTime; 303 | ring.setStartTrim(startTrim); 304 | final float rotation = ring.getStartingRotation() 305 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 306 | ring.setRotation(rotation); 307 | ring.setArrowScale(1 - interpolatedTime); 308 | } 309 | }; 310 | finishRingAnimation.setInterpolator(EASE_INTERPOLATOR); 311 | finishRingAnimation.setDuration(ANIMATION_DURATION/2); 312 | finishRingAnimation.setAnimationListener(new Animation.AnimationListener() { 313 | 314 | @Override 315 | public void onAnimationStart(Animation animation) { 316 | } 317 | 318 | @Override 319 | public void onAnimationEnd(Animation animation) { 320 | ring.goToNextColor(); 321 | ring.storeOriginals(); 322 | ring.setShowArrow(false); 323 | mParent.startAnimation(mAnimation); 324 | } 325 | 326 | @Override 327 | public void onAnimationRepeat(Animation animation) { 328 | } 329 | }); 330 | final Animation animation = new Animation() { 331 | @Override 332 | public void applyTransformation(float interpolatedTime, Transformation t) { 333 | // The minProgressArc is calculated from 0 to create an angle that 334 | // matches the stroke width. 335 | final float minProgressArc = (float) Math.toRadians(ring.getStrokeWidth() 336 | / (2 * Math.PI * ring.getCenterRadius())); 337 | final float startingEndTrim = ring.getStartingEndTrim(); 338 | final float startingTrim = ring.getStartingStartTrim(); 339 | final float startingRotation = ring.getStartingRotation(); 340 | 341 | // Offset the minProgressArc to where the endTrim is located. 342 | final float minArc = MAX_PROGRESS_ARC - minProgressArc; 343 | final float endTrim = startingEndTrim 344 | + (minArc * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime)); 345 | ring.setEndTrim(endTrim); 346 | 347 | final float startTrim = startingTrim 348 | + (MAX_PROGRESS_ARC * END_CURVE_INTERPOLATOR 349 | .getInterpolation(interpolatedTime)); 350 | ring.setStartTrim(startTrim); 351 | 352 | final float rotation = startingRotation + (0.25f * interpolatedTime); 353 | ring.setRotation(rotation); 354 | 355 | float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime) 356 | + (720.0f * (mRotationCount / NUM_POINTS)); 357 | setRotation(groupRotation); 358 | } 359 | }; 360 | animation.setRepeatCount(Animation.INFINITE); 361 | animation.setRepeatMode(Animation.RESTART); 362 | animation.setInterpolator(LINEAR_INTERPOLATOR); 363 | animation.setDuration(ANIMATION_DURATION); 364 | animation.setAnimationListener(new Animation.AnimationListener() { 365 | 366 | @Override 367 | public void onAnimationStart(Animation animation) { 368 | mRotationCount = 0; 369 | } 370 | 371 | @Override 372 | public void onAnimationEnd(Animation animation) { 373 | // do nothing 374 | } 375 | 376 | @Override 377 | public void onAnimationRepeat(Animation animation) { 378 | ring.storeOriginals(); 379 | ring.goToNextColor(); 380 | ring.setStartTrim(ring.getEndTrim()); 381 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 382 | } 383 | }); 384 | mFinishAnimation = finishRingAnimation; 385 | mAnimation = animation; 386 | } 387 | 388 | private final Callback mCallback = new Callback() { 389 | @Override 390 | public void invalidateDrawable(Drawable d) { 391 | invalidateSelf(); 392 | } 393 | 394 | @Override 395 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 396 | scheduleSelf(what, when); 397 | } 398 | 399 | @Override 400 | public void unscheduleDrawable(Drawable d, Runnable what) { 401 | unscheduleSelf(what); 402 | } 403 | }; 404 | 405 | private static class Ring { 406 | private final RectF mTempBounds = new RectF(); 407 | private final Paint mPaint = new Paint(); 408 | private final Paint mArrowPaint = new Paint(); 409 | 410 | private final Callback mCallback; 411 | 412 | private float mStartTrim = 0.0f; 413 | private float mEndTrim = 0.0f; 414 | private float mRotation = 0.0f; 415 | private float mStrokeWidth = 5.0f; 416 | private float mStrokeInset = 2.5f; 417 | 418 | private int[] mColors; 419 | // mColorIndex represents the offset into the available mColors that the 420 | // progress circle should currently display. As the progress circle is 421 | // animating, the mColorIndex moves by one to the next available color. 422 | private int mColorIndex; 423 | private float mStartingStartTrim; 424 | private float mStartingEndTrim; 425 | private float mStartingRotation; 426 | private boolean mShowArrow; 427 | private Path mArrow; 428 | private float mArrowScale; 429 | private double mRingCenterRadius; 430 | private int mArrowWidth; 431 | private int mArrowHeight; 432 | private int mAlpha; 433 | private final Paint mCirclePaint = new Paint(); 434 | private int mBackgroundColor; 435 | 436 | public Ring(Callback callback) { 437 | mCallback = callback; 438 | 439 | mPaint.setStrokeCap(Paint.Cap.SQUARE); 440 | mPaint.setAntiAlias(true); 441 | mPaint.setStyle(Style.STROKE); 442 | 443 | mArrowPaint.setStyle(Style.FILL); 444 | mArrowPaint.setAntiAlias(true); 445 | } 446 | 447 | public void setBackgroundColor(int color) { 448 | mBackgroundColor = color; 449 | } 450 | 451 | /** 452 | * Set the dimensions of the arrowhead. 453 | * 454 | * @param width Width of the hypotenuse of the arrow head 455 | * @param height Height of the arrow point 456 | */ 457 | public void setArrowDimensions(float width, float height) { 458 | mArrowWidth = (int) width; 459 | mArrowHeight = (int) height; 460 | } 461 | 462 | /** 463 | * Draw the progress spinner 464 | */ 465 | public void draw(Canvas c, Rect bounds) { 466 | final RectF arcBounds = mTempBounds; 467 | arcBounds.set(bounds); 468 | arcBounds.inset(mStrokeInset, mStrokeInset); 469 | 470 | final float startAngle = (mStartTrim + mRotation) * 360; 471 | final float endAngle = (mEndTrim + mRotation) * 360; 472 | float sweepAngle = endAngle - startAngle; 473 | 474 | mPaint.setColor(mColors[mColorIndex]); 475 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); 476 | 477 | drawTriangle(c, startAngle, sweepAngle, bounds); 478 | 479 | if (mAlpha < 255) { 480 | mCirclePaint.setColor(mBackgroundColor); 481 | mCirclePaint.setAlpha(255 - mAlpha); 482 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, 483 | mCirclePaint); 484 | } 485 | } 486 | 487 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 488 | if (mShowArrow) { 489 | if (mArrow == null) { 490 | mArrow = new Path(); 491 | mArrow.setFillType(Path.FillType.EVEN_ODD); 492 | } else { 493 | mArrow.reset(); 494 | } 495 | 496 | // Adjust the position of the triangle so that it is inset as 497 | // much as the arc, but also centered on the arc. 498 | float inset = (int) mStrokeInset / 2 * mArrowScale; 499 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 500 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 501 | 502 | // Update the path each time. This works around an issue in SKIA 503 | // where concatenating a rotation matrix to a scale matrix 504 | // ignored a starting negative rotation. This appears to have 505 | // been fixed as of API 21. 506 | mArrow.moveTo(0, 0); 507 | mArrow.lineTo(mArrowWidth * mArrowScale, 0); 508 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 509 | * mArrowScale)); 510 | mArrow.offset(x - inset, y); 511 | mArrow.close(); 512 | // draw a triangle 513 | mArrowPaint.setColor(mColors[mColorIndex]); 514 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 515 | bounds.exactCenterY()); 516 | c.drawPath(mArrow, mArrowPaint); 517 | } 518 | } 519 | 520 | /** 521 | * Set the colors the progress spinner alternates between. 522 | * 523 | * @param colors Array of integers describing the colors. Must be non-null. 524 | */ 525 | public void setColors(@NonNull int[] colors) { 526 | mColors = colors; 527 | // if colors are reset, make sure to reset the color index as well 528 | setColorIndex(0); 529 | } 530 | 531 | /** 532 | * @param index Index into the color array of the color to display in 533 | * the progress spinner. 534 | */ 535 | public void setColorIndex(int index) { 536 | mColorIndex = index; 537 | } 538 | 539 | /** 540 | * Proceed to the next available ring color. This will automatically 541 | * wrap back to the beginning of colors. 542 | */ 543 | public void goToNextColor() { 544 | mColorIndex = (mColorIndex + 1) % (mColors.length); 545 | } 546 | 547 | public void setColorFilter(ColorFilter filter) { 548 | mPaint.setColorFilter(filter); 549 | invalidateSelf(); 550 | } 551 | 552 | /** 553 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 554 | */ 555 | public void setAlpha(int alpha) { 556 | mAlpha = alpha; 557 | } 558 | 559 | /** 560 | * @return Current alpha of the progress spinner and arrowhead. 561 | */ 562 | public int getAlpha() { 563 | return mAlpha; 564 | } 565 | 566 | /** 567 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 568 | */ 569 | public void setStrokeWidth(float strokeWidth) { 570 | mStrokeWidth = strokeWidth; 571 | mPaint.setStrokeWidth(strokeWidth); 572 | invalidateSelf(); 573 | } 574 | 575 | @SuppressWarnings("unused") 576 | public float getStrokeWidth() { 577 | return mStrokeWidth; 578 | } 579 | 580 | @SuppressWarnings("unused") 581 | public void setStartTrim(float startTrim) { 582 | mStartTrim = startTrim; 583 | invalidateSelf(); 584 | } 585 | 586 | @SuppressWarnings("unused") 587 | public float getStartTrim() { 588 | return mStartTrim; 589 | } 590 | 591 | public float getStartingStartTrim() { 592 | return mStartingStartTrim; 593 | } 594 | 595 | public float getStartingEndTrim() { 596 | return mStartingEndTrim; 597 | } 598 | 599 | @SuppressWarnings("unused") 600 | public void setEndTrim(float endTrim) { 601 | mEndTrim = endTrim; 602 | invalidateSelf(); 603 | } 604 | 605 | @SuppressWarnings("unused") 606 | public float getEndTrim() { 607 | return mEndTrim; 608 | } 609 | 610 | @SuppressWarnings("unused") 611 | public void setRotation(float rotation) { 612 | mRotation = rotation; 613 | invalidateSelf(); 614 | } 615 | 616 | @SuppressWarnings("unused") 617 | public float getRotation() { 618 | return mRotation; 619 | } 620 | 621 | public void setInsets(int width, int height) { 622 | final float minEdge = (float) Math.min(width, height); 623 | float insets; 624 | if (mRingCenterRadius <= 0 || minEdge < 0) { 625 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 626 | } else { 627 | insets = (float) (minEdge / 2.0f - mRingCenterRadius); 628 | } 629 | mStrokeInset = insets; 630 | } 631 | 632 | @SuppressWarnings("unused") 633 | public float getInsets() { 634 | return mStrokeInset; 635 | } 636 | 637 | /** 638 | * @param centerRadius Inner radius in px of the circle the progress 639 | * spinner arc traces. 640 | */ 641 | public void setCenterRadius(double centerRadius) { 642 | mRingCenterRadius = centerRadius; 643 | } 644 | 645 | public double getCenterRadius() { 646 | return mRingCenterRadius; 647 | } 648 | 649 | /** 650 | * @param show Set to true to show the arrow head on the progress spinner. 651 | */ 652 | public void setShowArrow(boolean show) { 653 | if (mShowArrow != show) { 654 | mShowArrow = show; 655 | invalidateSelf(); 656 | } 657 | } 658 | 659 | /** 660 | * @param scale Set the scale of the arrowhead for the spinner. 661 | */ 662 | public void setArrowScale(float scale) { 663 | if (scale != mArrowScale) { 664 | mArrowScale = scale; 665 | invalidateSelf(); 666 | } 667 | } 668 | 669 | /** 670 | * @return The amount the progress spinner is currently rotated, between [0..1]. 671 | */ 672 | public float getStartingRotation() { 673 | return mStartingRotation; 674 | } 675 | 676 | /** 677 | * If the start / end trim are offset to begin with, store them so that 678 | * animation starts from that offset. 679 | */ 680 | public void storeOriginals() { 681 | mStartingStartTrim = mStartTrim; 682 | mStartingEndTrim = mEndTrim; 683 | mStartingRotation = mRotation; 684 | } 685 | 686 | /** 687 | * Reset the progress spinner to default rotation, start and end angles. 688 | */ 689 | public void resetOriginals() { 690 | mStartingStartTrim = 0; 691 | mStartingEndTrim = 0; 692 | mStartingRotation = 0; 693 | setStartTrim(0); 694 | setEndTrim(0); 695 | setRotation(0); 696 | } 697 | 698 | private void invalidateSelf() { 699 | mCallback.invalidateDrawable(null); 700 | } 701 | } 702 | 703 | /** 704 | * Squishes the interpolation curve into the second half of the animation. 705 | */ 706 | private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator { 707 | @Override 708 | public float getInterpolation(float input) { 709 | return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f)); 710 | } 711 | } 712 | 713 | /** 714 | * Squishes the interpolation curve into the first half of the animation. 715 | */ 716 | private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator { 717 | @Override 718 | public float getInterpolation(float input) { 719 | return super.getInterpolation(Math.min(1, input * 2.0f)); 720 | } 721 | } 722 | } 723 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/refresh/RefreshLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.rxjava_retrofit_mvp_md.refresh; 18 | 19 | 20 | import android.content.Context; 21 | import android.content.res.Resources; 22 | import android.content.res.TypedArray; 23 | import android.support.v4.view.MotionEventCompat; 24 | import android.support.v4.view.ViewCompat; 25 | import android.util.AttributeSet; 26 | import android.util.DisplayMetrics; 27 | import android.view.MotionEvent; 28 | import android.view.View; 29 | import android.view.ViewConfiguration; 30 | import android.view.ViewGroup; 31 | import android.view.animation.Animation; 32 | import android.view.animation.Animation.AnimationListener; 33 | import android.view.animation.DecelerateInterpolator; 34 | import android.view.animation.Transformation; 35 | import android.widget.AbsListView; 36 | 37 | import com.example.rxjava_retrofit_mvp_md.R; 38 | 39 | 40 | /** 41 | * The SwipeRefreshLayout should be used whenever the user can refresh the 42 | * contents of a view via a vertical swipe gesture. The activity that 43 | * instantiates this view should add an OnRefreshListener to be notified 44 | * whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout 45 | * will notify the listener each and every time the gesture is completed again; 46 | * the listener is responsible for correctly determining when to actually 47 | * initiate a refresh of its content. If the listener determines there should 48 | * not be a refresh, it must call setRefreshing(false) to cancel any visual 49 | * indication of a refresh. If an activity wishes to show just the progress 50 | * animation, it should call setRefreshing(true). To disable the gesture and 51 | * progress animation, call setEnabled(false) on the view. 52 | *

53 | * This layout should be made the parent of the view that will be refreshed as a 54 | * result of the gesture and can only support one direct child. This view will 55 | * also be made the target of the gesture and will be forced to match both the 56 | * width and the height supplied in this layout. The SwipeRefreshLayout does not 57 | * provide accessibility events; instead, a menu item must be provided to allow 58 | * refresh of the content wherever this gesture is used. 59 | *

60 | */ 61 | public class RefreshLayout extends ViewGroup { 62 | // Maps to ProgressBar.Large style 63 | public static final int LARGE = MaterialProgressDrawable.LARGE; 64 | // Maps to ProgressBar default style 65 | public static final int DEFAULT = MaterialProgressDrawable.DEFAULT; 66 | 67 | private static final String LOG_TAG = RefreshLayout.class.getSimpleName(); 68 | 69 | private static final int MAX_ALPHA = 255; 70 | private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA); 71 | 72 | private static final int CIRCLE_DIAMETER = 40; 73 | private static final int CIRCLE_DIAMETER_LARGE = 56; 74 | 75 | private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; 76 | private static final int INVALID_POINTER = -1; 77 | private static final float DRAG_RATE = .5f; 78 | 79 | // Max amount of circle that can be filled by progress during swipe gesture, 80 | // where 1.0 is a full circle 81 | private static final float MAX_PROGRESS_ANGLE = .8f; 82 | 83 | private static final int SCALE_DOWN_DURATION = 150; 84 | 85 | private static final int ALPHA_ANIMATION_DURATION = 300; 86 | 87 | private static final int ANIMATE_TO_TRIGGER_DURATION = 200; 88 | 89 | private static final int ANIMATE_TO_START_DURATION = 200; 90 | 91 | // Default background for the progress spinner 92 | private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; 93 | // Default offset in dips from the top of the view to where the progress spinner should stop 94 | private static final int DEFAULT_CIRCLE_TARGET = 64; 95 | 96 | private View mTarget; // the target of the gesture 97 | private SwipeRefreshLayoutDirection mDirection; 98 | private boolean mBothDirection; 99 | private OnRefreshListener mListener; 100 | private boolean mRefreshing = false; 101 | private int mTouchSlop; 102 | private float mTotalDragDistance = -1; 103 | private int mMediumAnimationDuration; 104 | private int mCurrentTargetOffsetTop; 105 | // Whether or not the starting offset has been determined. 106 | private boolean mOriginalOffsetCalculated = false; 107 | 108 | private float mInitialMotionY; 109 | private boolean mIsBeingDragged; 110 | private int mActivePointerId = INVALID_POINTER; 111 | // Whether this item is scaled up rather than clipped 112 | private boolean mScale; 113 | 114 | // Target is returning to its start offset because it was cancelled or a 115 | // refresh was triggered. 116 | private boolean mReturningToStart; 117 | private final DecelerateInterpolator mDecelerateInterpolator; 118 | private static final int[] LAYOUT_ATTRS = new int[]{ 119 | android.R.attr.enabled 120 | }; 121 | 122 | private CircleImageView mCircleView; 123 | private int mCircleViewIndex = -1; 124 | 125 | protected int mFrom; 126 | 127 | private float mStartingScale; 128 | 129 | protected int mOriginalOffsetTop; 130 | 131 | private MaterialProgressDrawable mProgress; 132 | 133 | private Animation mScaleAnimation; 134 | 135 | private Animation mScaleDownAnimation; 136 | 137 | private Animation mAlphaStartAnimation; 138 | 139 | private Animation mAlphaMaxAnimation; 140 | 141 | private Animation mScaleDownToStartAnimation; 142 | 143 | private float mSpinnerFinalOffset; 144 | 145 | private boolean mNotify; 146 | 147 | private int mCircleWidth; 148 | 149 | private int mCircleHeight; 150 | 151 | // Whether the client has set a custom starting position; 152 | private boolean mUsingCustomStart; 153 | 154 | private AnimationListener mRefreshListener = new AnimationListener() { 155 | @Override 156 | public void onAnimationStart(Animation animation) { 157 | } 158 | 159 | @Override 160 | public void onAnimationRepeat(Animation animation) { 161 | } 162 | 163 | @Override 164 | public void onAnimationEnd(Animation animation) { 165 | if (mRefreshing) { 166 | // Make sure the progress view is fully visible 167 | mProgress.setAlpha(MAX_ALPHA); 168 | mProgress.start(); 169 | if (mNotify) { 170 | if (mListener != null) { 171 | mListener.onRefresh(mDirection); 172 | } 173 | } 174 | } else { 175 | mProgress.stop(); 176 | mCircleView.setVisibility(View.GONE); 177 | setColorViewAlpha(MAX_ALPHA); 178 | // Return the circle to its start position 179 | if (mScale) { 180 | setAnimationProgress(0 /* animation complete and view is hidden */); 181 | } else { 182 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop, 183 | true /* requires update */); 184 | } 185 | } 186 | mCurrentTargetOffsetTop = mCircleView.getTop(); 187 | } 188 | }; 189 | 190 | private void setColorViewAlpha(int targetAlpha) { 191 | mCircleView.getBackground().setAlpha(targetAlpha); 192 | mProgress.setAlpha(targetAlpha); 193 | } 194 | 195 | /** 196 | * The refresh indicator starting and resting position is always positioned 197 | * near the top of the refreshing content. This position is a consistent 198 | * location, but can be adjusted in either direction based on whether or not 199 | * there is a toolbar or actionbar present. 200 | * 201 | * @param scale Set to true if there is no view at a higher z-order than 202 | * where the progress spinner is set to appear. 203 | * @param start The offset in pixels from the top of this view at which the 204 | * progress spinner should appear. 205 | * @param end The offset in pixels from the top of this view at which the 206 | * progress spinner should come to rest after a successful swipe 207 | * gesture. 208 | */ 209 | /* 210 | public void setProgressViewOffset(boolean scale, int start, int end) { 211 | mScale = scale; 212 | mCircleView.setVisibility(View.GONE); 213 | mOriginalOffsetTop = mCurrentTargetOffsetTop = start; 214 | mSpinnerFinalOffset = end; 215 | mUsingCustomStart = true; 216 | mCircleView.invalidate(); 217 | }*/ 218 | 219 | /** 220 | * The refresh indicator resting position is always positioned near the top 221 | * of the refreshing content. This position is a consistent location, but 222 | * can be adjusted in either direction based on whether or not there is a 223 | * toolbar or actionbar present. 224 | * 225 | * @param scale Set to true if there is no view at a higher z-order than 226 | * where the progress spinner is set to appear. 227 | * @param end The offset in pixels from the top of this view at which the 228 | * progress spinner should come to rest after a successful swipe 229 | * gesture. 230 | */ 231 | /* 232 | public void setProgressViewEndTarget(boolean scale, int end) { 233 | mSpinnerFinalOffset = end; 234 | mScale = scale; 235 | mCircleView.invalidate(); 236 | }*/ 237 | 238 | /** 239 | * One of DEFAULT, or LARGE. 240 | */ 241 | public void setSize(int size) { 242 | if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) { 243 | return; 244 | } 245 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 246 | if (size == MaterialProgressDrawable.LARGE) { 247 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); 248 | } else { 249 | mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); 250 | } 251 | // force the bounds of the progress circle inside the circle view to 252 | // update by setting it to null before updating its size and then 253 | // re-setting it 254 | mCircleView.setImageDrawable(null); 255 | mProgress.updateSizes(size); 256 | mCircleView.setImageDrawable(mProgress); 257 | } 258 | 259 | /** 260 | * Simple constructor to use when creating a SwipeRefreshLayout from code. 261 | * 262 | * @param context 263 | */ 264 | public RefreshLayout(Context context) { 265 | this(context, null); 266 | } 267 | 268 | /** 269 | * Constructor that is called when inflating SwipeRefreshLayout from XML. 270 | * 271 | * @param context 272 | * @param attrs 273 | */ 274 | public RefreshLayout(Context context, AttributeSet attrs) { 275 | super(context, attrs); 276 | 277 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 278 | 279 | mMediumAnimationDuration = getResources().getInteger( 280 | android.R.integer.config_mediumAnimTime); 281 | 282 | setWillNotDraw(false); 283 | mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); 284 | 285 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 286 | setEnabled(a.getBoolean(0, true)); 287 | a.recycle(); 288 | 289 | final TypedArray a2 = context.obtainStyledAttributes(attrs, R.styleable.SwipyRefreshLayout); 290 | SwipeRefreshLayoutDirection direction 291 | = SwipeRefreshLayoutDirection.getFromInt(a2.getInt(R.styleable.SwipyRefreshLayout_direction, 0)); 292 | if (direction != SwipeRefreshLayoutDirection.BOTH) { 293 | mDirection = direction; 294 | mBothDirection = false; 295 | } else { 296 | mDirection = SwipeRefreshLayoutDirection.TOP; 297 | mBothDirection = true; 298 | } 299 | a2.recycle(); 300 | 301 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 302 | mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); 303 | mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density); 304 | 305 | createProgressView(); 306 | ViewCompat.setChildrenDrawingOrderEnabled(this, true); 307 | // the absolute offset has to take into account that the circle starts at an offset 308 | mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density; 309 | mTotalDragDistance = mSpinnerFinalOffset; 310 | } 311 | 312 | protected int getChildDrawingOrder(int childCount, int i) { 313 | if (mCircleViewIndex < 0) { 314 | return i; 315 | } else if (i == childCount - 1) { 316 | // Draw the selected child last 317 | return mCircleViewIndex; 318 | } else if (i >= mCircleViewIndex) { 319 | // Move the children after the selected child earlier one 320 | return i + 1; 321 | } else { 322 | // Keep the children before the selected child the same 323 | return i; 324 | } 325 | } 326 | 327 | private void createProgressView() { 328 | mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER / 2); 329 | mProgress = new MaterialProgressDrawable(getContext(), this); 330 | mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); 331 | mCircleView.setImageDrawable(mProgress); 332 | mCircleView.setVisibility(View.GONE); 333 | addView(mCircleView); 334 | } 335 | 336 | /** 337 | * Set the listener to be notified when a refresh is triggered via the swipe 338 | * gesture. 339 | */ 340 | public void setOnRefreshListener(OnRefreshListener listener) { 341 | mListener = listener; 342 | } 343 | 344 | /** 345 | * Pre API 11, alpha is used to make the progress circle appear instead of scale. 346 | */ 347 | private boolean isAlphaUsedForScale() { 348 | return android.os.Build.VERSION.SDK_INT < 11; 349 | } 350 | 351 | /** 352 | * Notify the widget that refresh state has changed. Do not call this when 353 | * refresh is triggered by a swipe gesture. 354 | * 355 | * @param refreshing Whether or not the view should show refresh progress. 356 | */ 357 | public void setRefreshing(boolean refreshing) { 358 | if (refreshing && mRefreshing != refreshing) { 359 | // scale and show 360 | mRefreshing = refreshing; 361 | int endTarget = 0; 362 | if (!mUsingCustomStart) { 363 | endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop); 364 | } else { 365 | endTarget = (int) mSpinnerFinalOffset; 366 | } 367 | setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop, 368 | true /* requires update */); 369 | mNotify = false; 370 | startScaleUpAnimation(mRefreshListener); 371 | } else { 372 | setRefreshing(refreshing, false /* notify */); 373 | } 374 | } 375 | 376 | private void startScaleUpAnimation(AnimationListener listener) { 377 | mCircleView.setVisibility(View.VISIBLE); 378 | if (android.os.Build.VERSION.SDK_INT >= 11) { 379 | // Pre API 11, alpha is used in place of scale up to show the 380 | // progress circle appearing. 381 | // Don't adjust the alpha during appearance otherwise. 382 | mProgress.setAlpha(MAX_ALPHA); 383 | } 384 | mScaleAnimation = new Animation() { 385 | @Override 386 | public void applyTransformation(float interpolatedTime, Transformation t) { 387 | setAnimationProgress(interpolatedTime); 388 | } 389 | }; 390 | mScaleAnimation.setDuration(mMediumAnimationDuration); 391 | if (listener != null) { 392 | mCircleView.setAnimationListener(listener); 393 | } 394 | mCircleView.clearAnimation(); 395 | mCircleView.startAnimation(mScaleAnimation); 396 | } 397 | 398 | /** 399 | * Pre API 11, this does an alpha animation. 400 | * 401 | * @param progress 402 | */ 403 | private void setAnimationProgress(float progress) { 404 | if (isAlphaUsedForScale()) { 405 | setColorViewAlpha((int) (progress * MAX_ALPHA)); 406 | } else { 407 | ViewCompat.setScaleX(mCircleView, progress); 408 | ViewCompat.setScaleY(mCircleView, progress); 409 | } 410 | } 411 | 412 | private void setRefreshing(boolean refreshing, final boolean notify) { 413 | if (mRefreshing != refreshing) { 414 | mNotify = notify; 415 | ensureTarget(); 416 | mRefreshing = refreshing; 417 | if (mRefreshing) { 418 | animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener); 419 | } else { 420 | startScaleDownAnimation(mRefreshListener); 421 | } 422 | } 423 | } 424 | 425 | private void startScaleDownAnimation(AnimationListener listener) { 426 | mScaleDownAnimation = new Animation() { 427 | @Override 428 | public void applyTransformation(float interpolatedTime, Transformation t) { 429 | setAnimationProgress(1 - interpolatedTime); 430 | } 431 | }; 432 | mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION); 433 | mCircleView.setAnimationListener(listener); 434 | mCircleView.clearAnimation(); 435 | mCircleView.startAnimation(mScaleDownAnimation); 436 | } 437 | 438 | private void startProgressAlphaStartAnimation() { 439 | mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); 440 | } 441 | 442 | private void startProgressAlphaMaxAnimation() { 443 | mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA); 444 | } 445 | 446 | private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) { 447 | // Pre API 11, alpha is used in place of scale. Don't also use it to 448 | // show the trigger point. 449 | if (mScale && isAlphaUsedForScale()) { 450 | return null; 451 | } 452 | Animation alpha = new Animation() { 453 | @Override 454 | public void applyTransformation(float interpolatedTime, Transformation t) { 455 | mProgress 456 | .setAlpha((int) (startingAlpha + ((endingAlpha - startingAlpha) 457 | * interpolatedTime))); 458 | } 459 | }; 460 | alpha.setDuration(ALPHA_ANIMATION_DURATION); 461 | // Clear out the previous animation listeners. 462 | mCircleView.setAnimationListener(null); 463 | mCircleView.clearAnimation(); 464 | mCircleView.startAnimation(alpha); 465 | return alpha; 466 | } 467 | 468 | /** 469 | * Set the background color of the progress spinner disc. 470 | * 471 | * @param colorRes Resource id of the color. 472 | */ 473 | public void setProgressBackgroundColor(int colorRes) { 474 | mCircleView.setBackgroundColor(colorRes); 475 | mProgress.setBackgroundColor(getResources().getColor(colorRes)); 476 | } 477 | 478 | /** 479 | * @deprecated Use {@link #setColorSchemeResources(int...)} 480 | */ 481 | @Deprecated 482 | public void setColorScheme(int... colors) { 483 | setColorSchemeResources(colors); 484 | } 485 | 486 | /** 487 | * Set the color resources used in the progress animation from color resources. 488 | * The first color will also be the color of the bar that grows in response 489 | * to a user swipe gesture. 490 | * 491 | * @param colorResIds 492 | */ 493 | public void setColorSchemeResources(int... colorResIds) { 494 | final Resources res = getResources(); 495 | int[] colorRes = new int[colorResIds.length]; 496 | for (int i = 0; i < colorResIds.length; i++) { 497 | colorRes[i] = res.getColor(colorResIds[i]); 498 | } 499 | setColorSchemeColors(colorRes); 500 | } 501 | 502 | /** 503 | * Set the colors used in the progress animation. The first 504 | * color will also be the color of the bar that grows in response to a user 505 | * swipe gesture. 506 | * 507 | * @param colors 508 | */ 509 | public void setColorSchemeColors(int... colors) { 510 | ensureTarget(); 511 | mProgress.setColorSchemeColors(colors); 512 | } 513 | 514 | /** 515 | * @return Whether the SwipeRefreshWidget is actively showing refresh 516 | * progress. 517 | */ 518 | public boolean isRefreshing() { 519 | return mRefreshing; 520 | } 521 | 522 | private void ensureTarget() { 523 | // Don't bother getting the parent height if the parent hasn't been laid 524 | // out yet. 525 | if (mTarget == null) { 526 | for (int i = 0; i < getChildCount(); i++) { 527 | View child = getChildAt(i); 528 | if (!child.equals(mCircleView)) { 529 | mTarget = child; 530 | break; 531 | } 532 | } 533 | } 534 | } 535 | 536 | /** 537 | * Set the distance to trigger a sync in dips 538 | * 539 | * @param distance 540 | */ 541 | public void setDistanceToTriggerSync(int distance) { 542 | mTotalDragDistance = distance; 543 | } 544 | 545 | @Override 546 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 547 | final int width = getMeasuredWidth(); 548 | final int height = getMeasuredHeight(); 549 | if (getChildCount() == 0) { 550 | return; 551 | } 552 | if (mTarget == null) { 553 | ensureTarget(); 554 | } 555 | if (mTarget == null) { 556 | return; 557 | } 558 | final View child = mTarget; 559 | final int childLeft = getPaddingLeft(); 560 | final int childTop = getPaddingTop(); 561 | final int childWidth = width - getPaddingLeft() - getPaddingRight(); 562 | final int childHeight = height - getPaddingTop() - getPaddingBottom(); 563 | child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); 564 | int circleWidth = mCircleView.getMeasuredWidth(); 565 | int circleHeight = mCircleView.getMeasuredHeight(); 566 | mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop, 567 | (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight); 568 | } 569 | 570 | @Override 571 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 572 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 573 | if (mTarget == null) { 574 | ensureTarget(); 575 | } 576 | if (mTarget == null) { 577 | return; 578 | } 579 | mTarget.measure(MeasureSpec.makeMeasureSpec( 580 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 581 | MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( 582 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); 583 | mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY), 584 | MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY)); 585 | if (!mUsingCustomStart && !mOriginalOffsetCalculated) { 586 | mOriginalOffsetCalculated = true; 587 | 588 | switch (mDirection) { 589 | case BOTTOM: 590 | mCurrentTargetOffsetTop = mOriginalOffsetTop = getMeasuredHeight() - mCircleView.getMeasuredHeight(); 591 | break; 592 | case TOP: 593 | default: 594 | mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); 595 | break; 596 | } 597 | } 598 | mCircleViewIndex = -1; 599 | // Get the index of the circleview. 600 | for (int index = 0; index < getChildCount(); index++) { 601 | if (getChildAt(index) == mCircleView) { 602 | mCircleViewIndex = index; 603 | break; 604 | } 605 | } 606 | } 607 | 608 | /** 609 | * @return Whether it is possible for the child view of this layout to 610 | * scroll up. Override this if the child view is a custom view. 611 | */ 612 | public boolean canChildScrollUp() { 613 | if (android.os.Build.VERSION.SDK_INT < 14) { 614 | if (mTarget instanceof AbsListView) { 615 | final AbsListView absListView = (AbsListView) mTarget; 616 | return absListView.getChildCount() > 0 617 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 618 | .getTop() < absListView.getPaddingTop()); 619 | } else { 620 | return mTarget.getScrollY() > 0; 621 | } 622 | } else { 623 | return ViewCompat.canScrollVertically(mTarget, -1); 624 | } 625 | } 626 | // public boolean canChildScrollUp() { 627 | // if (android.os.Build.VERSION.SDK_INT < 14) { 628 | // if (mTarget instanceof AbsListView) { 629 | // final AbsListView absListView = (AbsListView) mTarget; 630 | // if (absListView.getLastVisiblePosition() + 1 == absListView.getCount()) { 631 | // int lastIndex = absListView.getLastVisiblePosition() - absListView.getFirstVisiblePosition(); 632 | // 633 | // boolean res = absListView.getChildAt(lastIndex).getBottom() == absListView.getPaddingBottom(); 634 | // 635 | // return res; 636 | // } 637 | // return true; 638 | // } else { 639 | // return mTarget.getScrollY() > 0; 640 | // } 641 | // } else { 642 | // return ViewCompat.canScrollVertically(mTarget, 1); 643 | // } 644 | // } 645 | 646 | 647 | public boolean canChildScrollDown() { 648 | if (android.os.Build.VERSION.SDK_INT < 14) { 649 | if (mTarget instanceof AbsListView) { 650 | final AbsListView absListView = (AbsListView) mTarget; 651 | try { 652 | if (absListView.getCount() > 0) { 653 | if (absListView.getLastVisiblePosition() + 1 == absListView.getCount()) { 654 | int lastIndex = absListView.getLastVisiblePosition() - absListView.getFirstVisiblePosition(); 655 | return absListView.getChildAt(lastIndex).getBottom() == absListView.getPaddingBottom(); 656 | } 657 | } 658 | } catch (Exception e) { 659 | e.printStackTrace(); 660 | } 661 | return true; 662 | } else { 663 | return true; 664 | } 665 | } else { 666 | return ViewCompat.canScrollVertically(mTarget, 1); 667 | } 668 | } 669 | 670 | @Override 671 | public boolean onInterceptTouchEvent(MotionEvent ev) { 672 | ensureTarget(); 673 | 674 | final int action = MotionEventCompat.getActionMasked(ev); 675 | 676 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 677 | mReturningToStart = false; 678 | } 679 | 680 | switch (mDirection) { 681 | case BOTTOM: 682 | if (!isEnabled() || mReturningToStart || (!mBothDirection && canChildScrollDown()) || mRefreshing) { 683 | // Fail fast if we're not in a state where a swipe is possible 684 | return false; 685 | } 686 | break; 687 | case TOP: 688 | default: 689 | if (!isEnabled() || mReturningToStart || (!mBothDirection && canChildScrollUp()) || mRefreshing) { 690 | // Fail fast if we're not in a state where a swipe is possible 691 | return false; 692 | } 693 | break; 694 | } 695 | 696 | switch (action) { 697 | case MotionEvent.ACTION_DOWN: 698 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true); 699 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 700 | mIsBeingDragged = false; 701 | final float initialMotionY = getMotionEventY(ev, mActivePointerId); 702 | if (initialMotionY == -1) { 703 | return false; 704 | } 705 | mInitialMotionY = initialMotionY; 706 | 707 | case MotionEvent.ACTION_MOVE: 708 | if (mActivePointerId == INVALID_POINTER) { 709 | return false; 710 | } 711 | 712 | final float y = getMotionEventY(ev, mActivePointerId); 713 | if (y == -1) { 714 | return false; 715 | } 716 | if (mBothDirection) { 717 | if (y > mInitialMotionY) { 718 | setRawDirection(SwipeRefreshLayoutDirection.TOP); 719 | } else if (y < mInitialMotionY) { 720 | setRawDirection(SwipeRefreshLayoutDirection.BOTTOM); 721 | } 722 | if ((mDirection == SwipeRefreshLayoutDirection.BOTTOM && canChildScrollDown()) 723 | || (mDirection == SwipeRefreshLayoutDirection.TOP && canChildScrollUp())) { 724 | return false; 725 | } 726 | } 727 | float yDiff; 728 | switch (mDirection) { 729 | case BOTTOM: 730 | yDiff = mInitialMotionY - y; 731 | break; 732 | case TOP: 733 | default: 734 | yDiff = y - mInitialMotionY; 735 | break; 736 | } 737 | if (yDiff > mTouchSlop && !mIsBeingDragged) { 738 | mIsBeingDragged = true; 739 | mProgress.setAlpha(STARTING_PROGRESS_ALPHA); 740 | } 741 | break; 742 | 743 | case MotionEventCompat.ACTION_POINTER_UP: 744 | onSecondaryPointerUp(ev); 745 | break; 746 | 747 | case MotionEvent.ACTION_UP: 748 | case MotionEvent.ACTION_CANCEL: 749 | mIsBeingDragged = false; 750 | mActivePointerId = INVALID_POINTER; 751 | break; 752 | } 753 | 754 | return mIsBeingDragged; 755 | } 756 | 757 | private float getMotionEventY(MotionEvent ev, int activePointerId) { 758 | final int index = MotionEventCompat.findPointerIndex(ev, activePointerId); 759 | if (index < 0) { 760 | return -1; 761 | } 762 | return MotionEventCompat.getY(ev, index); 763 | } 764 | 765 | @Override 766 | public void requestDisallowInterceptTouchEvent(boolean b) { 767 | // Nope. 768 | } 769 | 770 | private boolean isAnimationRunning(Animation animation) { 771 | return animation != null && animation.hasStarted() && !animation.hasEnded(); 772 | } 773 | 774 | @Override 775 | public boolean onTouchEvent(MotionEvent ev) { 776 | final int action = MotionEventCompat.getActionMasked(ev); 777 | 778 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 779 | mReturningToStart = false; 780 | } 781 | 782 | switch (mDirection) { 783 | case BOTTOM: 784 | if (!isEnabled() || mReturningToStart || canChildScrollDown() || mRefreshing) { 785 | // Fail fast if we're not in a state where a swipe is possible 786 | return false; 787 | } 788 | break; 789 | case TOP: 790 | default: 791 | if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) { 792 | // Fail fast if we're not in a state where a swipe is possible 793 | return false; 794 | } 795 | break; 796 | } 797 | 798 | switch (action) { 799 | case MotionEvent.ACTION_DOWN: 800 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 801 | mIsBeingDragged = false; 802 | break; 803 | 804 | case MotionEvent.ACTION_MOVE: { 805 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 806 | if (pointerIndex < 0) { 807 | return false; 808 | } 809 | 810 | final float y = MotionEventCompat.getY(ev, pointerIndex); 811 | 812 | float overscrollTop; 813 | switch (mDirection) { 814 | case BOTTOM: 815 | overscrollTop = (mInitialMotionY - y) * DRAG_RATE; 816 | break; 817 | case TOP: 818 | default: 819 | overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 820 | break; 821 | } 822 | if (mIsBeingDragged) { 823 | mProgress.showArrow(true); 824 | float originalDragPercent = overscrollTop / mTotalDragDistance; 825 | if (originalDragPercent < 0) { 826 | return false; 827 | } 828 | float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); 829 | float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; 830 | float extraOS = Math.abs(overscrollTop) - mTotalDragDistance; 831 | float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset 832 | - mOriginalOffsetTop : mSpinnerFinalOffset; 833 | float tensionSlingshotPercent = Math.max(0, 834 | Math.min(extraOS, slingshotDist * 2) / slingshotDist); 835 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( 836 | (tensionSlingshotPercent / 4), 2)) * 2f; 837 | float extraMove = (slingshotDist) * tensionPercent * 2; 838 | 839 | // int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove); 840 | int targetY; 841 | if (mDirection == SwipeRefreshLayoutDirection.TOP) { 842 | targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove); 843 | } else { 844 | targetY = mOriginalOffsetTop - (int) ((slingshotDist * dragPercent) + extraMove); 845 | } 846 | // where 1.0f is a full circle 847 | if (mCircleView.getVisibility() != View.VISIBLE) { 848 | mCircleView.setVisibility(View.VISIBLE); 849 | } 850 | if (!mScale) { 851 | ViewCompat.setScaleX(mCircleView, 1f); 852 | ViewCompat.setScaleY(mCircleView, 1f); 853 | } 854 | if (overscrollTop < mTotalDragDistance) { 855 | if (mScale) { 856 | setAnimationProgress(overscrollTop / mTotalDragDistance); 857 | } 858 | if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA 859 | && !isAnimationRunning(mAlphaStartAnimation)) { 860 | // Animate the alpha 861 | startProgressAlphaStartAnimation(); 862 | } 863 | float strokeStart = (float) (adjustedPercent * .8f); 864 | mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); 865 | mProgress.setArrowScale(Math.min(1f, adjustedPercent)); 866 | } else { 867 | if (mProgress.getAlpha() < MAX_ALPHA 868 | && !isAnimationRunning(mAlphaMaxAnimation)) { 869 | // Animate the alpha 870 | startProgressAlphaMaxAnimation(); 871 | } 872 | } 873 | float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; 874 | mProgress.setProgressRotation(rotation); 875 | setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, 876 | true /* requires update */); 877 | } 878 | break; 879 | } 880 | case MotionEventCompat.ACTION_POINTER_DOWN: { 881 | final int index = MotionEventCompat.getActionIndex(ev); 882 | mActivePointerId = MotionEventCompat.getPointerId(ev, index); 883 | break; 884 | } 885 | 886 | case MotionEventCompat.ACTION_POINTER_UP: 887 | onSecondaryPointerUp(ev); 888 | break; 889 | 890 | case MotionEvent.ACTION_UP: 891 | case MotionEvent.ACTION_CANCEL: { 892 | if (mActivePointerId == INVALID_POINTER) { 893 | if (action == MotionEvent.ACTION_UP) { 894 | } 895 | return false; 896 | } 897 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 898 | final float y = MotionEventCompat.getY(ev, pointerIndex); 899 | 900 | float overscrollTop; 901 | switch (mDirection) { 902 | case BOTTOM: 903 | overscrollTop = (mInitialMotionY - y) * DRAG_RATE; 904 | break; 905 | case TOP: 906 | default: 907 | overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 908 | break; 909 | } 910 | mIsBeingDragged = false; 911 | if (overscrollTop > mTotalDragDistance) { 912 | setRefreshing(true, true /* notify */); 913 | } else { 914 | // cancel refresh 915 | mRefreshing = false; 916 | mProgress.setStartEndTrim(0f, 0f); 917 | AnimationListener listener = null; 918 | if (!mScale) { 919 | listener = new AnimationListener() { 920 | 921 | @Override 922 | public void onAnimationStart(Animation animation) { 923 | } 924 | 925 | @Override 926 | public void onAnimationEnd(Animation animation) { 927 | if (!mScale) { 928 | startScaleDownAnimation(null); 929 | } 930 | } 931 | 932 | @Override 933 | public void onAnimationRepeat(Animation animation) { 934 | } 935 | 936 | }; 937 | } 938 | animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener); 939 | mProgress.showArrow(false); 940 | } 941 | mActivePointerId = INVALID_POINTER; 942 | return false; 943 | } 944 | } 945 | 946 | return true; 947 | } 948 | 949 | private void animateOffsetToCorrectPosition(int from, AnimationListener listener) { 950 | mFrom = from; 951 | mAnimateToCorrectPosition.reset(); 952 | mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION); 953 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator); 954 | if (listener != null) { 955 | mCircleView.setAnimationListener(listener); 956 | } 957 | mCircleView.clearAnimation(); 958 | mCircleView.startAnimation(mAnimateToCorrectPosition); 959 | } 960 | 961 | private void animateOffsetToStartPosition(int from, AnimationListener listener) { 962 | if (mScale) { 963 | // Scale the item back down 964 | startScaleDownReturnToStartAnimation(from, listener); 965 | } else { 966 | mFrom = from; 967 | mAnimateToStartPosition.reset(); 968 | mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION); 969 | mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); 970 | if (listener != null) { 971 | mCircleView.setAnimationListener(listener); 972 | } 973 | mCircleView.clearAnimation(); 974 | mCircleView.startAnimation(mAnimateToStartPosition); 975 | } 976 | } 977 | 978 | private final Animation mAnimateToCorrectPosition = new Animation() { 979 | @Override 980 | public void applyTransformation(float interpolatedTime, Transformation t) { 981 | int targetTop = 0; 982 | int endTarget = 0; 983 | if (!mUsingCustomStart) { 984 | switch (mDirection) { 985 | case BOTTOM: 986 | endTarget = getMeasuredHeight() - (int) (mSpinnerFinalOffset); 987 | break; 988 | case TOP: 989 | default: 990 | endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop)); 991 | break; 992 | } 993 | } else { 994 | endTarget = (int) mSpinnerFinalOffset; 995 | } 996 | targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime)); 997 | int offset = targetTop - mCircleView.getTop(); 998 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 999 | } 1000 | }; 1001 | 1002 | private void moveToStart(float interpolatedTime) { 1003 | int targetTop = 0; 1004 | targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime)); 1005 | int offset = targetTop - mCircleView.getTop(); 1006 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 1007 | } 1008 | 1009 | private final Animation mAnimateToStartPosition = new Animation() { 1010 | @Override 1011 | public void applyTransformation(float interpolatedTime, Transformation t) { 1012 | moveToStart(interpolatedTime); 1013 | } 1014 | }; 1015 | 1016 | private void startScaleDownReturnToStartAnimation(int from, 1017 | AnimationListener listener) { 1018 | mFrom = from; 1019 | if (isAlphaUsedForScale()) { 1020 | mStartingScale = mProgress.getAlpha(); 1021 | } else { 1022 | mStartingScale = ViewCompat.getScaleX(mCircleView); 1023 | } 1024 | mScaleDownToStartAnimation = new Animation() { 1025 | @Override 1026 | public void applyTransformation(float interpolatedTime, Transformation t) { 1027 | float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime)); 1028 | setAnimationProgress(targetScale); 1029 | moveToStart(interpolatedTime); 1030 | } 1031 | }; 1032 | mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION); 1033 | if (listener != null) { 1034 | mCircleView.setAnimationListener(listener); 1035 | } 1036 | mCircleView.clearAnimation(); 1037 | mCircleView.startAnimation(mScaleDownToStartAnimation); 1038 | } 1039 | 1040 | private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) { 1041 | mCircleView.bringToFront(); 1042 | mCircleView.offsetTopAndBottom(offset); 1043 | mCurrentTargetOffsetTop = mCircleView.getTop(); 1044 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { 1045 | invalidate(); 1046 | } 1047 | } 1048 | 1049 | private void onSecondaryPointerUp(MotionEvent ev) { 1050 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 1051 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 1052 | if (pointerId == mActivePointerId) { 1053 | // This was our active pointer going up. Choose a new 1054 | // active pointer and adjust accordingly. 1055 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1056 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 1057 | } 1058 | } 1059 | 1060 | /** 1061 | * Classes that wish to be notified when the swipe gesture correctly 1062 | * triggers a refresh should implement this interface. 1063 | */ 1064 | public interface OnRefreshListener { 1065 | public void onRefresh(SwipeRefreshLayoutDirection direction); 1066 | } 1067 | 1068 | public SwipeRefreshLayoutDirection getDirection() { 1069 | return mBothDirection ? SwipeRefreshLayoutDirection.BOTH : mDirection; 1070 | } 1071 | 1072 | public void setDirection(SwipeRefreshLayoutDirection direction) { 1073 | if (direction == SwipeRefreshLayoutDirection.BOTH) { 1074 | mBothDirection = true; 1075 | } else { 1076 | mBothDirection = false; 1077 | mDirection = direction; 1078 | } 1079 | 1080 | switch (mDirection) { 1081 | case BOTTOM: 1082 | mCurrentTargetOffsetTop = mOriginalOffsetTop = getMeasuredHeight() - mCircleView.getMeasuredHeight(); 1083 | break; 1084 | case TOP: 1085 | default: 1086 | mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); 1087 | break; 1088 | } 1089 | } 1090 | 1091 | // only TOP or Bottom 1092 | private void setRawDirection(SwipeRefreshLayoutDirection direction) { 1093 | if (mDirection == direction) { 1094 | return; 1095 | } 1096 | 1097 | mDirection = direction; 1098 | switch (mDirection) { 1099 | case BOTTOM: 1100 | mCurrentTargetOffsetTop = mOriginalOffsetTop = getMeasuredHeight() - mCircleView.getMeasuredHeight(); 1101 | break; 1102 | case TOP: 1103 | default: 1104 | mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight(); 1105 | break; 1106 | } 1107 | } 1108 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/refresh/SpaceItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.refresh; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | /** 7 | * Created by KomoriWu 8 | * on 2017-03-25. 9 | */ 10 | 11 | public class SpaceItemDecoration extends RecyclerView.ItemDecoration{ 12 | 13 | private int space; 14 | 15 | public SpaceItemDecoration(int space) { 16 | this.space = space; 17 | } 18 | 19 | @Override 20 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 21 | RecyclerView.State state) { 22 | 23 | if(parent.getChildPosition(view) != 0) 24 | outRect.top = space; 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/refresh/SwipeRefreshLayoutDirection.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.refresh; 2 | 3 | /** 4 | * Created by oliviergoutay on 1/23/15. 5 | */ 6 | public enum SwipeRefreshLayoutDirection { 7 | 8 | TOP(0), 9 | BOTTOM(1), 10 | BOTH(2); 11 | 12 | private int mValue; 13 | 14 | SwipeRefreshLayoutDirection(int value) { 15 | this.mValue = value; 16 | } 17 | 18 | public static SwipeRefreshLayoutDirection getFromInt(int value) { 19 | for (SwipeRefreshLayoutDirection direction : SwipeRefreshLayoutDirection.values()) { 20 | if (direction.mValue == value) { 21 | return direction; 22 | } 23 | } 24 | return BOTH; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/rxjava_retrofit_mvp_md/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.example.rxjava_retrofit_mvp_md.utils; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.res.TypedArray; 8 | import android.support.design.widget.Snackbar; 9 | import android.view.View; 10 | 11 | import com.example.rxjava_retrofit_mvp_md.R; 12 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 13 | import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer; 14 | 15 | /** 16 | * Created by KomoriWu 17 | * on 2017-03-24. 18 | */ 19 | 20 | public class Utils { 21 | public static final String URL = "http://120.24.218.251:8080/geek_wz/"; 22 | public static final String GET_ALL_ARTICLES = "Make_ArticleJson"; 23 | 24 | 25 | public static int getToolbarHeight(Context context) { 26 | final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes( 27 | new int[]{R.attr.actionBarSize}); 28 | int toolbarHeight = (int) styledAttributes.getDimension(0, 0); 29 | styledAttributes.recycle(); 30 | 31 | return toolbarHeight; 32 | } 33 | 34 | public static DisplayImageOptions getImageOptions() { 35 | return getImageOptions(R.mipmap.jiazaishibai, 0); 36 | } 37 | 38 | public static DisplayImageOptions getImageOptions(boolean isCircular) { 39 | return getImageOptions(R.mipmap.jiazaishibai, 180); 40 | } 41 | 42 | public static DisplayImageOptions getImageOptions(int defaultIconId) { 43 | return getImageOptions(defaultIconId, 0); 44 | } 45 | 46 | public static DisplayImageOptions getImageOptions(int defaultIconId, int cornerRadiusPixels) { 47 | return new DisplayImageOptions.Builder() 48 | .displayer(new RoundedBitmapDisplayer(cornerRadiusPixels)) 49 | .showImageOnLoading(defaultIconId) 50 | .showImageOnFail(defaultIconId) 51 | .showImageForEmptyUri(defaultIconId) 52 | .cacheInMemory(true) 53 | .cacheOnDisc() 54 | .build(); 55 | } 56 | 57 | public static void showSnackBar(View view, String str) { 58 | Snackbar.make(view, str, Snackbar.LENGTH_SHORT).setAction(R.string.kown, 59 | new View.OnClickListener() { 60 | @Override 61 | public void onClick(View v) { 62 | 63 | } 64 | }).show(); 65 | } 66 | public static void showAlertDialog(Context context, String message) { 67 | if (!((Activity) context).isFinishing()) { 68 | AlertDialog dialog = new AlertDialog.Builder(context) 69 | .setMessage(message) 70 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 71 | @Override 72 | public void onClick(DialogInterface dialog, int which) { 73 | dialog.dismiss(); 74 | } 75 | }) 76 | .create(); 77 | dialog.setCancelable(true); 78 | dialog.show(); 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_article.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/head_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_article.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 19 | 20 | 25 | 26 | 32 | 33 | 34 | 41 | 42 | 52 | 53 | 61 | 62 | 73 | 74 | 75 | 83 | 84 | 95 | 96 | 110 | 111 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 21 | 22 | 32 | 33 | 34 | 39 | 40 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/fab_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/fab_editor.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/guanyu_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/guanyu_4.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/jiazaishibai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/jiazaishibai.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/liulan_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/liulan_5.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/loading.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/loading.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/shequ_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/shequ_4.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/shouye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/shouye.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/xihuan_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/xihuan_4.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/yijianfankui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xhdpi/yijianfankui.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KomoriWu/RxJava-Retrofit-MVP-MD/ce7514aea420bd7be9abe9e137e595be7e50f82d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #607D8B 4 | #455A64 5 | #009688 6 | #CFD8DC 7 | #8a8a8a 8 | #000000 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16sp 6 | 14sp 7 | 48dp 8 | 32dp 9 | 70dp 10 | 3dp 11 | 16dp 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RxJava-Retrofit-MVP-MD 3 | KomoriWu 4 | 首页 5 | 微社区 6 | 说明 7 | 关于我们 8 | "我的" 9 | "推歌" 10 | "碎念" 11 | Open navigation drawer 12 | Close navigation drawer 13 | 知道了 14 | 最后一页到了! 15 | 确定 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 21 | 22 |