前情回顾:
在完成了上面的一系列操作之后终于可以看到文章的内容了,但是却只能看到“最近”的内容,无法刷新,也无法看到之前的内容,这篇文章将解决这个问题。按照惯例,先上效果图:
完成效果.gif下拉刷新,上拉加载
目前周围的同事在做有关刷新加载的功能都推荐使用开源框架SmartRefreshLayout,实现起来简单,效果也是很炫酷,我这里使用的是经典的形式。
1.添加依赖,在gradle里面添加如下内容
implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-21'
implementation 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.0-alpha-21'
2.在布局文件中添加进去,因为我们要刷新的是recycleView,所以在它外面加。
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.scwang.smartrefresh.layout.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/article_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<com.scwang.smartrefresh.layout.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
ClassicsHeader和ClassicsFooter是经典的头部和尾部,如果想使用默认的效果,那么这两个可以不写。
3.接口中添加加载,刷新的相关内容
在Contract.java
这个类中,添加IBaseView
,这里面用来定义错误和完成的接口,同时让IMainView
继承IBaseView
,通过监听这两种状态,来完成相关的操作,例如隐藏刷新/加载的Header和Footer。新增了刷新的接口。
package com.tom.wanandroid.contract;
import com.tom.wanandroid.bean.ArticleBean;
import com.tom.wanandroid.bean.BannerBean;
import java.util.List;
import io.reactivex.Observable;
/**
* <p>Title: Contract</p>
* <p>Description: </p>
*
* @author tom
* @date 2019/3/7 10:13
**/
public class Contract {
public interface IBaseView {
/**
* <p>加载错误回调</p>
* @param e Throwable
*/
void onError(Throwable e);
/**
* <p>加载完成</p>
*/
void onComplete();
}
public interface IMainModel {
/**
* <p>获取banner数据</p>
* @return banner数据
*/
Observable<BannerBean> loadBanner();
/**
* <p>加载首页文章</p>
* @param number 页码
* @return 首页文章
*/
Observable<List<ArticleBean>> loadArticle(int number);
/**
* <p>刷新首页文章</p>
* @return 首页文章
*/
Observable<List<ArticleBean>> refreshArticle();
}
public interface IMainView extends IBaseView{
/**
* <p>View 获取到数据后进行显示</p>
* @param bean banner的数据
*/
void loadBanner(BannerBean bean);
/**
* <p>加载首页文章信息</p>
* @param bean 首页文章数据
*/
void loadArticle(List<ArticleBean> bean);
/**
* <p>刷新首页文章</p>
*
* @param bean
*/
void refreshArticle(List<ArticleBean> bean);
}
public interface IMainPresenter{
/**
* <p>首页banner</p>
*/
void loadBanner();
/**
* <p>加载首页文章</p>
* @param number 页码
*/
void loadArticle(int number);
/**
* <p>刷新首页文章</p>
*/
void refreshArticle();
}
}
4.处理报错
因为接口新增,所有实现这些接口的类都会报错,接下来就是处理各种报错就好了,这里以Model为例,其它的都比较简单。
首先将从网站获取的json数据对应的bean更名为ArticleBeanOriginal
,然后新建了一个ArticleBean
,这里仅保存用到的属性,后续如果有需要添加的地方,可以直接进行修改。
package com.tom.wanandroid.bean;
/**
* <p>Title: ArticleBean</p>
* <p>Description:处理后的文章bean </p>
*
* @author tom
* @date 2019/3/29 13:39
**/
public class ArticleBean {
public int id;
public String title;
public String author;
public String niceDate;
public long publishTime;
public String chapterName;
public String superChapterName;
public boolean collect;
public String link;
@Override
public String toString() {
return "ArticleBean{" +
"id=" + id +
", title='" + title + '\'' +
", author='" + author + '\'' +
", niceDate='" + niceDate + '\'' +
", publishTime=" + publishTime +
", chapterName='" + chapterName + '\'' +
", superChapterName='" + superChapterName + '\'' +
", collect=" + collect +
", link='" + link + '\'' +
'}';
}
}
然后在IRetrofitData.java
中增加刷新的方法,刷新固定获取第0页的数据,因此不需要传递参数。添加后的文件内容如下:
package com.tom.wanandroid.retrofit;
import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.bean.ArticleBeanOriginal;
import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Path;
/**
* <p>Title: IRetrofitData</p>
* <p>Description: </p>
*
* @author tom
* @date 2019/3/7 11:34
**/
public interface IRetrofitData {
/**
* <p>获取首页轮播图数据</p>
* @return 首页轮播图数据
*/
@GET("banner/json")
Observable<BannerBean> loadBanner();
/**
* <p>获取首页文章数据</p>
* @param number 页码
* @return 返回首页文章
*/
@GET("article/list/{number}/json")
Observable<ArticleBeanOriginal> loadMainArticle(@Path("number") int number);
/**
* <p>刷新首页文章数据</p>
* @return 返回首页文章
*/
@GET("article/list/0/json")
Observable<ArticleBeanOriginal> refreshMainArticle();
}
接着修改MainModel
,刷新时固定获取第0页数据,如果数据相同,则不处理,如果不相同,则放置到已有数据的上面,因此需要对获取到的数据进行倒叙排序,即让获取到的数据为按照发表时间倒叙排序,这样,在调用了mArticleBeans.add(0, articleBean);
方法后,能够保证文章的数据的顺序是正确的。修改后的文件如下。
package com.tom.wanandroid.model;
import com.tom.wanandroid.bean.ArticleBean;
import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;
import com.tom.wanandroid.retrofit.IRetrofitData;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Observable;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* <p>Title: MainModel</p>
* <p>Description: </p>
*
* @author tom
* @date 2019/3/7 10:54
**/
public class MainModel implements Contract.IMainModel {
private static final String BASE_URL = "http://www.wanandroid.com";
List<ArticleBean> mArticleBeans = new ArrayList<>();
@Override
public Observable<BannerBean> loadBanner() {
IRetrofitData retrofitData = getIRetrofitData();
return retrofitData.loadBanner();
}
@Override
public Observable<List<ArticleBean>> loadArticle(int number) {
return getIRetrofitData().loadMainArticle(number).filter(a ->a.getErrorCode() == 0)
.map(original -> {
original.getData().getDatas().stream().sorted((o1,o2) -> (int)(o2.getPublishTime() - o1.getPublishTime()))
.forEach(datas -> {
long count = mArticleBeans.stream().filter(b -> b.id == datas.getId()).count();
if (count <= 0) {
ArticleBean articleBean = new ArticleBean();
articleBean.id = datas.getId();
articleBean.title = datas.getTitle();
articleBean.author = datas.getAuthor();
articleBean.niceDate = datas.getNiceDate();
articleBean.publishTime = datas.getPublishTime();
articleBean.chapterName = datas.getChapterName();
articleBean.superChapterName = datas.getSuperChapterName();
articleBean.collect = datas.isCollect();
articleBean.link = datas.getLink();
mArticleBeans.add(articleBean);
}
});
return mArticleBeans;
});
}
@Override
public Observable<List<ArticleBean>> refreshArticle() {
return getIRetrofitData().refreshMainArticle().filter(a ->a.getErrorCode() == 0)
.map(original -> {
original.getData().getDatas().stream().sorted((o1, o2) -> (int) (o1.getPublishTime() - o2.getPublishTime()))
.forEach(datas -> {
//如果数据是新数据
long count = mArticleBeans.stream().filter(b -> b.id == datas.getId()).count();
if (count <= 0) {
ArticleBean articleBean = new ArticleBean();
articleBean.id = datas.getId();
articleBean.title = datas.getTitle();
articleBean.author = datas.getAuthor();
articleBean.niceDate = datas.getNiceDate();
articleBean.publishTime = datas.getPublishTime();
articleBean.chapterName = datas.getChapterName();
articleBean.superChapterName = datas.getSuperChapterName();
articleBean.collect = datas.isCollect();
articleBean.link = datas.getLink();
mArticleBeans.add(0, articleBean);
}
});
return mArticleBeans;
});
}
private IRetrofitData getIRetrofitData() {
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return retrofit.create(IRetrofitData.class);
}
}
顺带一句,在MainPresenter
中,refreshArticle()
方法如下,loadArticle
同样修改即可。
@Override
public void refreshArticle() {
mModel.refreshArticle()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<ArticleBean>>() {
@Override
public void onSubscribe(Disposable d) {
mCompositeDisposable.add(d);
}
@Override
public void onNext(List<ArticleBean> articleBeans) {
if (isViewAttached()) {
getView().refreshArticle(articleBeans);
}
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
if (isViewAttached()) {
getView().onError(e);
}
}
@Override
public void onComplete() {
if (isViewAttached()) {
getView().onComplete();
}
}
});
}
5.在View中调用刷新和加载方法
在View中,设置加载和刷新的监听,分别实现监听的方法。
mRefreshLayout.setOnLoadMoreListener(this);
mRefreshLayout.setOnRefreshListener(this);
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
mCurPage++;
mPresenter.loadArticle(mCurPage);
}
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
mCurPage = 0;
mPresenter.refreshArticle();
}
还要考虑加载或者刷新失败时,需要将头部和尾部隐藏掉,整个MainActivity
的代码如下。
package com.tom.wanandroid.view;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.ToastUtils;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.tom.wanandroid.Constant;
import com.tom.wanandroid.R;
import com.tom.wanandroid.adapter.ArticleAdapter;
import com.tom.wanandroid.base.BaseActivity;
import com.tom.wanandroid.bean.ArticleBean;
import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;
import com.tom.wanandroid.presenter.MainPresenter;
import com.tom.wanandroid.utils.GlideImageLoader;
import com.tom.wanandroid.utils.Utils;
import com.youth.banner.Banner;
import com.youth.banner.BannerConfig;
import com.youth.banner.listener.OnBannerListener;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import butterknife.BindView;
/**
* <p>Title: MainActivity</p>
* <p>Description: 主页activity</p>
*
* @author tom
* @date 2019/3/9 10:15
*/
public class MainActivity extends BaseActivity<Contract.IMainView, MainPresenter> implements Contract.IMainView,
OnBannerListener, ArticleAdapter.OnItemClickListener,
com.scwang.smartrefresh.layout.listener.OnLoadMoreListener,
com.scwang.smartrefresh.layout.listener.OnRefreshListener {
@BindView(R.id.main_banner)
Banner mMainBanner;
@BindView(R.id.article_content)
RecyclerView mArticleContent;
@BindView(R.id.refresh_layout)
SmartRefreshLayout mRefreshLayout;
ArticleAdapter mArticleAdapter;
List<ArticleBean> mArticleDatas = new ArrayList<>();
List<String> mBannerUrls;
List<String> mTitles;
/**
* 当前页
*/
int mCurPage = 0;
/**
* 总页数,默认只有一页
*/
int mPageCount = 1;
@Override
protected int getContentViewId() {
return R.layout.activity_main;
}
@Override
protected void init(Bundle savedInstanceState) {
initBanner();
initArticleRecycle();
initListener();
initData();
}
/**
* <p>获取数据</p>
*/
private void initData() {
mPresenter.loadBanner();
mPresenter.refreshArticle();
}
/**
* <p>初始化listener</p>
*/
private void initListener() {
//设置监听
mMainBanner.setOnBannerListener(this);
mArticleAdapter.setListener(this);
mRefreshLayout.setOnLoadMoreListener(this);
mRefreshLayout.setOnRefreshListener(this);
}
/**
* <p>初始化文章adapter</p>
*/
private void initArticleRecycle() {
//设置adapter
mArticleContent.setLayoutManager(new LinearLayoutManager(this));
mArticleAdapter = new ArticleAdapter(mArticleContent, mArticleDatas);
mArticleContent.setAdapter(mArticleAdapter);
}
/**
* <p>初始化Banner</p>
*/
private void initBanner() {
mMainBanner.setBannerStyle(BannerConfig.CIRCLE_INDICATOR_TITLE_INSIDE);
mMainBanner.setImageLoader(new GlideImageLoader());
mMainBanner.setImages(new ArrayList<String>());
mMainBanner.setBannerTitles(new ArrayList<>());
mMainBanner.start();
}
@Override
protected MainPresenter createPresenter() {
return new MainPresenter();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
public void loadBanner(BannerBean bean) {
if (bean.getErrorCode() == Constant.BANNER_SUCCESS) {
//获取图片路径
List<String> images = bean.getData()
.stream()
.map(BannerBean.DataBean::getImagePath)
.collect(Collectors.toList());
mMainBanner.setImages(images);
//获取URL
mBannerUrls = bean.getData()
.stream()
.map(BannerBean.DataBean::getUrl)
.collect(Collectors.toList());
//获取title
mTitles = bean.getData()
.stream()
.map(BannerBean.DataBean::getTitle)
.collect(Collectors.toList());
mMainBanner.setBannerTitles(mTitles);
mMainBanner.start();
}
else {
LogUtils.d("bean 获取失败");
}
}
@Override
public void loadArticle(List<ArticleBean> bean) {
mArticleAdapter.setBeans(bean);
mArticleDatas = bean;
}
@Override
public void refreshArticle(List<ArticleBean> bean) {
mArticleAdapter.setBeans(bean);
mArticleDatas = bean;
}
@Override
public void OnBannerClick(int position) {
if (mBannerUrls != null) {
String url = mBannerUrls.get(position);
String title = mTitles.get(position);
Utils.startWebView(this, title, url);
}
}
@Override
public void onItemClick(View view, int position) {
if (mArticleDatas != null) {
ArticleBean bean = mArticleDatas.get(position);
Utils.startWebView(this, bean.title, bean.link);
}
}
@Override
public void onCollectionClick(View view, int position) {
ToastUtils.setBgColor(getResources().getColor(R.color.colorPrimaryDark, null));
ToastUtils.setMsgColor(getResources().getColor(R.color.white, null));
ToastUtils.showShort(R.string.coming_soon);
}
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
mCurPage++;
mPresenter.loadArticle(mCurPage);
}
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
mCurPage = 0;
mPresenter.refreshArticle();
}
@Override
protected void onPause() {
super.onPause();
mRefreshLayout.finishRefresh();
mRefreshLayout.finishLoadMore();
}
@Override
public void onError(Throwable e) {
//加载
if (mRefreshLayout.getState() == RefreshState.Loading) {
mRefreshLayout.finishLoadMore();
mCurPage--;
}
//刷新
if (mRefreshLayout.getState() == RefreshState.Refreshing) {
mRefreshLayout.finishRefresh();
}
}
@Override
public void onComplete() {
LogUtils.d("mRefreshLayout.getState():" + mRefreshLayout.getState());
//加载
if (mRefreshLayout.getState() == RefreshState.Loading) {
mRefreshLayout.finishLoadMore();
}
//刷新
if (mRefreshLayout.getState() == RefreshState.Refreshing) {
mRefreshLayout.finishRefresh();
}
}
}
上滑隐藏Banner
Banner虽然提供了很好的内容展示形式,但是在上滑的过程中,我希望能够看到更多的文章列表内容,这个时候选择将Banner隐藏是最好的办法,将Banner隐藏的方法有很多,这里选择使用官方的控件来处理,实现起来也是非常简单,只需要在布局文件中进行修改即可。
1.引入依赖库
//design
implementation 'com.android.support:design:28.0.0'
2.修改布局文件
这里使用CoordinatorLayout
作为根布局,结合AppBarLayout
、CollapsingToolbarLayout
来实现预期效果。
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".view.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap">
<com.youth.banner.Banner
android:id="@+id/main_banner"
android:layout_width="match_parent"
android:layout_height="@dimen/banner_height"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.scwang.smartrefresh.layout.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/article_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<com.scwang.smartrefresh.layout.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
</android.support.design.widget.CoordinatorLayout>
全部修改完成后,运行程序,满足要求,剩下的内容后面慢慢加。
网友评论