天天看點

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

繼續閱讀