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); } }
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有一些不熟,之后再详细看看。