SwipeRefreshLayout+Recyclerview的重新整理載入封裝
目錄
2)onlayout拿到RecyclerView,設定載入更多的監聽
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>