WanAndroid实战——刷新加载

作者: Tom_Ji | 来源:发表于2019-04-04 15:46 被阅读11次

前情回顾:

1.WanAndroid实战——首页Banner

2.WanAndroid实战——首页文章

3.WanAndroid实战——内容显示

在完成了上面的一系列操作之后终于可以看到文章的内容了,但是却只能看到“最近”的内容,无法刷新,也无法看到之前的内容,这篇文章将解决这个问题。按照惯例,先上效果图:

完成效果.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作为根布局,结合AppBarLayoutCollapsingToolbarLayout来实现预期效果。


<?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>

全部修改完成后,运行程序,满足要求,剩下的内容后面慢慢加。

相关文章

网友评论

    本文标题:WanAndroid实战——刷新加载

    本文链接:https://www.haomeiwen.com/subject/vmqqiqtx.html