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中
protectedvoid 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. 關於下拉刷新
2. 實現原理
3. 具體實現
1、IPullToRefresh<T extends View>
- public interface IPullToRefresh<T extends View> {
- public void setPullRefreshEnabled(boolean pullRefreshEnabled);
- public void setPullLoadEnabled(boolean pullLoadEnabled);
- public void setScrollLoadEnabled(boolean scrollLoadEnabled);
- public boolean isPullRefreshEnabled();
- public boolean isPullLoadEnabled();
- public boolean isScrollLoadEnabled();
- public void setOnRefreshListener(OnRefreshListener<T> refreshListener);
- public void onPullDownRefreshComplete();
- public void onPullUpRefreshComplete();
- public T getRefreshableView();
- public LoadingLayout getHeaderLoadingLayout();
- public LoadingLayout getFooterLoadingLayout();
- public void setLastUpdatedLabel(CharSequence label);
- }
2、PullToRefreshBase<T extends View>
- 處理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會根據狀態去顯示相應的界面式樣。
- 對於ListView,ScrollView,WebView這三種情況,他們是否滑動到最頂部或是最底部的實現是不一樣的,所以,在PullToRefreshBase類中需要調用兩個抽象方法來判斷當前的位置是否在頂部或底部,而其派生類必須要實現這兩個方法。比如對於ListView,它滑動到最頂部的條件就是第一個child完全可見並且first postion是0。這兩個抽象方法是:
- /**
- * 判斷刷新的View是否滑動到頂部
- *
- * @return true表示已經滑動到頂部,否則false
- */
- protected abstract boolean isReadyForPullDown();
- /**
- * 判斷刷新的View是否滑動到底
- *
- * @return true表示已經滑動到底部,否則false
- */
- protected abstract boolean isReadyForPullUp();
- 創建可下拉刷新的View(也就是content view)的抽象方法是
- /**
- * 創建可以刷新的View
- *
- * @param context context
- * @param attrs 屬性
- * @return View
- */
- protected abstract T createRefreshableView(Context context, AttributeSet attrs);
- getContentSize
這個方法返回當前這個刷新Layout的大小,通常返回的是布局的高度,為了以后可以擴展為水平拉動,所以方法名字沒有取成getLayoutHeight()之類的,這個返回值,將會作為松手后是否可以刷新的臨界值,如果下拉的偏移值大於這個值,就認為可以刷新,否則不刷新,這個方法必須由派生類來實現。
- setState
這個方法用來設置當前刷新Layout的狀態,PullToRefreshBase類會調用這個方法,當進入下拉,松手等動作時,都會調用這個方法,派生類里面只需要根據這些狀態實現不同的界面顯示,如下拉狀態時,就顯示出箭頭,刷新狀態時,就顯示loading的圖標。可能的狀態值有: RESET, PULL_TO_REFRESH, RELEASE_TO_REFRESH, REFRESHING, NO_MORE_DATA
4. 如何使用
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mPullListView = new PullToRefreshListView(this);
- setContentView(mPullListView);
- // 上拉加載不可用
- mPullListView.setPullLoadEnabled(false);
- // 滾動到底自動加載可用
- mPullListView.setScrollLoadEnabled(true);
- mCurIndex = mLoadDataCount;
- mListItems = new LinkedList<String>();
- mListItems.addAll(Arrays.asList(mStrings).subList(0, mCurIndex));
- mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListItems);
- // 得到實際的ListView
- mListView = mPullListView.getRefreshableView();
- // 綁定數據
- mListView.setAdapter(mAdapter);
- // 設置下拉刷新的listener
- mPullListView.setOnRefreshListener(new OnRefreshListener<ListView>() {
- @Override
- public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {
- mIsStart = true;
- new GetDataTask().execute();
- }
- @Override
- public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {
- mIsStart = false;
- new GetDataTask().execute();
- }
- });
- setLastUpdateTime();
- // 自動刷新
- mPullListView.doPullRefreshing(true, 500);
- }
5. 運行效果
6. 源碼下載
7. Bug修復
- PullToRefreshListView#setScrollLoadEnabled方法,修正后的代碼如下:
- @Override
- public void setScrollLoadEnabled(boolean scrollLoadEnabled) {
- if (isScrollLoadEnabled() == scrollLoadEnabled) {
- return;
- }
- super.setScrollLoadEnabled(scrollLoadEnabled);
- if (scrollLoadEnabled) {
- // 設置Footer
- if (null == mLoadMoreFooterLayout) {
- mLoadMoreFooterLayout = new FooterLoadingLayout(getContext());
- mListView.addFooterView(mLoadMoreFooterLayout, null, false);
- }
- mLoadMoreFooterLayout.show(true);
- } else {
- if (null != mLoadMoreFooterLayout) {
- mLoadMoreFooterLayout.show(false);
- }
- }
- }
LoadingLayout#show方法,修正后的代碼如下:
- /**
- * 顯示或隱藏這個布局
- *
- * @param show flag
- */
- public void show(boolean show) {
- // If is showing, do nothing.
- if (show == (View.VISIBLE == getVisibility())) {
- return;
- }
- ViewGroup.LayoutParams params = mContainer.getLayoutParams();
- if (null != params) {
- if (show) {
- params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
- } else {
- params.height = 0;
- }
- requestLayout();
- setVisibility(show ? View.VISIBLE : View.INVISIBLE);
- }
- }
在更改LayoutParameter后,調用requestLayout()方法。
- 圖片旋轉兼容2.x系統
- @Override
- public void onPull(float scale) {
- if (null == mRotationHelper) {
- mRotationHelper = new ImageViewRotationHelper(mArrowImageView);
- }
- float angle = scale * 180f; // SUPPRESS CHECKSTYLE
- mRotationHelper.setRotation(angle);
- }
ImageViewRotationHelper主要的作用就是實現了ImageView的旋轉功能,內部作了版本的區分,實現代碼如下:
- /**
- * The image view rotation helper
- *
- * @author lihong06
- * @since 2014-5-2
- */
- static class ImageViewRotationHelper {
- /** The imageview */
- private final ImageView mImageView;
- /** The matrix */
- private Matrix mMatrix;
- /** Pivot X */
- private float mRotationPivotX;
- /** Pivot Y */
- private float mRotationPivotY;
- /**
- * The constructor method.
- *
- * @param imageView the image view
- */
- public ImageViewRotationHelper(ImageView imageView) {
- mImageView = imageView;
- }
- /**
- * Sets the degrees that the view is rotated around the pivot point. Increasing values
- * result in clockwise rotation.
- *
- * @param rotation The degrees of rotation.
- *
- * @see #getRotation()
- * @see #getPivotX()
- * @see #getPivotY()
- * @see #setRotationX(float)
- * @see #setRotationY(float)
- *
- * @attr ref android.R.styleable#View_rotation
- */
- public void setRotation(float rotation) {
- if (APIUtils.hasHoneycomb()) {
- mImageView.setRotation(rotation);
- } else {
- if (null == mMatrix) {
- mMatrix = new Matrix();
- // 計算旋轉的中心點
- Drawable imageDrawable = mImageView.getDrawable();
- if (null != imageDrawable) {
- mRotationPivotX = Math.round(imageDrawable.getIntrinsicWidth() / 2f);
- mRotationPivotY = Math.round(imageDrawable.getIntrinsicHeight() / 2f);
- }
- }
- mMatrix.setRotate(rotation, mRotationPivotX, mRotationPivotY);
- mImageView.setImageMatrix(mMatrix);
- }
- }
- }
- PullToRefreshBase構造方法兼容2.x
@TargetApi(Build.VERSION_CODES.HONEYCOMB)

