打造android萬能上拉下拉重新整理架構——XRefreshView (二),android上拉下拉重新整理

來源:互聯網
上載者:User

打造android萬能上拉下拉重新整理架構——XRefreshView (二),android上拉下拉重新整理

一、前言

       自從上次發表了打造android萬能上拉下拉重新整理架構——XRefreshView (一)之後,期間的大半個月一直都很忙,但是我每天晚上下班以後都有在更新和維護XRefreshView,也根據一些朋友的意見解決了一些問題,這次之所以寫這篇文章,是因為XRefreshView已經到了一個功能相對可靠和穩定的一個階段。下面我會介紹下XrefreshView的最新功能和用法,以及實現的主要思路。

二、更新

2.1判斷下拉上拉重新整理時機方式的修改

之前是通過 refreshView.setRefreshViewType(XRefreshViewType.ABSLISTVIEW);這樣來預先設定view的類型來選擇對應判斷時機的方法,現在已經不用這樣做了,改成了下面這樣。

/** * @return Whether it is possible for the child view of this layout to *         scroll up. Override this if the child view is a custom view. */public boolean canChildPullDown() {if (child instanceof AbsListView) {final AbsListView absListView = (AbsListView) child;return canScrollVertically(child, -1)|| absListView.getChildCount() > 0&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0).getTop() < absListView.getPaddingTop());} else {return canScrollVertically(child, -1) || child.getScrollY() > 0;}}public boolean canChildPullUp() {if (child instanceof AbsListView) {AbsListView absListView = (AbsListView) child;return canScrollVertically(child, 1)|| absListView.getLastVisiblePosition() != mTotalItemCount - 1;} else if (child instanceof WebView) {WebView webview = (WebView) child;return canScrollVertically(child, 1)|| webview.getContentHeight() * webview.getScale() != webview.getHeight() + webview.getScrollY();} else if (child instanceof ScrollView) {ScrollView scrollView = (ScrollView) child;View childView = scrollView.getChildAt(0);if (childView != null) {return canScrollVertically(child, 1)|| scrollView.getScrollY() != childView.getHeight()- scrollView.getHeight();}}else{return canScrollVertically(child, 1);}return true;}/** * 用來判斷view在豎直方向上能不能向上或者向下滑動 * @param view v * @param direction 方向    負數代表向上滑動 ,正數則反之 * @return */public boolean canScrollVertically(View view, int direction) {return ViewCompat.canScrollVertically(view, direction);}
正如你所見,ViewCompat.canScrollVertically(view, direction)這個方法可以用來判斷view能不能向上或者向下滑動,從而可以判斷view有沒有到達頂部或者底部,在4.0以後在個方法通常是很管用的,但是2.3.3以前則不是這樣,為了相容2.3.3我又做了一些view類型的判斷,通過view的類型來提供特別的判斷到達頂部或者底部的方法。一般情況下,常用的view通過上述的方法都可以準確的判斷出有沒有到達頂部或者底部,但是如果你要重新整理的是一個複雜的或者自訂的view,也可以通過以下的方式來做
refreshView.setOnTopRefreshTime(new OnTopRefreshTime() {@Overridepublic boolean isTop() {return stickyLv.getFirstVisiblePosition() == 0;}});refreshView.setOnBottomLoadMoreTime(new OnBottomLoadMoreTime() {@Overridepublic boolean isBottom() {return stickyLv.getLastVisiblePosition() == mTotalItemCount - 1;}});

XRefreshView把判斷view到達頂部和底部的工作交給你去做了,你只要告訴XRefreshView什麼時候是正確的重新整理時機就行了,與上次部落格中提到的方法不同的是,XRefreshView這次提供了兩個介面,把頂部和底部的判斷時機給分開了,主要是考慮到下拉重新整理和上拉載入有的時候並不是都需要的。

2.2headview和footview上下移動時的方式的修改

一開始,移動headview和footview我是通過屬性動畫來移動的

public static void moveChildAndAddedView(View child, View addView,float childY, float addY, int during, AnimatorListener... listener) {// 屬性動畫移動ObjectAnimator y = ObjectAnimator.ofFloat(child, "y", child.getY(),childY);ObjectAnimator y2 = ObjectAnimator.ofFloat(addView, "y",addView.getY(), addY);AnimatorSet animatorSet = new AnimatorSet();animatorSet.playTogether(y, y2);animatorSet.setDuration(during);if (listener.length > 0)animatorSet.addListener(listener[0]);animatorSet.start();}
後來為了相容2.3.3我還專門下載了動畫開源庫NineOldAndroidsNineOldAndroids,這個庫究竟是幹嘛的呢?在API3.0(Honeycomb), SDK新增了一個android.animation包,裡面的類是實現動畫效果相關的類,通過Honeycomb API,能夠實現非常複雜的動畫效果,但是如果開發人員想在3.0以下使用這一套API, 則需要使用開源架構Nine Old Androids,在這個庫中會根據我們啟動並執行機器判斷其SDK版本,如果是API3.0以上則使用Android內建的動畫類,否則就使用Nine Old Androids庫中,這是一個相容庫。 (註:紅色部分的字我是直接引用夏安明大神的部落格原文,一直都在看他的部落格,所以一直很佩服他,他的部落格的品質都很不錯。)之後相容性的問題就算處理好了,但後來Xutils 4群的大炮告訴我,XRefreshView在下拉的時候會有抖動的情況,我知道了這個情況以後就開始找問題,後來發現是因為用屬性動畫來移動header的問題,不用屬性動畫就好了,仔細想一想,屬性動畫其實是通過反射來屬性對應的get/set方法來執行的,畢竟是反射,而在手指移動的時候會觸發大量的action_move,每個action_move都會做一次反射,那麼就會做大量的反射工作,大量的密集的反射就會導致效能方面有所降低,所以出現了抖動的情況。放棄反射以後,我用的是view.offsetTopAndBottom(deltaY)這個方法,看方法的注釋
    /**     * Offset this view's vertical location by the specified number of pixels.     *     * @param offset the number of pixels to offset the view by     */
翻譯過來就是在豎直方向上以像素為單位來移動view。沒什麼好說的,用起來很簡單,你值得擁有。

2.3demo用了流式布局

很簡單,感興趣的可以看看


2.4點擊按鈕重新整理和支援回彈

現在有支援點擊按鈕重新整理,

protected void onResume() {super.onResume();xRefreshView.startRefresh();}
還有就是可以支援設定是否下拉重新整理和上拉載入
// 設定是否可以下拉重新整理refreshView.setPullRefreshEnable(false);// 設定是否可以上拉載入refreshView.setPullLoadEnable(false);
大炮說如果可以在不可以下拉重新整理和上拉載入的情況下也可以有回彈的效果就好了,於是現在的版本就支援了。

三、實現相關
3.1前後變化

之前我是把headview,被重新整理的childview和footview當成了三個部分來看待,並且分別記錄了一開始的各個view的位置

/** * 在開始上拉載入更多的時候,記錄下childView一開始的Y軸座標 */private float mChildY = -1;/** * 在開始上拉載入更多的時候,記錄下FootView一開始的Y軸座標 */private float mFootY = -1;/** * 在開始上拉載入更多的時候,記錄下HeadView一開始的Y軸座標 */private float mHeadY = -1;
然後在手指移動的時候不斷更新當前各個view的y軸座標,最後再來逐個移動各個view,這樣做無意中就加大了工作量以及工作的複雜度,後來我想到了把三個部分當成一個整體,這樣以來就簡單很多了,也就不再需要那麼多的變數。


3.2實現過程

3.2.1測量

/* * 丈量視圖的寬、高。寬度為使用者佈建的寬度,高度則為header, content view, footer這三個子控制項的高度之和。 *  * @see android.view.View#onMeasure(int, int) */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = MeasureSpec.getSize(widthMeasureSpec);int childCount = getChildCount();int finalHeight = 0;for (int i = 0; i < childCount; i++) {View child = getChildAt(i);measureChild(child, widthMeasureSpec, heightMeasureSpec);finalHeight += child.getMeasuredHeight();}setMeasuredDimension(width, finalHeight);}
3.2.2布局
@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);LogUtils.d("onLayout mHolder.mOffsetY=" + mHolder.mOffsetY);mFootHeight = mFooterView.getMeasuredHeight();int childCount = getChildCount();int top = getPaddingTop() + mHolder.mOffsetY;for (int i = 0; i < childCount; i++) {View child = getChildAt(i);if (child == mHeaderView) {// 通過把headerview向上移動一個headerview高度的距離來達到隱藏headerview的效果child.layout(0, top - mHeaderViewHeight,child.getMeasuredWidth(), top);} else {child.layout(0, top, child.getMeasuredWidth(),child.getMeasuredHeight() + top);top += child.getMeasuredHeight();}}}

其中

int top = getPaddingTop() + mHolder.mOffsetY;
mHolder.mOffsetY是用來記錄整個view在y軸方向上的位移量的。這裡之所以加上mHolder.mOffsetY,是因為在拖動重新整理的過程中view的改變會引起系統重新測量和布局,加上這個位移量以後,可以在系統重新布局的時候保住view當前的位置,不恢複到初始位置。

3.2.3 事件處理並移動view

public boolean dispatchTouchEvent(MotionEvent ev) {final int action = MotionEventCompat.getActionMasked(ev);int deltaY = 0;switch (action) {case MotionEvent.ACTION_DOWN:mHasSendCancelEvent = false;mHasSendDownEvent = false;mLastY = (int) ev.getRawY();mInitialMotionY = mLastY;if (!mScroller.isFinished() && !mPullRefreshing && !mPullLoading) {mScroller.forceFinished(true);}break;case MotionEvent.ACTION_MOVE:if (mPullLoading || mPullRefreshing || !isEnabled()) {return super.dispatchTouchEvent(ev);}mLastMoveEvent = ev;int currentY = (int) ev.getRawY();deltaY = currentY - mLastY;mLastY = currentY;// intercept the MotionEvent only when user is not scrollingif (!isIntercepted && Math.abs(deltaY) < mTouchSlop) {isIntercepted = true;return super.dispatchTouchEvent(ev);}LogUtils.d("isTop=" + mContentView.isTop() + ";isBottom="+ mContentView.isBottom());deltaY = (int) (deltaY / OFFSET_RADIO);if (mContentView.isTop()&& (deltaY > 0 || (deltaY < 0 && mHolder.hasHeaderPullDown()))) {sendCancelEvent();updateHeaderHeight(currentY, deltaY);} else if (mContentView.isBottom()&& (deltaY < 0 || deltaY > 0 && mHolder.hasFooterPullUp())) {sendCancelEvent();updateFooterHeight(deltaY);} else if (mContentView.isTop() && !mHolder.hasHeaderPullDown()|| mContentView.isBottom() && !mHolder.hasFooterPullUp()) {if (deltaY > 0)sendDownEvent();}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:// if (mHolder.mOffsetY != 0 && mRefreshViewListener != null// && !mPullRefreshing && !mPullLoading) {// mRefreshViewListener.onRelease(mHolder.mOffsetY);// }if (mContentView.isTop() && mHolder.hasHeaderPullDown()) {// invoke refreshif (mEnablePullRefresh && mHolder.mOffsetY > mHeaderViewHeight) {mPullRefreshing = true;mHeaderView.setState(XRefreshViewState.STATE_REFRESHING);if (mRefreshViewListener != null) {mRefreshViewListener.onRefresh();}}resetHeaderHeight();} else if (mContentView.isBottom() && mHolder.hasFooterPullUp()) {if (mEnablePullLoad) {int offset = 0 - mHolder.mOffsetY - mFootHeight;startScroll(offset, SCROLL_DURATION);startLoadMore();} else {int offset = 0 - mHolder.mOffsetY;startScroll(offset, SCROLL_DURATION);}}mLastY = -1; // resetmInitialMotionY = 0;isIntercepted = true;break;}return super.dispatchTouchEvent(ev);}
首先可以看到,所以的事件處理都在dispatchTouchEvent(MotionEvent ev)方法裡進行,而之前則是分成兩部分進行的,在onInterceptTouchEvent(MotionEvent ev)方法中進行攔截,事件處理則在onTouchEvent(MotionEvent ev)中進行。這樣做是因為大炮說他下拉重新整理的時候,由於子view非常複雜,子view有時候會搶佔事件,造成卡住不重新整理了。我們都知道子view是可以通過requestDisallowInterceptTouchEvent來請求父類不要攔截事件,那麼onInterceptTouchEvent方法就不會執行,那我們下拉重新整理也就不可靠了,所以為瞭解決這個問題,我把所有的處理都丟到dispatchTouchEvent方法中做。

再來看看sendCancelEvent()和sendDownEvent()這兩個方法

private void sendCancelEvent() {if (!mHasSendCancelEvent) {setRefreshTime();mHasSendCancelEvent = true;mHasSendDownEvent = false;MotionEvent last = mLastMoveEvent;MotionEvent e = MotionEvent.obtain(last.getDownTime(),last.getEventTime()+ ViewConfiguration.getLongPressTimeout(),MotionEvent.ACTION_CANCEL, last.getX(), last.getY(),last.getMetaState());dispatchTouchEventSupper(e);}}private void sendDownEvent() {if (!mHasSendDownEvent) {LogUtils.d("sendDownEvent");mHasSendCancelEvent = false;mHasSendDownEvent = true;isIntercepted = false;final MotionEvent last = mLastMoveEvent;if (last == null)return;MotionEvent e = MotionEvent.obtain(last.getDownTime(),last.getEventTime(), MotionEvent.ACTION_DOWN, last.getX(),last.getY(), last.getMetaState());dispatchTouchEventSupper(e);}}
觸摸事件一開始肯定會被子view接收到的,如果是listview的話,就會有item的點擊效果出現,這很正常,但是如果此時觸發下拉重新整理的話,同時又有item的點擊效果,那麼看起來就不是很自然,所有此時可以通過sendCancelEvent()來給子view發送一個cancel事件,這樣item的點擊效果就會消失。還有當我們拉下headerview以後沒有達到重新整理條件,並且接著有往上推把headerview又完全隱藏了,此時就應該i把事件交還給子view,讓子view接收到事件並移動,可以通過sendDownEvent來達到效果。

最後說下移動view的處理

當手指在拖動的時候,

public void moveView(int deltaY) {mHolder.move(deltaY);mChild.offsetTopAndBottom(deltaY);mHeaderView.offsetTopAndBottom(deltaY);mFooterView.offsetTopAndBottom(deltaY);invalidate();}
public int mOffsetY;public void move(int deltaY) {mOffsetY += deltaY;}
通過moveView方法來移動view,並把位移量存了下來。

當手指離開以後,通過scroller來移動view

mScroller = new Scroller(getContext(), new LinearInterpolator());
這裡用了線性插值器,表示移動的時候是勻速變動的
/** *  * @param offsetY *            滑動位移量,負數向上滑,正數反之 * @param duration *            滑動期間 */public void startScroll(int offsetY, int duration) {mScroller.startScroll(0, mHolder.mOffsetY, 0, offsetY, duration);invalidate();}
public void computeScroll() {super.computeScroll();if (mScroller.computeScrollOffset()) {int lastScrollY = mHolder.mOffsetY;int currentY = mScroller.getCurrY();int offsetY = currentY - lastScrollY;lastScrollY = currentY;moveView(offsetY);LogUtils.d("currentY=" + currentY + ";mHolder.mOffsetY="+ mHolder.mOffsetY);} else {LogUtils.d("scroll end mOffsetY=" + mHolder.mOffsetY);}}
從上面可以看出,整個移動過程中只用到了一個mOffsetY變數來儲存位移量,代碼相較於之前瞬間變得很簡單。

四、最後的說明

如果你對XRefreshView感興趣,可以在github上關注XRefreshView

當然你也可以點此直接下載




著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.