Android-PullToRefresh下拉刷新庫基本用法


 

How:(使用)

轉自:http://blog.csdn.net/hantangsongming/article/details/42490277

 

PullToRefresh是一套實現非常好的下拉刷新庫,它支持:
ListView
ExpandableListView
GridView
WebView
ScrollView
HorizontalScrollView
ViewPager
等多種常用的需要刷新的View類型,而且使用起來也十分方便。
(下載地址:https://github.com/chrisbanes/Android-PullToRefresh

PullToRefresh基本用法(步驟):

一、繼承OnClickListener實現onRefresh()方法

1、在布局文件中添加PullToRefresh控件,比如PullToRefreshListView;

2、用PullToRefresh控件的setMode()方法設置PullToRefresh控件的Mode(包括 Mode.BOTH、Mode.PULL_FROM_START、Mode.PULL_FROM_END等);

3、在Activity中,設置監聽器OnRefreshListener以響應用戶下拉操作;
4、在監聽器的onRefresh()方法中通過PullToRefresh控件的getCurrentMode()方法來獲取當前“拉”操作的Mode,區分是“上拉”還是“下拉”,並各自執行自己的數據刷新操作,可以通過AsyncTask來實現;

  注:getMode()方法返回的是setMode()時的字符串值,getCurrentMode()返回的是實際操作時的Mode類型。各種Mode的值是字符串而不是數值,進行比較時必須注意。

5、在AsyncTask中獲取到數據后,記得調用onRefreshComplete()方法通知PullToRefresh控件數據已獲取完畢,可以結束刷新操作。

 

二、繼承OnClickListener2實現onPullUpToRefresh()和onPullDownToRefresh()方法

1、同上;

2、用setMode(Mode.BOTH)方法設置PullToRefresh控件的Mode為“上拉”和“下拉”均可;

3、在Activity中,設置監聽器OnRefreshListener2以響應用戶下拉操作;

4、在監聽器的onPullUpToRefresh()和onPullDownToRefresh()方法中分別執行“上拉”和“下拉”各自的操作,可通過AsyncTask來實現。

5、同上。

 

附加:

ListView顯示哪個Item通過如下方法

chatRoomAdapter.notifyDataSetChanged();
listView.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);    //數據變動則自動回到底部
listView.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_DISABLED);   //數據變動不回到底部
listView.setSelection(group.size());  //顯示第幾個Item。注:數據變動必須不會自動回到底部才有效。

注意:setSelection()方法必須寫在ListView設置適配器並提示數據改變后
注:ListView是從上到下從1開始排序的。而數據項插入ListView是從下往上頂的。

改變ListView中數據的整體位置是在頂部還是底部setStackFromBottom(Boolean)

改變ListView中的數據項的順序只能改變適配器中添加數據的順序或集合中數據的順序。如:

 

import  java.util.ArrayList;
import  java.util.Collections;
import  java.util.Comparator;
 
public  class  T {
   public  static  void  main(String[] args) {
     ArrayList list =  new  ArrayList();
     list.add( "92.8" );
     list.add( "68.9" );
     list.add( "168.61" );
     list.add( "242" );
     list.add( "317" );
     list.add( "105" );
     // 字符串排序
     Collections.sort(list);
     System.out.println(list.toString());  // [105, 168.61, 242, 317, 68.9, 92.8]
     Collections.sort(list,  new  Comparator() {
       @Override
       public  int  compare(Object o1, Object o2) {
         return  new  Double((String) o1).compareTo( new  Double((String) o2));
       }
     });
     System.out.println(list.toString());  // [68.9, 92.8, 105, 168.61, 242, 317]
   }
}  

 

 

Why:(源碼、類圖)

http://www.2cto.com/kf/201504/387623.html

 

PullToRefresh 這個庫用的是非常至多,github 今天主要分析一下源碼實現.

我們通過ListView的下拉刷新進行分析,其它的類似。

整個下拉刷新 父View是LinearLayout, 在LinearLayout添加了Header View ,Footer View,和ListView

PullToRefreshBase 是父類 擴展了 LinearLayout水平布局 如果我們使用ListView 需要觀看子類 PullToRefreshAdapterViewBase -> PullToRefreshListView

\

初始化代碼在PullToRefreshBase init方法中

重點代碼:

 

		// Refreshable View
		// By passing the attrs, we can add ListView/GridView params via XML
		mRefreshableView = createRefreshableView(context, attrs);//通過子類傳入的View,ListView或者ScrollView等
		addRefreshableView(context, mRefreshableView);//添加view到布局中

		// We need to create now layouts now  創建Header和Footer視圖,默認是INVISIBLE,要添加到父窗口
		mHeaderLayout = createLoadingLayout(context, Mode.PULL_FROM_START, a);
		mFooterLayout = createLoadingLayout(context, Mode.PULL_FROM_END, a);


		handleStyledAttributes(a);//添加loadingView效果,這里是把View添加到ListView HeaderView里面去
		updateUIForMode(); //把布局添加到父View中

 

 protected void updateUIForMode() {
		final LinearLayout.LayoutParams lp = getLoadingLayoutLayoutParams();
		// Remove Header, and then add Header Loading View again if needed
		if (this == mHeaderLayout.getParent()) {
			removeView(mHeaderLayout);
		}
		if (mMode.showHeaderLoadingLayout()) {
			addViewInternal(mHeaderLayout, 0, lp);//加入View到LinearLayout
		}

		// Remove Footer, and then add Footer Loading View again if needed
		if (this == mFooterLayout.getParent()) {
			removeView(mFooterLayout);
		}
		if (mMode.showFooterLoadingLayout()) {
			addViewInternal(mFooterLayout, lp);//加入View到LinearLayout
		}

		// Hide Loading Views
		refreshLoadingViewsSize();//把headerView隱藏起來,其實用的是padding的方式 設置為負值 就到屏幕頂部的外面了 

		// If we're not using Mode.BOTH, set mCurrentMode to mMode, otherwise
		// set it to pull down
		mCurrentMode = (mMode != Mode.BOTH) ? mMode : Mode.PULL_FROM_START;
	}


//這里有2個LoadingView,一個是加入到LinearLayout中去了,還有一個是加入到ListView本身的Header里面

 

看看handleStyledAttributes方法 定位到子類復寫的地方

FrameLayout frame = new FrameLayout(getContext());
mHeaderLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_START, a);
mHeaderLoadingView.setVisibility(View.GONE);
frame.addView(mHeaderLoadingView, lp);
mRefreshableView.addHeaderView(frame, null, false);//添加LoadingView到ListView Header上

//headerView一共有2個LoadingView,一個是被加入到LinearLayout一個是被加入到ListView的HeaderView
addViewInternal方法就是加入到LinearLayout父類中


看看LoadingLayout 有2種 FlipLoadingLayout 和 RotateLoadingLayout 一般我們用旋轉的加載動畫
左邊一個旋轉圖片,右邊是文字和時間提示

第一個LoadingLayout主要顯示 :下拉刷新,放開以刷新
第二個LoadingLayout顯示松手后的文字:正在載入...

結構是這樣    (注:下圖似乎有點問題

\

 

當UI初始化好,下面看看onTouch 下拉捕獲事件

 

 

	public final boolean onTouchEvent(MotionEvent event) {
		if (!isPullToRefreshEnabled()) {
			return false;
		}
		// If we're refreshing, and the flag is set. Eat the event
		if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
			return true;
		}
		if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
			return false;
		}
		switch (event.getAction()) {
			case MotionEvent.ACTION_MOVE: {
				if (mIsBeingDragged) {
					mLastMotionY = event.getY();
					mLastMotionX = event.getX();
					pullEvent();//開始下拉,移動 
					return true;
				}
				break;
			}

			case MotionEvent.ACTION_DOWN: {
				if (isReadyForPull()) {//按下 開始下拉
					mLastMotionY = mInitialMotionY = event.getY();
					mLastMotionX = mInitialMotionX = event.getX();
					return true;
				}
				break;
			}

			case MotionEvent.ACTION_CANCEL:
			case MotionEvent.ACTION_UP: { //停止下拉的時候
				if (mIsBeingDragged) {
					mIsBeingDragged = false;
					if (mState == State.RELEASE_TO_REFRESH
							&& (null != mOnRefreshListener || null != mOnRefreshListener2)) {
						setState(State.REFRESHING, true);//放下手指開始回調,執行我們的回調任務
						return true;
					}

					// If we're already refreshing, just scroll back to the top
					if (isRefreshing()) {
						smoothScrollTo(0);
						return true;
					}

					// If we haven't returned by here, then we're not in a state
					// to pull, so just reset
					setState(State.RESET); //恢復到原來的UI狀態

					return true;
				}
				break;
			}
		}

		return false;
	}



 

 

看看pullEvent方法
	private void pullEvent() {
		final int newScrollValue;
		final int itemDimension;
		final float initialMotionValue, lastMotionValue;

		switch (getPullToRefreshScrollDirection()) {
			case HORIZONTAL:
				initialMotionValue = mInitialMotionX;
				lastMotionValue = mLastMotionX;
				break;
			case VERTICAL:
			default:
				initialMotionValue = mInitialMotionY;
				lastMotionValue = mLastMotionY;
				break;
		}
		//計算下拉移動了多少
		switch (mCurrentMode) {
			case PULL_FROM_END://上拉
				newScrollValue = Math.round(Math.max(initialMotionValue - lastMotionValue, 0) / FRICTION);
				itemDimension = getFooterSize();
				break;
			case PULL_FROM_START://下拉
			default:
				newScrollValue = Math.round(Math.min(initialMotionValue - lastMotionValue, 0) / FRICTION);
				itemDimension = getHeaderSize();
				break;
		}

		//顯示HeaderView 得到移動的值,可以讓LoadingView顯示出來
		setHeaderScroll(newScrollValue);

		if (newScrollValue != 0 && !isRefreshing()) {
			float scale = Math.abs(newScrollValue) / (float) itemDimension;
			switch (mCurrentMode) {
				case PULL_FROM_END:
					mFooterLayout.onPull(scale);
					break;
				case PULL_FROM_START:
				default:
				 mHeaderLayout.onPull(scale);//旋轉左邊的加載圖片,顯示文字和圖片 這個地方最終會執行LoadingLayout中的 onPullImpl方法
					break;
			}
			//更新狀態 包括2種 下拉刷新:拉伸距離不足需要繼續拉伸的狀態;松手刷新:拉伸距離已經足夠可以釋放的狀態。
			if (mState != State.PULL_TO_REFRESH && itemDimension >= Math.abs(newScrollValue)) {
				setState(State.PULL_TO_REFRESH);
			} else if (mState == State.PULL_TO_REFRESH && itemDimension < Math.abs(newScrollValue)) {
				setState(State.RELEASE_TO_REFRESH);//下拉松手 可以松手了
			}
		}
	}



 

 

 

再看看setHeaderScroll方法代碼
	protected final void setHeaderScroll(int value) {
		if (DEBUG) {
			Log.d(LOG_TAG, "setHeaderScroll: " + value);
		}
		
		if (DEBUG) {
			Log.d(LOG_TAG, "setHeaderScroll:" + value );
		}

		// Clamp value to with pull scroll range
		final int maximumPullScroll = getMaximumPullScroll();
		value = Math.min(maximumPullScroll, Math.max(-maximumPullScroll, value));

		if (mLayoutVisibilityChangesEnabled) {
			if (value < 0) { //有位移才顯示
				mHeaderLayout.setVisibility(View.VISIBLE);
			} else if (value > 0) {  //有位移才顯示
				mFooterLayout.setVisibility(View.VISIBLE);
			} else { 
				mHeaderLayout.setVisibility(View.INVISIBLE);
				mFooterLayout.setVisibility(View.INVISIBLE);
			}
		}

		if (USE_HW_LAYERS) {
			/**
			 * Use a Hardware Layer on the Refreshable View if we've scrolled at
			 * all. We don't use them on the Header/Footer Views as they change
			 * often, which would negate any HW layer performance boost.
			 */
			ViewCompat.setLayerType(mRefreshableViewWrapper, value != 0 ? View.LAYER_TYPE_HARDWARE
					: View.LAYER_TYPE_NONE);
		}

		//回到最原始的scrollTo 最常用的 移動布局
		switch (getPullToRefreshScrollDirection()) {
			case VERTICAL:
				scrollTo(0, value);
				break;
			case HORIZONTAL:
				scrollTo(value, 0);
				break;
		}
	}


setState(State.REFRESHING, true);//拉倒最頂部 松手,會執行onRefreshing方法,回調我們實現的任務接口 也就是OnRefreshListener

 

 

 

protected void onRefreshing(final boolean doScroll) {
		if (mMode.showHeaderLoadingLayout()) {
			mHeaderLayout.refreshing();
		}
		if (mMode.showFooterLoadingLayout()) {
			mFooterLayout.refreshing();
		}

		if (doScroll) {
			if (mShowViewWhileRefreshing) {

				// Call Refresh Listener when the Scroll has finished
				OnSmoothScrollFinishedListener listener = new OnSmoothScrollFinishedListener() {
					@Override
					public void onSmoothScrollFinished() {
						callRefreshListener();//回調接口執行
					}
				};

				switch (mCurrentMode) {
					case MANUAL_REFRESH_ONLY:
					case PULL_FROM_END:
						smoothScrollTo(getFooterSize(), listener);
						break;
					default:
					case PULL_FROM_START:
						smoothScrollTo(-getHeaderSize(), listener);
						break;
				}
			} else {
				smoothScrollTo(0);//回到原來的位置
			}
		} else {
			// We're not scrolling, so just call Refresh Listener now
			callRefreshListener();//回調接口執行
		}
	}



 

 

 

	private void callRefreshListener() {
		if (null != mOnRefreshListener) {
			mOnRefreshListener.onRefresh(this);//回調
		} else if (null != mOnRefreshListener2) { //這個是上拉,下拉都可以的情況,使用 onRefreshListener2
			if (mCurrentMode == Mode.PULL_FROM_START) {
				mOnRefreshListener2.onPullDownToRefresh(this);
			} else if (mCurrentMode == Mode.PULL_FROM_END) {
				mOnRefreshListener2.onPullUpToRefresh(this);
			}
		}
	}


總結:狀態包括下拉刷新松手刷新正在刷新Loading隱藏。移動UI還是用的scrollTo最基本的代碼. 動畫部分可以看LoadingLayout的2個子類

 

主要的就這些,還有很多細節沒有分析。若有問題請指出謝謝。

 

 

 

Android 下拉刷新框架實現 (自定義的下拉刷新)   !!!未看!!!

轉自:http://blog.csdn.net/leehong2005/article/details/12567757

 

前段時間項目中用到了下拉刷新功能,之前在網上也找到過類似的demo,但這些demo的質量參差不齊,用戶體驗也不好,接口設計也不行。最張沒辦法,終於忍不了了,自己就寫了一個下拉刷新的框架,這個框架是一個通用的框架,效果和設計感覺都還不錯,現在分享給各位看官。

致謝:

1. 感謝lk6233160同學提出的問題,旋轉View時調用setRotation方法只能是在API Level11(3.0)以上才能用,這個問題的解決辦法是給ImageView設置一個Matrix,把Matrix上面作用一個旋轉矩陣,但是如果不是ImageView的話,可能實現起來比較麻煩,再次謝謝lk6233160同學。

2. 謝謝如毛毛風提出的問題,向下滑動后,再向上滑動到頭,只能再松手后才能再次下拉。這個問題的回復請參考評論。

技術交流群:

QQ:197990971(人員已滿)

 

1. 關於下拉刷新

下拉刷新這種用戶交互最早由twitter創始人洛倫•布里切特(Loren Brichter)發明, 有理論認為,下拉刷新是一種適用於按照從新到舊的時間順序排列feeds的應用,在這種應用場景中看完舊的內容時,用戶會很自然地下拉查找更新的內容,因此下拉刷新就顯得非常合理。大家可以參考這篇文章: 有趣的下拉刷新,下面我貼出一個有趣的下拉刷新的案例。
圖一、有趣的下拉刷新案例(一)
 
圖一、有趣的下拉刷新案例(二)
 
 
 

2. 實現原理

上面這些例子,外觀做得再好看,他的本質上都一樣,那就是一個下拉刷新控件通常由以下幾部分組成:
【1】Header
Header通常有下拉箭頭,文字,進度條等元素,根據下拉的距離來改變它的狀態,從而顯示不同的樣式
【2】Content
這部分是內容區域,網上有很多例子都是直接在ListView里面添加Header,但這就有局限性,因為好多情況下並不一定是用ListView來顯示數據。我們把要顯示內容的View放置在我們的一個容器中,如果你想實現一個用ListView顯示數據的下拉刷新,你需要創建一個ListView旋轉到我的容器中。我們處理這個容器的事件(down, move, up),如果向下拉,則把整個布局向下滑動,從而把header顯示出來。
【3】Footer
Footer可以用來顯示向上拉的箭頭,自動加載更多的進度條等。
 
以上三部分總結的說來,就是如下圖所示的這種布局結構:
圖三,下拉刷新的布局結構
 
關於上圖,需要說明幾點:
1、這個布局擴展於 LinearLayout,垂直排列
2、從上到下的順序是:Header, Content, Footer
3、Content填充滿父控件,通過設置top, bottom的padding來使Header和Footer不可見,也就是讓它超出屏幕外
4、下拉時,調用scrollTo方法來將整個布局向下滑動,從而把Header顯示出來,上拉正好與下拉相反。
5、派生類需要實現的是:將Content View填充到父容器中,比如,如果你要使用的話,那么你需要把ListView, ScrollView, WebView等添加到容器中。
6、上圖中的紅色區域就是屏的大小(嚴格來說,這里說屏幕大小並不准確,應該說成內容區域更加准確)
 
 

3. 具體實現

明白了實現原理與過程,我們嘗試來具體實現,首先,為了以后更好地擴展,設計更加合理,我們把下拉刷新的功能抽象成一個接口:

1、IPullToRefresh<T extends View>

它具體的定義方法如下:
 
[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public interface IPullToRefresh<T extends View> {  
  2.     public void setPullRefreshEnabled(boolean pullRefreshEnabled);  
  3.     public void setPullLoadEnabled(boolean pullLoadEnabled);  
  4.     public void setScrollLoadEnabled(boolean scrollLoadEnabled);  
  5.     public boolean isPullRefreshEnabled();  
  6.     public boolean isPullLoadEnabled();  
  7.     public boolean isScrollLoadEnabled();  
  8.     public void setOnRefreshListener(OnRefreshListener<T> refreshListener);  
  9.     public void onPullDownRefreshComplete();  
  10.     public void onPullUpRefreshComplete();  
  11.     public T getRefreshableView();  
  12.     public LoadingLayout getHeaderLoadingLayout();  
  13.     public LoadingLayout getFooterLoadingLayout();  
  14.     public void setLastUpdatedLabel(CharSequence label);  
  15. }  

 

這個接口是一個泛型的,它接受View的派生類, 因為要放到我們的容器中的不就是一個View嗎?

2、PullToRefreshBase<T extends View>
這個類實現了IPullToRefresh接口,它是從LinearLayout繼承過來,作為下拉刷新的一個 抽象基類,如果你想實現ListView的下拉刷新,只需要擴展這個類,實現一些必要的方法就可以了。這個類的職責主要有以下幾點:
  • 處理onInterceptTouchEvent()和onTouchEvent()中的事件:當內容的View(比如ListView)正如處於最頂部,此時再向下拉,我們必須截斷事件,然后move事件就會把后續的事件傳遞到onTouchEvent()方法中,然后再在這個方法中,我們根據move的距離再進行scroll整個View。
  • 負責創建Header、Footer和Content View:在構造方法中調用方法去創建這三個部分的View,派生類可以重寫這些方法,以提供不同式樣的Header和Footer,它會調用createHeaderLoadingLayout和createFooterLoadingLayout方法來創建Header和Footer創建Content View的方法是一個抽象方法,必須讓派生類來實現,返回一個非null的View,然后容器再把這個View添加到自己里面。
  • 設置各種狀態:這里面有很多狀態,如下拉、上拉、刷新、加載中、釋放等,它會根據用戶拉動的距離來更改狀態,狀態的改變,它也會把Header和Footer的狀態改變,然后Header和Footer會根據狀態去顯示相應的界面式樣。
3、PullToRefreshBase<T extends View>繼承關系
這里我實現了三個下拉刷新的派生類,分別是ListView、ScrollView、WebView三個,它們的繼承關系如下:
圖四、PullToRefreshBase類的繼承關系
 
關於PullToRefreshBase類及其派和類,有幾點需要說明:
  • 對於ListView,ScrollView,WebView這三種情況,他們是否滑動到最頂部或是最底部的實現是不一樣的,所以,在PullToRefreshBase類中需要調用兩個抽象方法來判斷當前的位置是否在頂部或底部,而其派生類必須要實現這兩個方法。比如對於ListView,它滑動到最頂部的條件就是第一個child完全可見並且first postion是0。這兩個抽象方法是:
 
[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * 判斷刷新的View是否滑動到頂部 
  3.  *  
  4.  * @return true表示已經滑動到頂部,否則false 
  5.  */  
  6. protected abstract boolean isReadyForPullDown();  
  7.   
  8. /** 
  9.  * 判斷刷新的View是否滑動到底 
  10.  *  
  11.  * @return true表示已經滑動到底部,否則false 
  12.  */  
  13. protected abstract boolean isReadyForPullUp();  
  • 創建可下拉刷新的View(也就是content view)的抽象方法是
[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * 創建可以刷新的View 
  3.  *  
  4.  * @param context context 
  5.  * @param attrs 屬性 
  6.  * @return View 
  7.  */  
  8. protected abstract T createRefreshableView(Context context, AttributeSet attrs);  

 

4、LoadingLayout
LoadingLayout是刷新Layout的一個抽象,它是一個抽象基類。Header和Footer都擴展於這個類。這類抽象類,提供了兩個抽象方法:
  • getContentSize
這個方法返回當前這個刷新Layout的大小,通常返回的是布局的高度,為了以后可以擴展為水平拉動,所以方法名字沒有取成getLayoutHeight()之類的,這個返回值,將會作為松手后是否可以刷新的臨界值,如果下拉的偏移值大於這個值,就認為可以刷新,否則不刷新,這個方法必須由派生類來實現。
  • setState
這個方法用來設置當前刷新Layout的狀態,PullToRefreshBase類會調用這個方法,當進入下拉,松手等動作時,都會調用這個方法,派生類里面只需要根據這些狀態實現不同的界面顯示,如下拉狀態時,就顯示出箭頭,刷新狀態時,就顯示loading的圖標。
可能的狀態值有: RESET, PULL_TO_REFRESH, RELEASE_TO_REFRESH, REFRESHING, NO_MORE_DATA
LoadingLayout及其派生類的繼承關系如下圖所示:
圖五、LoadingLayout及其派生類的類圖
 
我們可以隨意地制定自己的Header和Footer,我們也可以實現如圖一和圖二中顯示的各種下拉刷新案例中的Header和Footer,只要重寫上述兩個方法getContentSize()和setState()就行了。HeaderLoadingLayout,它默認是顯示箭頭式樣的布局,而RotateLoadingLayout則是顯示一個旋轉圖標的式樣。
 
5、事件處理
我們必須重寫PullToRefreshBase類的兩個事件相關的方法 onInterceptTouchEvent()和onTouchEvent()方法。由於ListView,ScrollView,WebView它們是放到PullToRefreshBase內部的,所在事件先是傳遞到PullToRefreshBase#onInterceptTouchEvent()方法中,所以我們應該在這個方法中去處理ACTION_MOVE事件,判斷如果當前ListView,ScrollView,WebView是否在最頂部或最底部,如果是,則開始截斷事件,一旦事件被截斷,后續的事件就會傳遞到PullToRefreshBase#onInterceptTouchEvent()方法中,我們再在ACTION_MOVE事件中去移動整個布局,從而實現下拉或上拉動作。
 
6、滾動布局(scrollTo)
如圖三的布局結構可知,默認情況下Header和Footer是放置在Content View的最上面和最下面,通過設置padding來讓他跑到屏幕外面去了,如果我們將整個布局向下滾動(scrollTo)一定距離,那么Header就會被顯示出來,基於這種情況,所以在我的實現中,最終我是調用 scrollTo來實現下拉動作的。
 
總的說來,實現的重要的點就這些,具體的一些細節在實現在會碰到很多,可以參考代碼。
 

4. 如何使用

使用下拉刷新的代碼如下
 
[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2.     public void onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.           
  5.         mPullListView = new PullToRefreshListView(this);  
  6.         setContentView(mPullListView);  
  7.           
  8.         // 上拉加載不可用  
  9.         mPullListView.setPullLoadEnabled(false);  
  10.         // 滾動到底自動加載可用  
  11.         mPullListView.setScrollLoadEnabled(true);  
  12.           
  13.         mCurIndex = mLoadDataCount;  
  14.         mListItems = new LinkedList<String>();  
  15.         mListItems.addAll(Arrays.asList(mStrings).subList(0, mCurIndex));  
  16.         mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListItems);  
  17.           
  18.         // 得到實際的ListView  
  19.         mListView = mPullListView.getRefreshableView();  
  20.         // 綁定數據  
  21.         mListView.setAdapter(mAdapter);         
  22.         // 設置下拉刷新的listener  
  23.         mPullListView.setOnRefreshListener(new OnRefreshListener<ListView>() {  
  24.             @Override  
  25.             public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {  
  26.                 mIsStart = true;  
  27.                 new GetDataTask().execute();  
  28.             }  
  29.   
  30.             @Override  
  31.             public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {  
  32.                 mIsStart = false;  
  33.                 new GetDataTask().execute();  
  34.             }  
  35.         });  
  36.         setLastUpdateTime();  
  37.           
  38.         // 自動刷新  
  39.         mPullListView.doPullRefreshing(true, 500);  
  40.     }  

 

這是初始化一個下拉刷新的布局,並且調用setContentView來設置到Activity中。
在下拉刷新完成后,我們可以調用 onPullDownRefreshComplete()和onPullUpRefreshComplete()方法來停止刷新和加載
 

5. 運行效果

這里列出了demo的運行效果圖。
圖六、ListView下拉刷新,注意Header和Footer的樣式
 
圖七、WebView和ScrollView的下拉刷新效果圖
 
 

6. 源碼下載

實現這個下拉刷新的框架,並不是我的原創,我也是參考了很多開源的,把我認為比較好的東西借鑒過來,從而形成我的東西,我主要是參考了下面這個demo:
https://github.com/chrisbanes/Android-PullToRefresh 這個demo寫得不錯,不過他這個太復雜了,我們都知道,一旦復雜了,萬一我們要添加一些需要,自然也要費勁一些,我其實就是把他的簡化再簡化,以滿足我們自己的需要。
 
 
轉載請說明出處
謝謝!!!
 
 

7. Bug修復

 
已知bug修復情況如下,發現了代碼bug的看官也可以給我反饋,謝謝~~~
 
1,對於ListView的下拉刷新,當啟用滾動到底自動加載時,如果footer由隱藏變為顯示時,出現顯示異常的情況
這個問題已經修復了,修正的代碼如下:
  • PullToRefreshListView#setScrollLoadEnabled方法,修正后的代碼如下:
 
[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. public void setScrollLoadEnabled(boolean scrollLoadEnabled) {  
  3.     if (isScrollLoadEnabled() == scrollLoadEnabled) {  
  4.         return;  
  5.     }  
  6.       
  7.     super.setScrollLoadEnabled(scrollLoadEnabled);  
  8.       
  9.     if (scrollLoadEnabled) {  
  10.         // 設置Footer  
  11.         if (null == mLoadMoreFooterLayout) {  
  12.             mLoadMoreFooterLayout = new FooterLoadingLayout(getContext());  
  13.             mListView.addFooterView(mLoadMoreFooterLayout, null, false);  
  14.         }  
  15.           
  16.         mLoadMoreFooterLayout.show(true);  
  17.     } else {  
  18.         if (null != mLoadMoreFooterLayout) {  
  19.             mLoadMoreFooterLayout.show(false);  
  20.         }  
  21.     }  
  22. }  

LoadingLayout#show方法,修正后的代碼如下:

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * 顯示或隱藏這個布局 
  3.  *  
  4.  * @param show flag 
  5.  */  
  6. public void show(boolean show) {  
  7.     // If is showing, do nothing.  
  8.     if (show == (View.VISIBLE == getVisibility())) {  
  9.         return;  
  10.     }  
  11.       
  12.     ViewGroup.LayoutParams params = mContainer.getLayoutParams();  
  13.     if (null != params) {  
  14.         if (show) {  
  15.             params.height = ViewGroup.LayoutParams.WRAP_CONTENT;  
  16.         } else {  
  17.             params.height = 0;  
  18.         }  
  19.           
  20.         requestLayout();  
  21.         setVisibility(show ? View.VISIBLE : View.INVISIBLE);  
  22.     }  
  23. }  

在更改LayoutParameter后,調用requestLayout()方法。

  • 圖片旋轉兼容2.x系統
我之前想的是這個只需要兼容3.x以上的系統,但發現有很多網友在使用過程中遇到過兼容性問題,這次抽空將這個兼容性一並實現了。
       onPull的修改如下:
 
[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. public void onPull(float scale) {  
  3.     if (null == mRotationHelper) {  
  4.         mRotationHelper = new ImageViewRotationHelper(mArrowImageView);  
  5.     }  
  6.       
  7.     float angle = scale * 180f; // SUPPRESS CHECKSTYLE  
  8.     mRotationHelper.setRotation(angle);  
  9. }  

 ImageViewRotationHelper主要的作用就是實現了ImageView的旋轉功能,內部作了版本的區分,實現代碼如下:

 

  1. /** 
  2.      * The image view rotation helper 
  3.      *  
  4.      * @author lihong06 
  5.      * @since 2014-5-2 
  6.      */  
  7.     static class ImageViewRotationHelper {  
  8.         /** The imageview */  
  9.         private final ImageView mImageView;  
  10.         /** The matrix */  
  11.         private Matrix mMatrix;  
  12.         /** Pivot X */  
  13.         private float mRotationPivotX;  
  14.         /** Pivot Y */  
  15.         private float mRotationPivotY;  
  16.           
  17.         /** 
  18.          * The constructor method. 
  19.          *  
  20.          * @param imageView the image view 
  21.          */  
  22.         public ImageViewRotationHelper(ImageView imageView) {  
  23.             mImageView = imageView;  
  24.         }  
  25.           
  26.         /** 
  27.          * Sets the degrees that the view is rotated around the pivot point. Increasing values 
  28.          * result in clockwise rotation. 
  29.          * 
  30.          * @param rotation The degrees of rotation. 
  31.          * 
  32.          * @see #getRotation() 
  33.          * @see #getPivotX() 
  34.          * @see #getPivotY() 
  35.          * @see #setRotationX(float) 
  36.          * @see #setRotationY(float) 
  37.          * 
  38.          * @attr ref android.R.styleable#View_rotation 
  39.          */  
  40.         public void setRotation(float rotation) {  
  41.             if (APIUtils.hasHoneycomb()) {  
  42.                 mImageView.setRotation(rotation);  
  43.             } else {  
  44.                 if (null == mMatrix) {  
  45.                     mMatrix = new Matrix();  
  46.                       
  47.                     // 計算旋轉的中心點  
  48.                     Drawable imageDrawable = mImageView.getDrawable();  
  49.                     if (null != imageDrawable) {  
  50.                         mRotationPivotX = Math.round(imageDrawable.getIntrinsicWidth() / 2f);  
  51.                         mRotationPivotY = Math.round(imageDrawable.getIntrinsicHeight() / 2f);  
  52.                     }  
  53.                 }  
  54.                   
  55.                 mMatrix.setRotate(rotation, mRotationPivotX, mRotationPivotY);  
  56.                 mImageView.setImageMatrix(mMatrix);  
  57.             }  
  58.         }  
  59.     }  

 

最核心的就是,如果在2.x的版本上,旋轉ImageView使用Matrix。
 
  • PullToRefreshBase構造方法兼容2.x
在三個參數的構造方法聲明如下標注:
    @SuppressLint("NewApi")
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM