教你如何使用SwipeRefreshLayout来构建一个上拉加载下拉刷新框架

前言:

基本上所以的移动端应用都有Listview(当然RecyclerView也一样),那必不可少的都会嵌入一个上拉加载下拉刷新的功能。这样能大大的减少用户的流量消耗,同样对于用户也有更好的用户体验。说到这个功能,那必不可少的会涉及到
SwipeRefreshLayout,这是由Google给我们封装好的一个sdk,他支持我们listview的下拉刷新功能。当然也可以自己自定义一个下拉刷新的view,这样能根据自己的项目来设计,这样更有个性化吧。不过我这仅仅使用这个SwipeRefreshLayout来构建。当然仅仅有它还是不够的,还需要对它进行一层封装,增加一个上拉加载的功能。SwipeRefreshLayout这里就不详细介绍了,网上有很多介绍资料。

先谈下SwipeRefreshLayout的封装-增加上拉加载的功能

1.我们先要有个footerview,它的作用就是当能上拉加载的时候显示的。大部分app都是一句提示,上拉加载更多
2.只有listview滑动到底部的时候才会触发上拉加载的功能,所以我们要知道listview是否滑动到底部,同样的它也决定我们footerview显示的时机。
3.通过滑动的距离来判断,我们是否在进行上拉操作。
4.通过标识来判断是否正在加载数据,避免重复上拉而导致重复调用接口。

好了,大致思路就这样,接下来就对SwipeRefreshLayout来进行封装吧,代码中都有注释,我就不细说了。
public class RefreshLayout extends SwipeRefreshLayout {

    //最小滑动距离,超过则判断是否是上拉操作
    private final int mTouchSlop;

    private ListView mListView;

    //提供获取数据接口
    private OnLoadListener mOnLoadListener;

    //用来判断是否是上拉操作
    private float firstTouchY;
    private float lastTouchY;

    //Listview是否正在加载数据
    private boolean isLoading = false;

    public RefreshLayout(Context context) {
        this(context, null);
    }

    public RefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    //set the child view of RefreshLayout,ListView
    public void setChildView(ListView mListView) {
        this.mListView = mListView;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                firstTouchY = event.getRawY();
                break;

            case MotionEvent.ACTION_UP:
                lastTouchY = event.getRawY();
                //是否能加载数据
                if (canLoadMore()) {
                    loadData();
                }
                break;
            default:
                break;
        }

        return super.dispatchTouchEvent(event);
    }

    //是否能加载数据
    private boolean canLoadMore() {
        return isBottom() && !isLoading && isPullingUp();
    }

    //判断Listview是否滑动到底部
    private boolean isBottom() {
        if (mListView.getCount() > 0) {
            if (mListView.getLastVisiblePosition() == mListView.getAdapter().getCount() - 1 &&
                    mListView.getChildAt(mListView.getChildCount() - 1).getBottom() <= mListView.getHeight()) {
                return true;
            }
        }
        return false;
    }

    //是否正在上拉
    private boolean isPullingUp() {
        return (firstTouchY - lastTouchY) >= mTouchSlop;
    }

    private void loadData() {
        if (mOnLoadListener != null) {
            setLoading(true);
        }
    }

    public void setLoading(boolean loading) {
        if (mListView == null) return;
        isLoading = loading;
        if (loading) {
            if (isRefreshing()) {
                setRefreshing(false);
            }
            //避免listview刷新的时候直接滑动到顶部
            mListView.setSelection(mListView.getAdapter().getCount() - 1);
            mOnLoadListener.onLoad();
        } else {
            firstTouchY = 0;
            lastTouchY = 0;
        }
    }

    //提供接口,执行获取数据操作
    public void setOnLoadListener(OnLoadListener loadListener) {
        mOnLoadListener = loadListener;
    }

    public interface OnLoadListener {
        public void onLoad();
    }
}
footerview我就没定义在里面了,这里主要是获取footerview显示的时机。

第二部,构建一个utils,用来封装我们的刷新与加载操作,同时可以在其中添加我们的加载动画,footerview的显示时机等。

1.处理具体的上拉加载操作,下拉刷新操作,这里是通过暴露接口的方式提供给个个页面的。
2.获取数据之后的操作,往adapter中设值,以及动画,footerview的显示等。
大致上就这两个点,说的浅显点,我们无非就是要获取数据,然后把数据给显示出来。接下来看下我们utils的代码。

public class RefreshHelper <T>{
    //刷新框架
    private RefreshLayout refreshLayout;

    //正在加载时显示的动画,这个就要自己根据自己项目来定义了,这代码我就不贴出来了
    private LoadingLayout loadingLayout;

    //获取第几页的数据
    private int cuurentPage = 1;

    //当前页的条数
    private int mCurrentPageCount = 0;

    //是否能加载更多
    private boolean isLoadMore = true;

    //是否正在刷新, 避免接口慢的情况下,同时执行上拉下载和下拉刷新的操作
    private boolean isRefresh = false;

    //自定义的footerview,这footerview的代码就不贴了,这个自己根据自己项目的ui设计来定义吧
    private XListViewFooter footerView;

    private Context mContext;

    private ListView mListView;

    private OnGetListViewDataInterface onGetListViewDataInterface = null;

    public interface OnGetListViewDataInterface{
        //获取服务端数据接口,用于刷新以及下拉
        void getDataList(int currentPage);
        //清除adapter中的数据
        void clearData();
        //往adapter中放数据
        void setData();
    }

    private Handler handler;

    public RefreshHelper(RefreshLayout refreshLayout, LoadingLayout loadingLayout, XListViewFooter footerView, Context mContext, ListView mListView, Handler handler, OnGetListViewDataInterface onGetListViewDataInterface){
        this.refreshLayout = refreshLayout;
        this.loadingLayout = loadingLayout;
        this.footerView = footerView;
        this.mContext = mContext;
        this.onGetListViewDataInterface = onGetListViewDataInterface;
        this.mListView = mListView;
        this.handler = handler;
        init();
    }

    //初始化refreshlayout,以及加载,刷新操作
    private void init() {
        refreshLayout.setChildView(mListView);

        //直接转成HeaderViewListAdapter,防止强转失败异常
        if(mListView.getFooterViewsCount()==0){
            mListView.addFooterView(footerView);
        }


        //下拉刷新动画处理
        refreshLayout.setColorSchemeResources(R.color.color_swipeRefreshLayout1,
                R.color.color_swipeRefreshLayout2, R.color.color_swipeRefreshLayout3,
                R.color.color_swipeRefreshLayout4);

        //下拉刷新操作
        refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                isRefresh = true;
                //避免正在下拉的时候执行上拉操作,其实已经在外层通过 isRefresh这个值来标识了,
                refreshLayout.setRefreshing(true);
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        loadingLayout.setLoadStrat();
                        refreshLayout.setRefreshing(false);
                        //每次刷新重新加载次初始数据
                        cuurentPage = 1;
                        onGetListViewDataInterface.clearData();
                        onGetListViewDataInterface.getDataList(cuurentPage);
                    }
                }, 2000);
            }
        });

        //是否加载更多
        refreshLayout.setOnLoadListener(new RefreshLayout.OnLoadListener() {
            @Override
            public void onLoad() {
                //如果可以加载更多,并且不是正在获取数据,则调起接口获取数据
                if(isLoadMore && !isRefresh){
                    refreshLayout.setLoading(false);
                    footerView.loading();
                    cuurentPage++;
                    onGetListViewDataInterface.getDataList(cuurentPage);
                }else{
                    refreshLayout.setLoading(false);
                }

            }
        });

        //设置动画效果,这个就要根据自己项目来定了,也就是一个加载动画,未加载动画定义一个点击事件,当接口获取失败时,重新调起接口
        loadingLayout.setBtnRetry(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //检查网络是否连接
                if (NetUtil.checkNetIsAccess(mContext)) {
                    loadingLayout.setLoadStrat();
                    //这里是初次进入页面调起接口失败重新调起接口时候的操作,
                    cuurentPage = 1;
                    onGetListViewDataInterface.getDataList(cuurentPage);
                } else {
                    loadingLayout.setLoadStop(false, null, "请检查网络连接");
                }
            }
        });

    }

    //从服务端获取到数据之后的,后续操作
    public void laterHandle(List<T> mList){
        if(mList != null){
            mCurrentPageCount = mList.size();
            //当前页的Count>0的时候,如果当前页的Count == 20则可以上拉加载,否则不行
            if(mCurrentPageCount > 0){
                //这里Constant.PAGE_SIZE == 20
                if(mCurrentPageCount == Constant.PAGE_SIZE){
                    isLoadMore = true;
                    //这里footerview的操作是 显示提示语,上拉下载更多
                    footerView.normal();
                }else{
                    isLoadMore = false;
                    mListView.removeFooterView(footerView);
                }
                onGetListViewDataInterface.setData();
                isRefresh = false;
            }
            //如果当前页码为第一页,且Record为0,则隐藏listview
            if(mCurrentPageCount == 0 && cuurentPage == 1){
                isLoadMore = false;
                mListView.setVisibility(View.GONE);
                refreshLayout.setVisibility(View.GONE);
            }
            //如果当前页不为1,切加载到的Record为0,则已经没有更多的数据了
            if(mCurrentPageCount == 0 && cuurentPage>1){
                ToastUtil.show(mContext, "没有更多数据了");
                isLoadMore = false;
                mListView.removeFooterView(footerView);
            }

        }else {
            ToastUtil.show(mContext, "没有更多数据了");
            isLoadMore = false;
            mListView.setVisibility(View.GONE);
            refreshLayout.setVisibility(View.GONE);
        }
        //停止加载动画
        loadingLayout.setLoadStop(true, null, null);
        isRefresh = false;
    }


    public void setCuurentPage(int cuurentPage) {
        this.cuurentPage = cuurentPage;
    }

    public int getCuurentPage() {

        return cuurentPage;
    }

    public OnGetListViewDataInterface getOnGetListViewDataInterface() {
        return onGetListViewDataInterface;
    }

    public void setOnGetListViewDataInterface(OnGetListViewDataInterface onGetListViewDataInterface) {
        this.onGetListViewDataInterface = onGetListViewDataInterface;
    }
}

具体架构我们已经搭建好了,这里仅仅只是提供一些必要的操作,当然你也可以根据自己要处理的业务来添加内容。那接下来就是如何使用这个了。先贴下布局代码。

由于我这里也没写demo,最近一直在优化项目太忙了,就直接拿我在项目中写的框架来讲解的,所以我就只没整全部的代码来了。

然后就是我们的初始化操作,以及接口的调起还有获取数据之后的操作。

1.初始化操作代码:

2.调起接口操作:



3.获取数据之后的操作,也就是我们handlemessage中的操作


好了,整个流程已经介绍到这里了,多琢磨琢磨代码,你就会发现实现上拉加载下拉刷新其实就这么简单,希望这个框架能帮助到你。值得一提的是,我这为什么没有把这个框架写成单例模式?就是因为避免在不注意的时候可能会出现内存泄漏。所以有时候单例还是要慎用的。不过这个具体还是要根据自己实际情况来吧。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值