Diycode开源项目 搭建可以具有下拉刷新和上拉加载的Fragment

1.效果预览

1.1.这个首页就是一个Fragment碎片,本文讲述的就是这个碎片的搭建方式。

  

  下拉会有一个旋转的刷新圈,上拉会刷新数据。

 

1.2.整体结构

  首先底层的是BaseFragment

  然后RefreshRecyclerFragment继承了BaseFragment

  然后SimpleRefreshRecycleFragment继承了RefreshRecyclerFragment

  所以应用类的话只要继承SimpleRefreshRecycleFragment就可以了。


2.最基础的BaseFragment

2.1.首先看一下有哪些成员变量

  

  ViewHolder用作View管理

  Config用作配置状态信息

  Diycode是本项目的一个在线服务器

  DataCache是一个缓冲器

 

2.2.onCreate函数,完成初始化

  

  Config是获取单例。

  Diycode也是获取单例。 

  DataCache是新建一个类。

 

2.3.定义获取布局id的抽象函数

  

  继承者中就要实现这个函数了。

 

2.4.获得本类中的ViewHolder

  

 

2.5.实现onCreateView

  

  新建的ViewHolder其实已经用了一个getLayoutId()方法,这里调用的是本类的抽象函数。

  然后返回的是一个View。

 

2.6.实现抽象函数initViews

  

 

2.7.复写onActivityCreated

  

  实现了间接调用抽象函数。

 

2.8.提示函数toast

  

 

2.9.来一张Fragment的生命周期

  

  所以这里的执行顺序是:

  onCreate==>onCreateView==>onActivityCreate

  onCreate作用:初始化配置和Diycode单例和数据缓存器新建。

  onCreateView作用:得到ViewHolder。

  onActivityCreate作用:实现一个抽象函数initViews,用来给基类来实现。


3.具有下拉刷新和上拉加载的Fragment

3.1.继承方式

  

  名称:RefreshRecyclerFragment<T,Event extends BaseEvent<List<T>>>

  

  一个类中有两个参数,像这样

  

  所以本类构造函数也需要两个参数,一个是T,一个是继承BaseEvent<List<T>>的类

 

3.2.请求状态

  

 

3.3.当前状态

  

 

3.4.分页加载

  

 

3.5.视图

   

  SwipeRefreshLayout==>旋转的加载圈

  RecyclerView==>ListView类型的,列表

 

3.6.状态

  

 

3.7.适配器

  

  HeaderFooterAdapter:带有头部和底部的适配器

  FooterProvider:底部的内容提供器

  

 

3.8.实现BaseFragment的getLayoutId方法

  

 

3.9.fragment_refresh_recycler.xml

  

  实际上是这个东西

  

 

3.10.初始化视图  

  

  在BaseFragment中执行最后面的一个函数。

  如果第一次添加到Footer,就不执行loadMore()。

  loadMore执行了什么呢?

  

  如果不能加载更多,或者当前状态为没有更多数据了。

  确定了请求的数据。

  将请求类型封装成mPostTypes

  然后页码+1

  然后状态确定为正在加载

  最后设置内容提供其的FooterLoading,就是底部的loading的用户提示。

  

  回到initViews中

  

  设置Normal的效果==>--end--

  然后适配器注册Footer,Footer时一个空的Bean类

  

  mFooterProvider刚才上面new的一个,写了一个needLoadMore方法,调用了本类中的loadMore方法。

  

  再次回到initViews中继续

  

  refresh_layout就是那个android系统提供的加载圈SwipeRefreshLayout

  setProgressViewOffset来设置下拉刷新的高度

  setColorSchemeColors设置加载圈的颜色

  

  RecyclerView是一个列表。

  setHasFixedSize的作用就是确保尺寸是通过用户输入从而确保RecyclerView的尺寸是一个常数。

  setAdapter将列表和适配器关联,适配器已经注册了Footer

  setLayoutManager这个对于RecyclerView将会提升很大作用

  

  可以达到很多效果,这里就是RecyclerView强大的地方。

   上面有一个自定义的函数setAdapterRegister

  

  这是一个抽象函数,在initViews中执行了。在基类中也要具体的实现代码。

  这里需要传进去三个参数,一个是上下文,一个是recyclerView,一个是adapter,

  具体的实现方法,会调用adapter注册数据类型的方法,这里只是声明这个方法,耦合性降低了。

 

  再次回到initViews中

  

  如果下拉了,将会执行刷新的方法。

  refersh()方法

  

  这个函数和在新建的FooterProvider中的needLoadMore中的loadMore方法很类似

  区别在于pageIndex这里是固定为0了

  然后这个的状态为刷新,常数定义为3

  然后没有mFooterProvider设置的setFooterLoading了,因为不涉及到。

 

  再次回到initViews中

  

  这个函数是initViews中最后一个函数

  这个函数是一个抽象函数

  

  具体作用是从缓存加载数据。在子类中将实现这个方法。

 

3.11.请求结果的回调

  

  得到请求数据的UUID

  如果是加载更多,就执行onLoadMore(event)

  

  这里判断是否请求得到的数据的长度小于当前请求的长度

  然后执行了一个抽象函数onLoadMore(event,mAdapter)

  

  在回调函数中执行,就是数据得到了,下面要实现如何将数据完美展现出去。

  具体交给子类去实现。

  

  如果请求的数据时刷新

  

  也是首先判断一下,因为加载更多涉及到mFooterProvider

  所以这里设置setFooterNormal(),就是正常的loading

  然后执行了一个抽象函数onLoadMore

  

  传入两个产生event和adapter

  主要解决:如何将数据加入到adapter中。交给子类具体实现。

 

  如果没有得到数据将会执行onError方法

  

  将状态设置为正常。

  然后判断请求类型

  如果为加载更多,设置mFooterProvider设置成==>失败,点击重试。

  设置的点击重试,将原来自增了的pageIndex减回去,然后在执行loadMore

  这里面执行了一个 request函数

  

  这个函数也是自己定义的抽象函数,具体实现交给子类吧。就是传递一个范围区间。

  如果是刷新失败,就将mRefreshLayout取消转圈,将底部设置为正常。

  最后执行onError(event,postType)方法

  

  当加载更多失败和刷新数据失败的时候,用一个toast来提示用户。 

 

3.12.设置是否可以刷新

  

  SwipeRefreshLayout有这个setEnabled功能来设置是否可以刷新

 

3.13.设置是否可以加载更多

  

  仅仅设置成员变量即可。

 

3.14.快速回到顶部

  

  调用了RecyclerView的方法。


4.可以给被人用的刷新加载的Fragment

4.1.首先看一下继承方式

  这个Fragment是最高级的抽象类了。

  它是一个可以给被人用的刷新加载的Fragment。

  

  这里我想说一些这个Event是自己随意起的名字,就是一个继承BaseEvent<List<T>>的一个Object。

  用法:如果一个话题类

  

  继承方法:

  TopicListFragment extends

            SimpleRefreshRecycleFragment<Topic,GetTopicsListEvent>

  GetTopicsListEvent==>extends BaseEvent<List<Topic>>

 

4.2.getRecyclerViewLayoutManager方法来设置快速返回顶部的功能

  

  返回一个自定义类,传入上下文的参数。

  比较复杂,就先不管,理解作用即可。

 

4.3.第二次刷新

  为什么叫做第二次刷新呢?

  因为在RefreshRecycleFragment已经首先执行了onRefresh==>设置状态

  然后再执行一个抽象函数onRefresh,这里的函数就是实现的这个抽象函数

  第二次刷新主要是处理数据的。

  

  刷新的话,要先清空一下数据

  然后将请求得到的数据,填充到适配器,采用addDatas的方式来讲数据加进去

  最后友好地提示一下用户:刷新成功。

 

4.4.第二次加载更多

  同理,在RefreshRecycleFragement已经首先执行了onLoadMore==>设置状态+是否到底了。

  然后再执行一个抽象函数onLoadMore,这里的函数就是实现的这个抽象函数

  第二次刷新主要是处理数据的。

  

  加载跟多的话,调用方法addDatas即可。

  这样就会把新的数据加到adapter中。

  这里就没必要再提示用户了,不然每一页都会有这个烦人的提示了。

 

4.5.第二次发生异常

  同理,在请求数据失败的时候,在RefreshRecycleFragment会执行onError==>设置状态+点击事件。

  然后再执行一个抽象函数onError,这里的函数就是实现的这个抽象函数。

  第二次处理异常主要是为了提示用户的。

  


5.一个案例NewsListFragment继承上面的类

5.1.我们要使用的基类就是最高等级的抽象类SimpleRefreshRecyclerFragment

  

 

5.2.这里的T就是News,News是一个简单的Bean

  

  这个Bean类SDK已经定义好了。

 

5.3.同理这里的Event也是SDK中定义的GetNewsListEvent

  

 

5.4.NewsListFragment的源代码如下

/*
 * Copyright 2017 GcsSloop
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Last modified 2017-04-09 05:15:40
 *
 * GitHub:  https://github.com/GcsSloop
 * Website: http://www.gcssloop.com
 * Weibo:   http://weibo.com/GcsSloop
 */

package com.gcssloop.diycode.fragment;

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.gcssloop.diycode.fragment.base.SimpleRefreshRecyclerFragment;
import com.gcssloop.diycode.fragment.provider.NewsProvider;
import com.gcssloop.diycode_sdk.api.news.bean.New;
import com.gcssloop.diycode_sdk.api.news.event.GetNewsListEvent;
import com.gcssloop.diycode_sdk.log.Logger;
import com.gcssloop.recyclerview.adapter.multitype.HeaderFooterAdapter;

import java.util.List;

/**
 * 首页 news 列表
 */
public class NewsListFragment extends SimpleRefreshRecyclerFragment<New, GetNewsListEvent> {

    private boolean isFirstLaunch = true;

    public static NewsListFragment newInstance() {
        Bundle args = new Bundle();
        NewsListFragment fragment = new NewsListFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override public void initData(HeaderFooterAdapter adapter) {
        // 优先从缓存中获取数据,如果是第一次加载则恢复滚动位置,如果没有缓存则从网络加载
        List<Object> news = mDataCache.getNewsListObj();
        if (null != news && news.size() > 0) {
            Logger.e("news : " + news.size());
            pageIndex = mConfig.getNewsListPageIndex();
            adapter.addDatas(news);
            if (isFirstLaunch) {
                int lastPosition = mConfig.getNewsListLastPosition();
                mRecyclerView.getLayoutManager().scrollToPosition(lastPosition);
                isFirstAddFooter = false;
                isFirstLaunch = false;
            }
        } else {
            loadMore();
        }
    }

    @Override protected void setAdapterRegister(Context context, RecyclerView recyclerView,
                                                HeaderFooterAdapter adapter) {
        adapter.register(New.class, new NewsProvider(getContext()));
    }

    @NonNull @Override protected String request(int offset, int limit) {
        return mDiycode.getNewsList(null, offset,limit);
    }

    @Override protected void onRefresh(GetNewsListEvent event, HeaderFooterAdapter adapter) {
        super.onRefresh(event, adapter);
        mDataCache.saveNewsListObj(adapter.getDatas());
    }

    @Override protected void onLoadMore(GetNewsListEvent event, HeaderFooterAdapter adapter) {
        // TODO 排除重复数据
        super.onLoadMore(event, adapter);
        mDataCache.saveNewsListObj(adapter.getDatas());
    }

    @Override public void onDestroyView() {
        super.onDestroyView();
        // 存储 PageIndex
        mConfig.saveNewsListPageIndex(pageIndex);
        // 存储 RecyclerView 滚动位置
        View view = mRecyclerView.getLayoutManager().getChildAt(0);
        int lastPosition = mRecyclerView.getLayoutManager().getPosition(view);
        mConfig.saveNewsListPosition(lastPosition);
    }
}
View Code

 

5.5.首先看一下怎么定义的

  

  GetNewsListEvent是新闻的集合作为参数的一个BaseEvent类。

  这里设置是否是第一次加载。

 

5.6.外部如何调用这个碎片

  

  外部只要调用了这个函数,即可加载news这个碎片。

  数据通过bundle传递。

 

5.7.实现RefreshRecyclerFragment中的最后的一个抽象函数initData

  作用:尝试读取缓存数据。

  

  优先从缓存中获取数据

  如果是第一次加载则恢复混动位置

  如果没有缓存则从网络加载

  用什么方法记住当前看到了第几页呢?当前的位置呢?

  答案是Config。

  这是一个自定义的类,就是存放临时用来记录历史的。

  如何让recyclerView滑动到原来的位置呢?

  答案是:mRecyclerView.getLayoutManager().scrollToPosition(上次的位置)

  然后修改是否第一次添加Footer,是否第一次加载

  如果缓存中没有数据,就loadMore()

  loadMore()是定义在RefreshRecyclerFragment中的==>重新request数据

 

5.8.实现在RefreshRecycleFragment中定义的setAdapterRegister方法

  

  将一个Bean类的参数+一个BaseViewProvider类型的参数 传进去即可。

  第一个参数,是SDK中定义的Bean,第二个参数是内容提供器(自定义)。以后再研究。

 

5.9.实现在RefreshRecycleFragment中定义的request方法

  

     传入两个范围参数,进行真实数据的请求。

 

5.10.第三次onRefresh刷新

  第一次RefreshRecyclerFragment==>状态的配置

  第二次SimpleRefreshRecyclerFragment==>数据处理

  第三次自定义某个应用类==>添加缓存

  

 

5.11.第三次onLoadMore加载

  第一次RefreshRecyclerFragment==>状态的配置

  第二次SimpleRefreshRecyclerFragment==>数据处理

  第三次自定义某个应用类==>添加缓存

  

 

5.12.将这个Fragment关闭时要执行的函数

  

  每次退出APP,退出这个页面的时候,记录一下上次滑动的位置

  将这个位置存储到Config中,这是自定义的一个存储数据的类。


6.总结一下

6.1.这篇博客讲述了Diycode开源项目 搭建可以具有下拉刷新和上拉加载的Fragment,其实不只有这个项目要用到

  这种可以上拉刷新,下拉加载更多的Fragment,之前我做大学喵APP的时候也是用Fragment实现上拉刷新,

  下拉加载更多,我只会用一些第三方库来实现,而且这个第三方库局限性特别大,而且不好用,今后可以用这种

  方法,效果也好,虽然复杂点,但是原理搞懂了,什么都不怕了。

 

6.2.如何搭建这种Fragment呢?首先由一个BaseFragment,定义了最基本的三个方法,onCreate,onCreateView,

  onActivityCreated,主要的作用就是一些配置+缓存的初始化,除此之外两个抽象函数,必须在子类中实现,

  一个是getLayoutId获取布局id,就是这个碎片的布局,另外一个是initViews初始化视图,在onActivityCreated

  中执行了,可以说这个是最后一个在BaseFragment中执行的函数了。然后是一个用户提示的toast了。

 

6.3.最复杂的莫过于这个RefreshRecycleFragment,挺长的。具体实现了SwipeRefreshLayout+RecyclerView

  的初始化,将一个自定义的HeaderFooterAdapter(带有头部和尾部的一个适配器)+尾部的内容提供器发生

  必要的联系。然后定义刷新执行什么请求,加载执行什么请求,怎么修改状态,定义数据的回调,回调中

  刷新成功,加载成功,或者请求失败该怎么处理,部分定义了抽象函数,所以要在子类中实现。

 

6.4.然后再次继承者为SimpleRefreshRecyclerFragment,作用也非常明显,就是处理真实数据的,将调用适配器中

  的addDatas方法,将event中的数据放在适配器中。然后如果请求失败,也会toast友好提示用户。

 

6.5.真实的调用过程,首先是实现newInstance,基本都要实现的吧。为了方便调用这个碎片。然后就是最关键的

  初始化数据,作用相当明显,就是从缓存中获得数据,如果没有才去请求。然后记录一下位置,用Config来

  记录每个碎片的页码和位置。然后实现了一些抽象函数,setAdapterRegister函数是注入数据类型,里面

  有很大一坨东西,不过先不研究。然后是request这里进行细节的请求。然后第三次刷新和第三次加载来放入

  缓存,然后一个onDestroyView来处理关闭碎片后记录存储位置,放入Config。

 

6.6.总之,一个实现上拉刷新,下拉加载更多的碎片要实现这些东西,这里讲3个抽象类放在一起,然后Fragment

  只要修改一下本Fragment就行了,实现必要的刷新和加载更多的方法,耦合性比较低,所以可以直接用的。

  唯一的问题就是这里用到了EventBus,这个和SDK定义的一些Bean还扯上了关系,这里有一些耦合,另外还有

  HeadFooterAdapter和FooterProvider有一些不熟,之后再详细看看。

 


posted @ 2017-11-23 22:45  Jason_Jan  阅读(720)  评论(0编辑  收藏  举报