本文重點介紹了SwipeRefreshLayout的使用和自定View繼承SwipeRefreshLayout添加上拉加載更多的功能。
-
介紹之前,先來看一下SwipeRefreshLayout實現的下拉刷新效果圖。從圖中可以看到,下拉到了一定的高度才會進行刷新,高度不夠就會回收上去,正在刷新過程中,繼續下拉沒反應,說明刷新時屏蔽掉了下拉事件。
上拉刷新效果圖
一、SwipeRefreshLayout簡單介紹
-
先看以下官方文檔,已有了很詳細的描述了。
官方文檔說明 -
這里我再大概解釋一下:
在豎直滑動時想要刷新頁面可以用SwipeRefreshLayout來實現。它通過設置OnRefreshListener來監聽界面的滑動從而實現刷新。也可以通過一些方法來設置SwipeRefreshLayout是否可以刷新。如:setRefreshing(true),展開刷新動畫。
setRefreshing(false),取消刷新動畫。setEnable(true)下拉刷新將不可用。使用這個布局要想達到刷新的目的,需要在這個布局里包裹可以滑動的子控件,如ListView等,并且只能有一個子控件。
介紹總結:使用SwipeRefreshLayout可以實現下拉刷新,前提是布局里需要包裹一個可以滑動的子控件,然后在代碼里設置OnRefreshListener設置監聽,最后在監聽里設置刷新時的數據獲取就可以了。由于是新出來的東西,所以要想使用,先把support library的版本升級到19.1或更新。
二、SwipeRefreshLayout主要方法介紹
翻看官方的文檔,可以看到方法有很多,這里只介紹五個經常用到的方法。
-
isRefreshing()
- 判斷當前的狀態是否是刷新狀態。
-
setColorSchemeResources(int... colorResIds)
- 設置下拉進度條的顏色主題,參數為可變參數,并且是資源id,可以設置多種不同的顏色,每轉一圈就顯示一種顏色。
-
setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener)
- 設置監聽,需要重寫onRefresh()方法,頂部下拉時會調用這個方法,在里面實現請求數據的邏輯,設置下拉進度條消失等等。
-
setProgressBackgroundColorSchemeResource(int colorRes)
- 設置下拉進度條的背景顏色,默認白色。
-
setRefreshing(boolean refreshing)
- 設置刷新狀態,true表示正在刷新,false表示取消刷新。
三、SwipeRefreshLayout的基本使用
- 介紹了SwipeRefreshLayout,主要的方法也講了,接下來就是實戰,其實使用起來非常的簡單。
3.1 設置布局
-
官方文檔已經說明,SwipeRefreshLayout只能有一個孩子,當然我們不般也不會往里面放其他的布局。我們只需要在容器里包裹一個ListView就好了。
<ListView android:id="@+id/lv" android:layout_width="match_parent" android:layout_height="match_parent"/> </android.support.v4.widget.SwipeRefreshLayout> -->
3.2 在代碼中使用
-
在該布局文件對應的Activity或其他類中獲取布局id,先設置ListView顯示的適配器,然后再設置SwipeRefreshLayout。
// 不能在onCreate中設置,這個表示當前是刷新狀態,如果一進來就是刷新狀態,SwipeRefreshLayout會屏蔽掉下拉事件
//swipeRefreshLayout.setRefreshing(true);// 設置顏色屬性的時候一定要注意是引用了資源文件還是直接設置16進制的顏色,因為都是int值容易搞混 // 設置下拉進度的背景顏色,默認就是白色的 swipeRefreshView.setProgressBackgroundColorSchemeResource(android.R.color.white); // 設置下拉進度的主題顏色 swipeRefreshView.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark); // 下拉時觸發SwipeRefreshLayout的下拉動畫,動畫完畢之后就會回調這個方法 swipeRefreshView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { // 開始刷新,設置當前為刷新狀態 //swipeRefreshLayout.setRefreshing(true); // 這里是主線程 // 一些比較耗時的操作,比如聯網獲取數據,需要放到子線程去執行 // TODO 獲取數據 final Random random = new Random(); new Handler().postDelayed(new Runnable() { @Override public void run() { mList.add(0, "我是天才" + random.nextInt(100) + "號"); mAdapter.notifyDataSetChanged(); Toast.makeText(MainActivity.this, "刷新了一條數據", Toast.LENGTH_SHORT).show(); // 加載完數據設置為不刷新狀態,將下拉進度收起來 swipeRefreshView.setRefreshing(false); } }, 1200); // System.out.println(Thread.currentThread().getName()); // 這個不能寫在外邊,不然會直接收起來 //swipeRefreshLayout.setRefreshing(false); } });
經過以上兩步簡單的設置就能使用SwipeRefreshLayout了。
四、自定義View繼承SwipeRefreshLayout,添加上拉加載更多功能
由于谷歌并沒有提供上拉加載更多的布局,所以我們只能自己去定義布局實現這個功能。
這里通過自定義View繼承SwipeRefreshLayout容器,然后添加上拉加載更多的功能。
-
先來看一下上拉加載更多的效果圖
上拉加載更多效果圖
4.1 定義View繼承SwipeRefreshLayout,添加上拉加載功能
代碼中的注釋比較詳細,這里就不一一解釋了,說一下大概的實現思路,主要分為四步。
4.1.1 獲取子控件ListView
-
在布局使用中,這里和SwipeRefreshLayout一樣,ListView是SwipeRefreshView的子控件,所以需要在onLayout()方法中獲取子控件ListView。
// 獲取ListView,設置ListView的布局位置
if (mListView == null) {
// 判斷容器有多少個孩子
if (getChildCount() > 0) {
// 判斷第一個孩子是不是ListView
if (getChildAt(0) instanceof ListView) {
// 創建ListView對象
mListView = (ListView) getChildAt(0);// 設置ListView的滑動監聽 setListViewOnScroll(); } } }
4.1.2 對ListView設置滑動監聽
-
監聽ListView的滑動事件,當滑動到底部,并且當前可見頁的最后一個條目等于adapter的getCount數目-1,就滿足加載數據的條件。
/**
* 設置ListView的滑動監聽
*/
private void setListViewOnScroll() {mListView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 移動過程中判斷時候能下拉加載更多 if (canLoadMore()) { // 加載數據 loadData(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }); }
4.1.3 處理SwipeRefreshView容器的分發事件
-
由于ListView是SwipeRefreshView的子控件,所以這里要進行事件的分發處理,判斷用戶的滑動距離是否滿足條件。
/**
* 在分發事件的時候處理子控件的觸摸事件
*
* @param ev
* @return
*/
private float mDownY, mUpY;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // 移動的起點 mDownY = ev.getY(); break; case MotionEvent.ACTION_MOVE: // 移動過程中判斷時候能下拉加載更多 if (canLoadMore()) { // 加載數據 loadData(); } break; case MotionEvent.ACTION_UP: // 移動的終點 mUpY = getY(); break; } return super.dispatchTouchEvent(ev); }
4.1.4 判斷條件,滿足就用回調去加載數據
-
當滿足了需要判斷的所有的條件之后,就可以去調用加載數據的方法,這里提供一個設置上拉布局顯示和隱藏的方法,通過傳入當前的狀態,是true就顯示加載,是false就隱藏。
/**
* 判斷是否滿足加載更多條件
*
* @return
*/
private boolean canLoadMore() {
// 1. 是上拉狀態
boolean condition1 = (mDownY - mUpY) >= mScaledTouchSlop;
if (condition1) {
System.out.println("是上拉狀態");
}// 2. 當前頁面可見的item是最后一個條目 boolean condition2 = false; if (mListView != null && mListView.getAdapter() != null) { condition2 = mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() - 1); } if (condition2) { System.out.println("是最后一個條目"); } // 3. 正在加載狀態 boolean condition3 = !isLoading; if (condition3) { System.out.println("不是正在加載狀態"); } return condition1 && condition2 && condition3; } /** * 處理加載數據的邏輯 */ private void loadData() { System.out.println("加載數據..."); if (mOnLoadListener != null) { // 設置加載狀態,讓布局顯示出來 setLoading(true); mOnLoadListener.onLoad(); } } /** * 設置加載狀態,是否加載傳入boolean值進行判斷 * * @param loading */ public void setLoading(boolean loading) { // 修改當前的狀態 isLoading = loading; if (isLoading) { // 顯示布局 mListView.addFooterView(mFooterView); } else { // 隱藏布局 mListView.removeFooterView(mFooterView); // 重置滑動的坐標 mDownY = 0; mUpY = 0; } }
4.2 使用自定義View
4.2.1. 書寫布局
-
因為是繼承自SwipeRefreshLayout,所以SwipeRefreshView也只能有一個孩子
<!--自定義View實現SwipeRefreshLayout,添加上拉加載更多的功能--> <com.pinger.swiperefreshdemo.view.SwipeRefreshView android:id="@+id/srl" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/lv" android:layout_width="match_parent" android:layout_height="match_parent"/> </com.pinger.swiperefreshdemo.view.SwipeRefreshView>
4.2.2. 在代碼中使用
-
在代碼中使用更加的簡單,只需要設置監聽重寫onLoad()方法,在里面加載數據,加載完數據然后設置為不加載狀態就可以了。
// 設置下拉加載更多 swipeRefreshView.setOnLoadListener(new SwipeRefreshView.OnLoadListener() { @Override public void onLoad() { new Handler().postDelayed(new Runnable() { @Override public void run() { // 添加數據 for (int i = 30; i < 35; i++) { mList.add("我是天才" + i+ "號"); // 這里要放在里面刷新,放在外面會導致刷新的進度條卡住 mAdapter.notifyDataSetChanged(); } Toast.makeText(MainActivity.this, "加載了" + 5 + "條數據", Toast.LENGTH_SHORT).show(); // 加載完數據設置為不加載狀態,將加載進度收起來 swipeRefreshView.setLoading(false); } }, 1200); } });