继承自SwipeRefreshLayout,实现加载更多

android官方提供的 SwipeRefreshLayout 非常美观实用,但是只有下拉刷新功能,我们项目中一般都是下拉刷新和上拉加载更多同时使用的。

本文将介绍一套工具集,继承自官方 SwipeRefreshLayout, 实现上拉加载更多,并且兼容ListView 和 RecyclerView 以及 RecyclerView 多列网格样式

ListView 和 RecyclerView效果一致,如下:
在这里插入图片描述
RecyclerView 多列网格样式如下:
在这里插入图片描述
首先列出核心类:

import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.zhangxq.loadrefreshlayout.R;

/**
 * Created by zhangxiaoqi on 2019/4/12.
 */
public abstract class RefreshLayout extends SwipeRefreshLayout {
    private int mTouchSlop;
    private int mYDown;
    private int mLastY;
    private float mPrevX;
    protected boolean isAutoLoad = true;
    protected View footView;

    private boolean isLoading;
    private boolean isLoadEnable;
    private OnLoadListener mOnLoadListener;

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

    public RefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        footView = LayoutInflater.from(context).inflate(R.layout.view_list_footer, null);
    }

    /**
     * 解决SwipeRefreshLayout包含ViewPager时的滑动事件冲突
     *
     * @param event
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mPrevX = MotionEvent.obtain(event).getX();
                break;

            case MotionEvent.ACTION_MOVE:
                final float eventX = event.getX();
                float xDiff = Math.abs(eventX - mPrevX);
                if (xDiff > mTouchSlop) {
                    return false;
                }
        }
        return super.onInterceptTouchEvent(event);
    }

    /**
     * 当手指上滑,离开屏幕时加载更多
     *
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mYDown = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                mLastY = (int) event.getRawY();
                loadData();
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    /**
     * 是否是上拉操作
     *
     * @return
     */
    protected boolean isPullUp() {
        return mYDown > mLastY;
    }

    /**
     * 加载更多数据
     */
    protected void loadData() {
        if (isReachBottom() && !isLoading && isPullUp() && isLoadEnable) {
            setLoading(true);
            if (mOnLoadListener != null) {
                mOnLoadListener.onLoad();
            }
        }
    }

    /**
     * @param loading
     */
    public void setLoading(boolean loading) {
        isLoading = loading;
        if (isLoading) {
            showLoadView(true);
        } else {
            showLoadView(false);
            mYDown = 0;
            mLastY = 0;
        }
    }

    /**
     * 是否滚动到了底部
     *
     * @return
     */
    protected abstract boolean isReachBottom();

    /**
     * 控制加载更多view的显示可隐藏
     *
     * @param isShow
     */
    protected abstract void showLoadView(boolean isShow);

    /**
     * 设置自动加载更多,默认开启
     *
     * @param isAuto
     */
    public void setAutoLoad(boolean isAuto) {
        isAutoLoad = isAuto;
    }

    /**
     * 设置是否可以上拉加载更多
     *
     * @param enable
     */
    public void setLoadEnable(boolean enable) {
        this.isLoadEnable = enable;
    }

    /**
     * 设置加载更多监听器,同时默认开启加载更多开关
     *
     * @param loadListener
     */
    public void setOnLoadListener(OnLoadListener loadListener) {
        mOnLoadListener = loadListener;
        setLoadEnable(true);
    }
}
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;

import com.zhangxq.loadrefreshlayout.base.OnScrolllistener;
import com.zhangxq.loadrefreshlayout.base.RefreshLayout;

public class ListRefreshLayout extends RefreshLayout {
    private ListView mListView;
    private OnScrolllistener onScrolllistener;

    /**
     * @param context
     */
    public ListRefreshLayout(Context context) {
        this(context, null);
    }

    public ListRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mListView == null) {
            findListView(this);
        }
    }

    private void findListView(View view) {
        if (view instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) view;
            if (viewGroup.getChildCount() > 0) {
                for (int i = 0; i < viewGroup.getChildCount(); i++) {
                    View viewItem = viewGroup.getChildAt(i);
                    if (viewItem instanceof ListView) {
                        mListView = (ListView) viewItem;
                        mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
                            @Override
                            public void onScrollStateChanged(AbsListView absListView, int i) {
                                if (onScrolllistener != null) {
                                    onScrolllistener.onScrollStateChanged(absListView, i);
                                }
                            }

                            @Override
                            public void onScroll(AbsListView absListView, int i, int i1, int i2) {
                                if (isAutoLoad) {
                                    loadData();
                                }
                                if (onScrolllistener != null) {
                                    onScrolllistener.onScroll(absListView, i, i1, i2);
                                }
                            }
                        });
                        return;
                    } else {
                        findListView(viewItem);
                    }
                }
            }
        }
    }

    /**
     * 判断是否到了最底部
     */
    @Override
    protected boolean isReachBottom() {
        if (mListView == null) {
            return false;
        }
        int position = mListView.getLastVisiblePosition();
        int count = 0;
        if (mListView.getAdapter() != null) {
            count = mListView.getAdapter().getCount();
        }
        return position == count - 1;
    }

    @Override
    protected void showLoadView(boolean isShow) {
        if (mListView == null) return;
        if (isShow) {
            mListView.addFooterView(footView);
        } else {
            mListView.removeFooterView(footView);
        }
    }

    public void setCrollListener(OnScrolllistener listener) {
        this.onScrolllistener = listener;
    }
}
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import com.zhangxq.loadrefreshlayout.base.RefreshLayout;

public class RecyclerRefreshLayout extends RefreshLayout {
    private RecyclerView mRecyclerView;

    /**
     * @param context
     */
    public RecyclerRefreshLayout(Context context) {
        this(context, null);
    }

    public RecyclerRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mRecyclerView == null) {
            findListView(this);
        }
    }

    private void findListView(View view) {
        if (view instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) view;
            if (viewGroup.getChildCount() > 0) {
                for (int i = 0; i < viewGroup.getChildCount(); i++) {
                    View viewItem = viewGroup.getChildAt(i);
                    if (viewItem instanceof RecyclerView) {
                        mRecyclerView = (RecyclerView) viewItem;
                        if (mRecyclerView.getAdapter() != null && mRecyclerView.getAdapter() instanceof LoadRecyclerAdapter) {
                            ((LoadRecyclerAdapter) mRecyclerView.getAdapter()).setFootView(footView);
                        }
                        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                            @Override
                            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                                super.onScrolled(recyclerView, dx, dy);
                                if (isAutoLoad) {
                                    loadData();
                                }
                            }

                            @Override
                            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                                super.onScrollStateChanged(recyclerView, newState);
                            }
                        });
                        return;
                    } else {
                        findListView(viewItem);
                    }
                }
            }
        }
    }

    /**
     * 判断是否到了最底部
     */
    @Override
    protected boolean isReachBottom() {
        if (mRecyclerView == null) {
            return false;
        } else {
            LinearLayoutManager lm = (LinearLayoutManager) mRecyclerView.getLayoutManager();
            int position = lm.findLastVisibleItemPosition();
            int count = lm.getItemCount();
            return position > count - 2;
        }
    }

    @Override
    protected void showLoadView(boolean isShow) {
        if (mRecyclerView != null && mRecyclerView.getAdapter() != null && mRecyclerView.getAdapter() instanceof LoadRecyclerAdapter) {
            LoadRecyclerAdapter adapter = (LoadRecyclerAdapter) mRecyclerView.getAdapter();
            if (isShow) {
                adapter.showFootView(true);
            } else {
                adapter.showFootView(false);
            }
        }
    }
}

import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by zhangxiaoqi on 2019/4/3.
 */
public abstract class LoadRecyclerAdapter extends RecyclerView.Adapter {
    private View footView;
    private int footerCount;
    private int dataSize;
    private Handler handler = new Handler();

    void showFootView(boolean isShow) {
        if (isShow) {
            footerCount = 1;
        } else {
            footerCount = 0;
        }
        handler.post(new Runnable() {
            @Override
            public void run() {
                notifyDataSetChanged();
            }
        });
    }

    public void setDataSize(int dataSize) {
        this.dataSize = dataSize;
        handler.post(new Runnable() {
            @Override
            public void run() {
                notifyDataSetChanged();
            }
        });
    }

    void setFootView(View footView) {
        this.footView = footView;
        notifyDataSetChanged();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == 1) {
            return onCreateItemViewHolder();
        } else {
            return new MyFooterViewHolder(footView);
        }
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return getItemViewType(position) == 1 ? 1 : gridManager.getSpanCount();
                }
            });
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (!(holder instanceof LoadRecyclerAdapter.MyFooterViewHolder)) {
            onBindItemViewHolder(holder, position);
        }
    }

    public abstract RecyclerView.ViewHolder onCreateItemViewHolder();

    public abstract void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position);

    @Override
    public int getItemCount() {
        return dataSize + footerCount;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == dataSize) {
            return -1;
        } else {
            return 1;
        }
    }

    class MyFooterViewHolder extends RecyclerView.ViewHolder {
        MyFooterViewHolder(View itemView) {
            super(itemView);
        }
    }
}

使用方式

refreshLayout.setOnLoadListener(this);
一般在onCreate方法中调用。

接口介绍

setOnLoadListener:设置加载更多监听器。
setLoadEnable:设置是否可以加载更多,设置加载更多监听器之后会默认开启,一般当加载完全部数据时关闭,下拉刷新后开启。
setAutoLoad:设置列表滚动到底部时是否自动加载更多,默认开启。
setLoading:用于数据加载完成后关闭加载动画。
setCrollListener(仅限ListRefreshLayout):由于需要监听列表滚动,所以ListRefreshLayout占用了滚动监听器,用户可以在这里设置监听器,接收由ListRefreshLayout监听并且回调出来的滚动事件。

代码简介

核心类分为三个,基类RefreshLayout,两个子类ListRefreshLayout,RecyclerRefreshLayout。

ListRefreshLayout用于实现ListView的加载更多。
RecyclerRefreshLayout和LoadRecyclerAdapter配合,用于实现RecyclerView(包含网格形式)的加载更多。

由于ListView和RecyclerView设置滚动监听方式,判断是否滚动底部方式以及设置加载更多动画的方式不一样,所以,RefreshLayout通过虚方法将这些任务交给两个子类分别实现,RefreshLayout实现了核心的加载更多监听设置以及回调,是否加载更多开关的设置,上滑手势的判断。

LoadRecyclerAdapter使用方法

RecyclerRefreshLayout和LoadRecyclerAdapter需要配合使用,RecyclerView必须设置一个继承自LoadRecyclerAdapter的适配器才能正常使用加载更多功能。
LoadRecyclerAdapter使用方法,实现两个虚方法:
onCreateItemViewHolder:使用方式同onCreateViewHolder。
onBindItemViewHolder:使用方式同onBindViewHolder。
设置数据个数:
setDataSize,当数据发生改变,及时设置数据个数。
目前来看,可以满足大部分需求。

加载更多的判断方式

加载更多的判断条件有两个,一个是手指向上滑动,一个是列表滚动到底部,当两个条件都达到时,判断用户是否开启了加载更多选项,如果开启,则触发加载更多回调,并且显示加载更多动画。
相关代码如下:

	/**
     * 当手指上滑,离开屏幕时加载更多
     *
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mYDown = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                mLastY = (int) event.getRawY();
                loadData();
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }
    
	/**
     * 加载更多数据
     */
    protected void loadData() {
        if (isReachBottom() && !isLoading && isPullUp() && isLoadEnable) {
            setLoading(true);
            if (mOnLoadListener != null) {
                mOnLoadListener.onLoad();
            }
        }
    }

isPullUp() 方法通过 mYDown 和 mLastY 两个参数判断用户手指是否向上滑动。
isReachBottom() 由两个子类实现,判断列表是否滚动到底部。
isLoadEnable,由用户通过setLoadEnable(boolean enable)设置。

加载更多动画设置方式

  • ListView
    在适当的时机添加footerView和去掉footerView,footView里包含加载更多动画。
    @Override
    protected void showLoadView(boolean isShow) {
        if (mListView == null) return;
        if (isShow) {
            mListView.addFooterView(footView);
        } else {
            mListView.removeFooterView(footView);
        }
    }
    
  • RecyclerView
    RecyclerView没有footView的概念,给RecyclerView添加footView其实就是增加了一个特殊的item,所以需要特定的适配器LoadRecyclerAdapter来实现
    @Override
    protected void showLoadView(boolean isShow) {
        if (mRecyclerView != null && mRecyclerView.getAdapter() != null && mRecyclerView.getAdapter() instanceof LoadRecyclerAdapter) {
            LoadRecyclerAdapter adapter = (LoadRecyclerAdapter) mRecyclerView.getAdapter();
            if (isShow) {
                adapter.showFootView(true);
            } else {
                adapter.showFootView(false);
            }
        }
    }
    
    public void showFootView(boolean isShow) {
        if (isShow) {
            footerCount = 1;
        } else {
            footerCount = 0;
        }
        handler.post(new Runnable() {
            @Override
            public void run() {
                notifyDataSetChanged();
            }
        });
    }
    
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == 1) {
            return onCreateItemViewHolder();
        } else {
            return new MyFooterViewHolder(footView);
        }
    }
    
    @Override
    public int getItemCount() {
        return dataSize + footerCount;
    }
    
    @Override
    public int getItemViewType(int position) {
        if (position == dataSize) {
            return -1;
        } else {
            return 1;
        }
    }
    
    showFootView其实就是修改了footerCount的值,然后通过getItemCount给列表增加或者去掉一个item名额,通过getItemViewType来判断item类型,最后在onCreateViewHolder里,在合适的时机返回并显示footView,这里利用了RecyclerView可以适配不同类型item的特性,来实现显示和隐藏加载更多动画的功能。

one more

改工具集无法实现GridView加载更多功能,如果想要实现网格型列表,使用RecyclerView设置GridLayoutManager即可。
适配RecyclerView多列列表的代码在LoadRecyclerAdapter的onAttachedToRecyclerView方法里,感兴趣的可以看看。

github源码地址
源码使用方式:直接copy loadrefreshlayout模块里的6个类或者接口到自己的项目即可,或者直接导入loadrefreshlayout模块。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值