RecyclerView下拉刷新上拉加载

按照国际惯例,先上效果图。
下拉刷新:

RecyclerView下拉刷新上拉加载


上拉加载:

RecyclerView下拉刷新上拉加载


说一下第一张图,顶端的黑边是做gif图的时候出现的,不明其意,尴尬咯!下面进入正题。


下拉刷新

思路:一切复杂的事情都是先从简单入手,分为以下步骤:

1. 我们可以把头部的下拉刷新布局当做列表的一个item,就是头部item,它的布局肯定是跟我们内容不一样的,这个好办,RecyclerView.Adapter类提供了一个getItemViewType(int position)方法,这个方法可以让我们轻松实现一个列表可以有不同布局。

2. 我们发现当我们下拉的时候,该布局的内容是需要改变的,这个也容易,只要在适配类添加方法就可以办到,比如我这个例子只是简单的改变文字描述

private TextView tvContentHead;
class HeadViewHolder extends RecyclerView.ViewHolder{


    public HeadViewHolder(View itemView) {
        super(itemView);
        tvContentHead = (TextView) itemView.findViewById(R.id.tvContentHead);
    }
}

public void setText(String text){
    tvContentHead.setText(text);
}

setText() 就是我给外部提供的方法,HeadViewHoler类就是我们的头部Item

3. 我们的列表是到达顶部时,才执行下拉刷新的,所以我们应当要判断列表是否到达顶部,RecyclerView可以监听OnScrollListener类,并且实现onScrolled(RecyclerView recyclerView, int dx, int dy)方法,这个方法是监听列表的滑动,那怎么样才知道列表已经滑动到顶部了呢?网上有很多资料,列举了几个方法,但是我觉得最有信服力是canScrollVertically方法,下面看代码

this.addOnScrollListener(new OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (recyclerView.canScrollVertically(-1)){
            isTop = false;
        }else {
            isTop = true;
        }

        if (!recyclerView.canScrollVertically(1)){
            pullRefreshListener.isLoading();
        }

    }
});


recyclerView.canScrollVertically(-1) 这个方法返回false时,说明列表已经滑动到顶部了,这时我们就可以操作下拉刷新了

4. 下面是整个下拉刷新的核心内容  已经到达顶部了,我们怎么样才可以让他有下拉的感觉呢? 我这里使用一个技巧,说到这个技巧我都想笑,那就是外边距,对,没错,我就是通过控制列表的上边外边距实现的下拉刷新,开始时,我们的头部本来就是存在的,所以我们把marginTop设置为负头部的高度值,这样给人的感觉就是没有头部内容了,所以当我们到底顶部的时候,就可以通过控制上边外边距来控制下来效果,当然,还有解决滑动冲突,下面看代码:

this.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    y = event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (!isTop) {
                        y = event.getRawY();
                    } else {
                        float mY = event.getRawY();
                        float dY = mY - y;
                        if (dY >= 0 && isOpenRefresh) {
                            int top = (int) (dY + topMargin) >= 0 ? 0 : (int) (dY + topMargin);
                            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
                            layoutParams.setMargins(0, top, 0, 0);
                            setLayoutParams(layoutParams);
                            if (dY >= headHeight) {
                                isRefresh = true;
                                dropDownRefreshListener.isRefreshTip();
                            } else {
                                isRefresh = false;
                                dropDownRefreshListener.noRefreshTip();
                            }
                            return true;
                        }
                    }

                    break;
                case MotionEvent.ACTION_UP:
                    if (isTop) {
                        if (isRefresh) {
                            dropDownRefreshListener.isRefreshing();
                        }else {
                            dropDownRefreshListener.noRefresh();
                        }

                    }

                    break;
            }
            return false;
        }
    });
}

实现onTouch,当到达顶部时,就计算下来距离,然后改变上边外边距,当下拉到一定数值时,提示用户可以可以刷新了。我这里是使用了回调dropDownRefreshListener.isRefreshTip();方便用户自定义信息。当我们正在执行下拉操作时,onTouch返回true,直接消费,其他操作直接返回false,这样就解决了滑动冲突。当手指离开屏幕时,如果isRefesh为true,说明可以执行刷新操作,否则反之。

5. 我们的接口都提供出来了,那就看看我们的实现吧,其实很简单,上代码吧

myAadapter = new MyTestAadapter(this,list);
recyclerView.setAdapter(myAadapter);
recyclerView.setRefresh(true);
recyclerView.onDropDownResfreshListener(new MyRecyclerView.DropDownRefreshListener() {
    @Override
    public void isRefreshing() {
        myAadapter.setText("正在刷新");
        //模拟网络加载数据,这里使用延迟策略
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //加载完成
                recyclerView.hideRefeshLayout();
                myAadapter.setText("下拉刷新");

            }
        }, 4000);
    }

    @Override
    public void noRefresh() {
        recyclerView.hideRefeshLayout();
        myAadapter.setText("下拉刷新");
    }

    @Override
    public void isRefreshTip() {
        myAadapter.setText("松开刷新");
    }

    @Override
    public void noRefreshTip() {
        myAadapter.setText("下拉刷新");
    }

});

recyclerView.setRefresh(true)表示开启下拉刷新,四个回调方法一看就明白了。


上拉加载

上拉加载就比较简单了,

添加一个底部item,跟头部同理,判断是否到达底部,

if (!recyclerView.canScrollVertically(1)){
    pullRefreshListener.isLoading();
}
到达底部则实现回调,下面看看接口的实现:

recyclerView.onPullResfreshListener(new MyRecyclerView.PullRefreshListener() {
    @Override
    public void isLoading() {
        if (!islaod){
            islaod = true;
            myAadapter.setFooterText("正在加载");
            //模拟网络加载数据,这里使用延迟策略
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                   List<String> list = new ArrayList<>();
                    for (int i=0;i<10;i++){
                        list.add("加载内容"+i);
                    }
                    myAadapter.addData(list);
                    islaod = false;
                    //加载完成
                    myAadapter.setFooterText("上拉加载");

                }
            }, 4000);
        }

    }

});

当正在加载时,如果用户继续上拉,就会再次回调isLoading,isLaod是为了防止这个情况的发生。延迟策略是模拟一下数据加载。

下面发一下完整代码:
MyRecyclerView类:

public class MyRecyclerView extends RecyclerView {

    private float y;
    private boolean isTop = false;
    private boolean isRefresh = false;
    private boolean isOpenRefresh = false;

    private int topMargin;
    private int headHeight = 200;

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

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.addOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (recyclerView.canScrollVertically(-1)){
                    isTop = false;
                }else {
                    isTop = true;
                }

                if (!recyclerView.canScrollVertically(1)){
                    pullRefreshListener.isLoading();
                }

            }
        });

        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        y = event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (!isTop) {
                            y = event.getRawY();
                        } else {
                            float mY = event.getRawY();
                            float dY = mY - y;
                            if (dY >= 0 && isOpenRefresh) {
                                int top = (int) (dY + topMargin) >= 0 ? 0 : (int) (dY + topMargin);
                                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
                                layoutParams.setMargins(0, top, 0, 0);
                                setLayoutParams(layoutParams);
                                if (dY >= headHeight) {
                                    isRefresh = true;
                                    dropDownRefreshListener.isRefreshTip();
                                } else {
                                    isRefresh = false;
                                    dropDownRefreshListener.noRefreshTip();
                                }
                                return true;
                            }
                        }

                        break;
                    case MotionEvent.ACTION_UP:
                        if (isTop) {
                            if (isRefresh) {
                                dropDownRefreshListener.isRefreshing();
                            }else {
                                dropDownRefreshListener.noRefresh();
                            }

                        }

                        break;
                }
                return false;
            }
        });
    }

    /**
     * 是否开启下拉刷新
     * @param isOpenRefresh
     */
    public void setRefresh(boolean isOpenRefresh){
        this.isOpenRefresh = isOpenRefresh;
        if (isOpenRefresh){
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)getLayoutParams();
            topMargin = layoutParams.topMargin;
//            Log.e("setRefresh","topMargin="+topMargin);
        }

    }

    /**
     * 隐藏下拉刷新布局
     */
    public void hideRefeshLayout(){
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
        layoutParams.setMargins(0, topMargin, 0, 0);
        setLayoutParams(layoutParams);
        isRefresh = false;
    }


    public void onDropDownResfreshListener(DropDownRefreshListener dropDownRefreshListener){
        this.dropDownRefreshListener = dropDownRefreshListener;
    }

   public interface DropDownRefreshListener{

       /**
        * 当手指离开屏幕时,下拉距离符合加载要求,执行这个方法
        */
        void isRefreshing();

       /**
        * 当手指离开屏幕时,下拉距离不符合加载要求,执行这个方法
        */
       void noRefresh();

       /**
        * 当手指还在屏幕上时,下拉距离符合加载要求,执行这个方法
        */
       void isRefreshTip();

       /**
        * 当手指还在屏幕上时,下拉距离不符合加载要求,执行这个方法
        */
       void noRefreshTip();
    }

    private DropDownRefreshListener dropDownRefreshListener;


    public void onPullResfreshListener(PullRefreshListener pullRefreshListener){
        this.pullRefreshListener = pullRefreshListener;
    }

    public interface PullRefreshListener{

        /**
         * 当上拉距离符合加载要求,执行这个方法
         */
        void isLoading();

    }

    private PullRefreshListener pullRefreshListener;

}


MyTestAadapter类:

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

    private Context context;
    private LayoutInflater inflater;
    private List<String> list;

    public static final int HEAD = 1;
    public static final int DEF_VIEW = 2;
    public static final int FOOTER = 3;

    public MyTestAadapter(Context context, List<String> list){
        this.context = context;
        this.inflater = LayoutInflater.from(context);
        this.list = list;

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder viewHolder = null;
        switch (viewType){
            case DEF_VIEW:
                viewHolder = new DefViewHolder(inflater.inflate(R.layout.recycler_item_def,parent,false));
                break;
            case HEAD:
                viewHolder = new HeadViewHolder(inflater.inflate(R.layout.recycler_item_head,parent,false));
                break;
            case FOOTER:
                viewHolder = new FooterViewHolder(inflater.inflate(R.layout.recycler_item_footer,parent,false));
                break;
        }
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        switch (getItemViewType(position)){
            case DEF_VIEW:
                DefViewHolder defViewHolder = (DefViewHolder) holder;
                defViewHolder.tvContent.setText(list.get(position-1));
                break;
            case HEAD:
                break;
            case FOOTER:
                break;
        }
    }

    @Override
    public int getItemCount() {
        return list.size()==0?0:list.size()+2;
    }

    @Override
    public int getItemViewType(int position) {
        if (position==0){
            return HEAD;
        } if (list.size()>0 && list.size()+1==position){
            return FOOTER;
        }else {
            return DEF_VIEW;
        }

    }

    public void addData(List<String> stringList){
        for (String s : stringList){
            list.add(s);
        }
        notifyItemRangeInserted(getItemCount()-2,stringList.size());
    }

    class DefViewHolder extends RecyclerView.ViewHolder{

        private TextView tvContent;

        public DefViewHolder(View itemView) {
            super(itemView);
            tvContent = (TextView) itemView.findViewById(R.id.tvContent);
        }
    }

    private TextView tvContentHead;
    class HeadViewHolder extends RecyclerView.ViewHolder{


        public HeadViewHolder(View itemView) {
            super(itemView);
            tvContentHead = (TextView) itemView.findViewById(R.id.tvContentHead);
        }
    }

    public void setText(String text){
        tvContentHead.setText(text);
    }


    private TextView tvContentFooter;
    class FooterViewHolder extends RecyclerView.ViewHolder{

        public FooterViewHolder(View itemView) {
            super(itemView);
            tvContentFooter = (TextView) itemView.findViewById(R.id.tvContentFooter);
        }
    }

    public void setFooterText(String text){
        tvContentFooter.setText(text);
    }
}



demo地址:   http://download.csdn.net/download/u012992345/10015294