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