RecyclerView学习(五)----SwipeRefreshLayout的下拉刷新与上拉加载

简介: SwipeRefreshLayout作为官方的下拉刷新控件,简洁美观的风格使其广泛应用在项目中。美中不足的是SwipeRefreshLayout缺少上拉加载的效果,今天结合RecyclerView实现一个支持下拉刷新与上拉加载的SwipeRefreshLayout。

SwipeRefreshLayout作为官方的下拉刷新控件,简洁美观的风格使其广泛应用在项目中。美中不足的是SwipeRefreshLayout缺少上拉加载的效果,今天结合RecyclerView实现一个支持下拉刷新与上拉加载的SwipeRefreshLayout。

先看一下最后实现的效果图:

这里写图片描述

整体效果如上所示,一起看看怎么实现的:

一.准备工作

1.加载动画实现:
示例图中,上拉加载的进度动画是一个自定义的View,这里着重分析一下实现方法,源码末尾会给出:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.translate(mWidth / 2, mHeight / 2);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.rotate(degree);
        for (int i = 0; i < 12; i++) {
            if (i == 0 || i == 1 || i == 2) {
                mPaint.setColor(Color.GRAY);
            } else {
                mPaint.setColor(Color.LTGRAY);
            }
            RectF rectF = new RectF(-mRadius / 4, -mRadius * 7 / 2, mRadius / 4, -mRadius * 2);
            canvas.drawRoundRect(rectF, mRadius / 8, mRadius / 8, mPaint);
            canvas.rotate(30);
            canvas.save();
        }
        canvas.restore();
    }

主要是画布的一些操作,移动原点到中心,绘制圆角矩形,旋转画布,动画效果是通过属性动画实现的:

    public void startAnimation() {

        degree = 0;
        degreeAnimation = ValueAnimator.ofFloat(0, 360);
        degreeAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                degree = (float) animation.getAnimatedValue();
                invalidate();
            }

        });
        degreeAnimation.setRepeatCount(ValueAnimator.INFINITE);
        degreeAnimation.setDuration(1000);
        degreeAnimation.start();
    }

    public void endAnimation() {
        degreeAnimation.end();
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (!hasWindowFocus) {
            endAnimation();
        }
    }

需要注意的是,使用循环动画时记得调用onWindowFocusChanged()方法来及时结束动画。这是因为退出当前Activity或者将当前Activity切入后台时,如果没有结束动画,可能会导致Activity无法释放从而导致内存泄漏。

关于更多的自定义加载动画,可以参考我这篇博客:

Android自定义加载动画(持续更新中…)

2.上拉加载View的显示:

ListView有直接添加头部View与尾部View的方法,RecyclerView没有直接提供这两个方法,那我们上拉加载View放在哪里呢?处理方法是重写RecyclerView.Adapter的 getItemViewType(int position)方法,根据getItemViewType传入的viewType来返回不同类型的ViewHolder。不同的位置返回不同的类型,把最后一个位置预留出来,用来存放加载更多的view。

关于这一块的内容,可以参考我这篇博客:

RecyclerView学习(一)—-初步认知

二.下拉刷新的实现

        swipeRefreshLayout.post(new Runnable() {
            @Override
            public void run() {
                swipeRefreshLayout.setRefreshing(true);
            }
        });
        refresh();

        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                refresh();
            }
        });

    private void refresh() {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                getData();
                recyclerView.setAdapter(myAdapter);
                myAdapter.notifyDataSetChanged();
                swipeRefreshLayout.setRefreshing(false);
            }
        }, 1500);
    }

SwipeRefreshLayout的基本用法,这里有一点要注意的就是SwipeRefreshLayout进入页面时自动刷新。直接使用 swipeRefreshLayout.setRefreshing(true)方法没有效果,得像上面那样设置才会有效果。

三.上拉加载的实现

    public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {

        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

            View rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_refresh_recycler, parent, false);

            return new MyViewHolder(rootView);
        }

        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            holder.txt.setText(integerList.get(position));
        }

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

    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView txt;

        public MyViewHolder(View itemView) {
            super(itemView);
            txt = (TextView) itemView.findViewById(R.id.item_txt);
        }
    }

这是支持下拉刷新下RecyclerView的adapter,上面我们分析到,需要重写 getItemViewType(int position)方法来存放上拉加载View。那我们需要修改getItemViewType(),onCreateViewHolder(),onBindViewHolder(),getItemCount()等方法,并对viewType进行判断。那么如何在不破坏原有Adapter实现的情况下完成呢?

这里引入装饰器(Decorator)设计模式,该设计模式通过组合的方式,在不 破坏原有类代码的情况下,对原有类的功能进行扩展。

参考资料:
RecyclerView 必知必会

看看怎么实现的:

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


    private RecyclerView.Adapter adapter;
    private View footerView;
    public static final int NORMAL_VIEW_TYPE = 1;
    public static final int FOOTER_VIEW_TYPE = 2;

    public MyRefreshAdapter(RecyclerView.Adapter adapter) {
        this.adapter = adapter;
    }


    @Override
    public int getItemViewType(int position) {
        if (position == getItemCount() - 1) {
            return FOOTER_VIEW_TYPE;
        }
        return NORMAL_VIEW_TYPE;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == FOOTER_VIEW_TYPE) {
            return new RecyclerView.ViewHolder(footerView) {
            };
        } else {
            return adapter.onCreateViewHolder(parent, viewType);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == getItemCount() - 1) {
            return;
        } else {
            adapter.onBindViewHolder(holder, position);
        }

    }

    @Override
    public int getItemCount() {
        return adapter.getItemCount() + 1;

    }

    public void addFooterView(View footerView) {
        this.footerView = footerView;
    }
}

getItemViewType()用于决定元素的布局使用哪种类型,返回的是一个int值作为传递给onCreateViewHolder的第二个参数;onCreateViewHolder根据getItemViewType传入的viewType来渲染构造不同的ViewHolder;ViewHolder用来存放视图与数据,通过返回不同类型的ViewHolder达到预期效果。

上拉加载view已经找到存放的地方,什么时候显示呢?

我这里的处理方法是自定义一个RecyclerView,添加滑动监听,当滑动到底部时进行处理:

        this.addOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
                //停止滚动时
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    //获取最后一个完全显示Item的Position
                    int lastVisibleItem = manager.findLastCompletelyVisibleItemPosition();
                    int totalItemCount = manager.getItemCount();
                    // 判断是否滚动到底部,并且不在加载状态
                    if (lastVisibleItem == (totalItemCount - 1) && !isLoadMore) {
                        isLoadMore = true;
                        loadTxt.setText("正在加载...");
                        circleProgressView.setVisibility(VISIBLE);
                        footerView.setVisibility(VISIBLE);
                        myLoadListener.onLoadMore();
                    }
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });

当RecyclerView停止滚动时,获取最后一个完全显示Item的Position,判断是否滚动到底部,并且不在加载状态,符合条件的情况就接口回调加载数据。

    public interface MyLoadListener {
        void onLoadMore();
    }

重写setAdapter()方法,这样我们的MyRefreshAdapter便能发挥它的功能:

    @Override
    public void setAdapter(Adapter adapter) {

        LinearLayout footerLayout = new LinearLayout(getContext());
        footerLayout.setGravity(Gravity.CENTER);
        footerLayout.setLayoutParams(new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, 160));

        circleProgressView = new CircleProgressView(getContext());
        circleProgressView.setLayoutParams(new LinearLayout.LayoutParams(80, 80));
        circleProgressView.startAnimation();
        footerLayout.addView(circleProgressView);
        loadTxt = new TextView(getContext());
        footerLayout.addView(loadTxt);
        footerView = footerLayout;
        footerView.setVisibility(GONE);
        myRefreshAdapter = new MyRefreshAdapter(adapter);
        myRefreshAdapter.addFooterView(footerView);
        super.setAdapter(myRefreshAdapter);
    }

动态添加上拉加载view,调用myRefreshAdapter.addFooterView()方法添加进去。Activity中使用:

MyAdapter myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);

       recyclerView.setMyLoadListener(new MyRefreshRecyclerView.MyLoadListener() {
            @Override
            public void onLoadMore() {
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (integerList.size() > 14) {
                            recyclerView.setLoadMore(true);
                        } else {
                            int randomInt = new Random().nextInt(100);
                            integerList.add("上拉加载添加数字:" + randomInt);
                            myAdapter.notifyDataSetChanged();
                            recyclerView.setLoadMore(false);
                        }

                    }
                }, 1000);
            }
        });

额外在自定义的recyclerView中添加了一个判断是否加载完成的方法:

    public void setLoadMore(boolean complete) {
        if (complete) {
            loadTxt.setText("已经全部加载完啦!");
            circleProgressView.setVisibility(GONE);
        } else {
            footerView.setVisibility(GONE);
        }
        isLoadMore = false;
    }

这样,一个完整的下拉刷新与上拉加载就已经完成了,希望看完本篇文章能对你有所帮助。

项目完整源码已经上传到我的github上,源码地址:

https://github.com/18722527635/MyRecyclerView

欢迎Star,fork,提issues,一起进步!

目录
相关文章
|
Android开发
Android 使用SwipeRefreshLayout实现RecyclerVeiw的下拉刷新和上拉加载
Android 使用SwipeRefreshLayout实现RecyclerVeiw的下拉刷新和上拉加载
131 0
利用RecyclerView实现无限轮播广告条1
利用RecyclerView实现无限轮播广告条
利用RecyclerView实现无限轮播广告条2
利用RecyclerView实现无限轮播广告条
自己动手写RecyclerView的下拉刷新1
自己动手写RecyclerView的下拉刷新
自己动手写RecyclerView的下拉刷新2
自己动手写RecyclerView的下拉刷新
SwipeRefreshLayout 下拉刷新控件(一)
SwipeRefreshLayout 下拉刷新控件(一)
SwipeRefreshLayout 下拉刷新控件(二)
SwipeRefreshLayout 下拉刷新控件(二)
|
Android开发
ScrollView 与 ListView 以及 GridView 滑动冲突完美解决
ScrollView 与 ListView 以及 GridView 滑动冲突完美解决
|
API
为RecyclerView添加下拉刷新功能
在之前的文章中,我们实现了带有header和footer功能的WrapRecyclerView。 现今App中列表的下拉刷新和上拉加载已经是一种习惯了,这两个操作也确实方便很多。 为RecyclerView添加这个功能可以通过多种方法,这里我选用了一种简单的做法。基于pulltorefresh这个库。
190 0
SwipeRefreshLayout 嵌套 RecyclerView滑动冲突
SwipeRefreshLayout 嵌套 RecyclerView滑动冲突
333 0