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


    既然选择了,便不顾风雨兼程。Just follow yourself.
  • 相关阅读:
    Java数组的使用
    Java的栈堆以及数组两种不同类型的定义
    Java数组声明的创建
    JAVA递归
    Java可变参数
    Java方法(类--------对象--------方法)
    html块元素和内联元素的区别
    HTML基础介绍
    CSS网页美化元素属性介绍
    ArrayList类的remove(Object o)方法简述
  • 原文地址:https://www.cnblogs.com/Jason-Jan/p/7887430.html
Copyright © 2020-2023  润新知