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;
    }

這樣,一個完整的下拉刷新與上拉加載就已經完成了,希望看完本篇文章能對你有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章