美文网首页AndroidAndroid技术知识Android开发
继承自SwipeRefreshLayout,实现加载更多

继承自SwipeRefreshLayout,实现加载更多

作者: 空手接白刀 | 来源:发表于2019-04-19 09:38 被阅读83次

    更加强大的实现方式,请看这里
    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模块。

    相关文章

      网友评论

        本文标题:继承自SwipeRefreshLayout,实现加载更多

        本文链接:https://www.haomeiwen.com/subject/gkwfgqtx.html