1. 程式人生 > >SwipeRefreshLayout+Recyclerview的重新整理載入封裝

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>