Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 開源Android-PullToRefresh下拉刷新源碼分析

開源Android-PullToRefresh下拉刷新源碼分析

編輯:關於Android編程

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個子類

 

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

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved