SwipeRefreshLayout+Recyclerview的刷新加载封装

目录

1.简介

2.自定义的SwipeRefreshLayout

1)全局变量和基础方法

2)onlayout拿到RecyclerView,设置加载更多的监听

3)其余的判断标准

3.Activity中的使用

4.xml布局使用

5.适配器和单行布局


1.简介

当页面展示大量相同布局的数据的时候,公司的接口一般都是一页一页的去请求并拿到数据去展示,防止页面因同时加载大量数据出现内存溢出等问题。

下面demo假设每页最多20条数据,第一页数据或者后面加载的某一页数据有可能数量小于20,当小于20的时候,我们就不接着去触发上拉加载更多的监听。主要介绍封装的SwipeRefreshLayout以及它的使用。

顺便说一下,我们上拉加载更多的判定条件有4个

a.recyclerview的状态为RecyclerView.SCROLL_STATE_IDLE

b.recyclerview滑动到了底部

c.recyclerview未在上拉加载或者下拉刷新状态(免得多次加载或者刷新数据的时候去加载数据)

d.手指做了上划的操作,且滑动距离大于android认定的最小滑动距离

demo地址:https://download.csdn.net/download/qq_37321098/10657545

2.自定义的SwipeRefreshLayout

1)全局变量和基础方法

    private static final String TAG = "测试";
    //正在加载状态
    private boolean isLoading = false;
    //最小滑动距离,手移动的距离大于这个距离才能拖动控件
    private int mScaledTouchSlop;
    //加载控件
    private RecyclerView mRecyclerView;
    //在分发事件的时候处理子控件的触摸事件
    private float mDownY, mUpY;
    private OnLoadMoreListener mListener;
    //是否还有更多数据
    private boolean hasMoreDate = true;

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

    //属性设置
    public void setInitParams(int bgColor, int proColor) {
        //下拉进度的背景颜色
        setProgressBackgroundColorSchemeResource(bgColor);
        //进度条颜色
        setColorSchemeResources(proColor);
    }

注意全局变量hasMoreDate就是我们用来判断是否还有更多加载数据判断的依据。如果没有,是不会去触发加载更多的监听。

2)onlayout拿到RecyclerView,设置加载更多的监听

 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        //判断内部是ListView还是RecyclerView
        if (getChildCount() > 0) {
            if (getChildAt(0) instanceof RecyclerView) {
                mRecyclerView = (RecyclerView) getChildAt(0);
                // 设置RecyclerView的滑动监听
                setRecyclerViewOnScroll();
            }
        }
    }

    //设置RecyclerView的滑动监听
    private void setRecyclerViewOnScroll() {
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                boolean isBottom = isVisBottom(recyclerView);
                if (newState == RecyclerView.SCROLL_STATE_IDLE &&
                        isBottom &&
                        !isLoading &&
                        (mDownY - mUpY) >= mScaledTouchSlop) {
                    Log.e(TAG, "满足条件->加载数据");
                    if(!hasMoreDate){
                        Log.e(TAG, "没有更多数据了");
                        return;
                    }
                    if (mListener != null) {
                        // 加载状态
                        setLoading(true);
                        mListener.onLoadMore();
                    }
                }
            }
        });
    }


 public static boolean isVisBottom(RecyclerView recyclerView) {
        //屏幕中最后一个可见子项的position=总数-1
        //当前屏幕所看到的子项个数大于0
        //RecyclerView的滑动状态为空闲
        if (linearLayoutManager.getChildCount() > 0 &&
                linearLayoutManager.findLastVisibleItemPosition() == linearLayoutManager.getItemCount() - 1 &&
                recyclerView.getScrollState() == recyclerView.SCROLL_STATE_IDLE) {
            Log.e(TAG, "页面展示到底部条目");
            return true;
        } else {
            Log.e(TAG, "页面未展示到底部条目");
            return false;
        }
    }

加载更多的4个依据,开头已经提到过。isVisBottom()函数就是判断Recyclerview是否滑动到了底部,根据最后显示的数据位置是否等于item总数减一(lastVisibleItemPosition 从0起算的),注意这是判断垂直Recyclerview布局情况下滑动到底部的依据。如果是瀑布流布局,我们需要减的数目是你每一行展示内容的个数。还有一种特殊情况,之前也遇到过。如果是Scrollview嵌套的Recyclerview,需要判断的是Scrollview滑动到底部(你会发现Recyclerview滑动的监听不会触发,因为焦点在Scrollview身上),再去加载更多数据,这个可以去百度下如何判断Scrollview滑动到底部,此情况不多见。

3)其余的判断标准

  @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 移动的起点
                mDownY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                // 移动的终点
                mUpY = getY();
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    public void setLoading(boolean loading) {
        isLoading = loading;
        //FIXME 可以为recyclerview添加底部布局,通过判断布局type,数据总数+1,加载底部布局
        mDownY = 0;
        mUpY = 0;
    }

    //是否还有更多数据
    public void setHasMoreDate(boolean hasMoreDate) {
        this.hasMoreDate = hasMoreDate;
    }

选择在dispatchTouchEvent方法中,拿到down和up在Y轴上位置去判断是否上滑。这个判断还是有必要的,不然会出现到达底部,即使做下滑的操作,都会去触发上拉加载数据的监听。

setLoading()函数就是外部用来设置正在刷新数据或者加载数据的标志,让SwipeRefreshLayout不去触发加载的监听。

setHasMoreDate()函数,就是没有更多数据,或者第一页数据不满分页加载时每一页个数的时候,去调用的方法,设置了之后同样也不会触发加载的监听。

3.Activity中的使用

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private MySwipeRefresh mySwipeRefresh;
    private TextDateAdapter textDateAdapter;
    private LinearLayoutManager linearLayoutManager;
    //是否刷新状态中
    private boolean isRefreshing = false;
    //接口中每一页的数据
    private int pageSize = 20;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initRefreshView();
    }

    private void initRefreshView() {
        recyclerView = (RecyclerView) findViewById(R.id.rv);
        linearLayoutManager = new LinearLayoutManager(MainActivity.this);
        recyclerView.setLayoutManager(linearLayoutManager);
        mySwipeRefresh = (MySwipeRefresh) findViewById(R.id.refresh);
        mySwipeRefresh.setInitParams(android.R.color.white, R.color.colorAccent);
        //分割
        recyclerView.addItemDecoration(new DividerItemDecoration(MainActivity.this,
                linearLayoutManager.getOrientation()));
        //刷新加载回调
        initLoadCallback();
        //数据初始加载
        initData();
    }

    private void initLoadCallback() {
        // 下拉时触发SwipeRefreshLayout的下拉动画,动画完毕之后就会回调这个方法
        mySwipeRefresh.setOnRefreshListener(new MySwipeRefresh.OnRefreshListener() {
            @Override
            public void onRefresh() {
                if (isRefreshing) {
                    Log.e("测试: ", "下拉刷新中,return");
                    return;
                }
                initData();
            }
        });

        // 设置下拉加载更多
        mySwipeRefresh.setOnLoadMoreListener(new MySwipeRefresh.OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                loadMoreData();
            }
        });
    }

    private void initData() {
        isRefreshing = true;
        mySwipeRefresh.setRefreshing(true);
        mySwipeRefresh.setLoading(true);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //FIXME 设置是否还有更多数据,没有的话,不去调用加载的监听
                List<String> list = getRefreshDate();
                if (list.size() != pageSize) {
                    mySwipeRefresh.setHasMoreDate(false);
                } else {
                    mySwipeRefresh.setHasMoreDate(true);
                }
                //初始化数据加载
                if (textDateAdapter == null) {
                    textDateAdapter = new TextDateAdapter(MainActivity.this, list);
                    recyclerView.setAdapter(textDateAdapter);
                } else {
                    textDateAdapter.refreshData(list);
                }
                // 收起下拉进度条
                if (mySwipeRefresh.isRefreshing()) {
                    Log.e("测试", "收起下拉进度条");
                    mySwipeRefresh.setRefreshing(false);
                    mySwipeRefresh.setLoading(false);
                    isRefreshing = false;
                }
            }
        }, 1000);
    }

    private void loadMoreData() {
        //FIXME 设置关卡,没有更多数据或者数据个数小于分页加载中每一页的数据,则不去加载更多数据
        //FIXME textDateAdapter.addBottomData前,swipererefresh中设置标志,不去触发loadMore的监听
        //FIXME 注意在刷新的时候,去变更适配器中的这个标志
        //mySwipeRefresh内部设置了loading状态
        isRefreshing = true;
        mySwipeRefresh.setLoading(true);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                if (textDateAdapter != null) {
                    List<String> list = getUpLoadDate();
                    //FIXME 标志的设置
                    if (list.size() != pageSize) {
                        mySwipeRefresh.setHasMoreDate(false);
                    }
                    textDateAdapter.addBottomData(list);
                    //未刷新状态
                    mySwipeRefresh.setLoading(false);
                    isRefreshing = false;
                }
            }
        }, 100);
    }

    private List<String> getRefreshDate() {
        //底部添加的数据集合
        List<String> list = new ArrayList<>();
        //大于90.模拟后台数据少于分页加载时候每一页的数据
        int num = new Random().nextInt(100);
        if (num > 80) {
            for (int i = 0; i < 10; i++) {
                list.add("最后一页数据,少于20:" + i);
            }
            return list;
        }
        for (int i = 0; i < 20; i++) {
            list.add("分页加载数据:" + i);
        }
        return list;
    }

    private List<String> getUpLoadDate() {
        //底部添加的数据集合
        List<String> list = new ArrayList<>();
        //大于90.模拟后台数据少于分页加载时候每一页的数据
        int num = new Random().nextInt(100);
        if (num > 80) {
            for (int i = 0; i < 10; i++) {
                list.add("最后一页数据,少于20:" + i);
            }
            return list;
        }
        for (int i = 0; i < 20; i++) {
            list.add("分页加载数据:" + i);
        }
        return list;
    }
}

4.xml布局使用

<?xml version="1.0" encoding="utf-8"?>
<com.bihucj.mcandroid.view.MySwipeRefresh xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/refresh"
    tools:context="com.bihucj.mcandroid.ui.MainActivity">


    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.bihucj.mcandroid.view.MySwipeRefresh>

5.适配器和单行布局

public class TextDateAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<String> mList;
    private Context context;

    public TextDateAdapter(Context context, List<String> list) {
        this.context = context;
        this.mList = list;
    }

    public void clearData() {
        if (mList.size() > 0 && mList != null) {
            mList.clear();
            notifyDataSetChanged();
        }
    }

    public void addBottomData(List<String> list) {
        if (list.size() > 0 && list != null) {
            mList.addAll(list);
            notifyDataSetChanged();
        }
    }

    public int getListSize() {
        return (mList == null) ? 0 : mList.size();
    }

    public void refreshData(List<String> list) {
        if (list.size() > 0 && mList != null) {
            mList.clear();
            mList.addAll(list);
            notifyDataSetChanged();
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.singleitem_text_date, null);
        return new ImgsViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        holder.setIsRecyclable(false);
        if (holder instanceof ImgsViewHolder) {
            TextView tv_text = ((ImgsViewHolder) holder).tv_text;
            tv_text.setHeight(50);
            tv_text.setGravity(Gravity.CENTER);
            tv_text.setText(mList.get(position));
        }
    }


    @Override
    public int getItemCount() {
        return mList.size();
    }


    private class ImgsViewHolder extends RecyclerView.ViewHolder {
        private TextView tv_text;

        public ImgsViewHolder(View itemView) {
            super(itemView);
            tv_text = itemView.findViewById(R.id.tv_text);
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp">

    <TextView
        android:id="@+id/tv_text"
        android:textColor="#00aaff"
        android:textSize="@dimen/_15dp"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="asdas"
        android:textStyle="bold" />

</LinearLayout>

猜你喜欢

转载自blog.csdn.net/qq_37321098/article/details/82592956