天天看點

Android仿qq下拉重新整理及向左滑動清單----PullToRefresh, SwipeMenuListView開源項目整合

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。

項目Github連結:https://github.com/licaomeng/Android-PullToRefresh-SwipeMenuListView-Sample

如果項目對您有幫助,歡迎小夥伴們在github上Star我的項目~

PullToRefresh是一個非常完美的下拉重新整理的開源項目,SwipeMenuListView是一個向左滑動ListView中item實作可以删除功能的開源項目。筆者在此将兩套開源項目整合形成一套,類似于手機qq那樣同時支援下拉重新整理和向左滑動的清單。效果如下:

Android仿qq下拉重新整理及向左滑動清單----PullToRefresh, SwipeMenuListView開源項目整合

兩套架構整合的關鍵在于對Touch事件處理的把握,Touch事件的處理主要由Action_Down, Action_Move,Action_Up組成。Action_Down表示使用者按下螢幕的操作,Action_Up表示使用者按下螢幕後擡起的操作,Action_Move表示使用者在螢幕上的移動操作。Touch事件處理流程圖如下:

Android仿qq下拉重新整理及向左滑動清單----PullToRefresh, SwipeMenuListView開源項目整合

1.      當ACTION_UP事件生效的時候,判斷如果是下拉操作,執行onRefresh(), 實作下拉重新整理,然後執行resetHeaderHeight()恢複Header的高度;判斷如果是上拉操作,當mFooterView的高度大于自定義的高度,那麼就執行startLoadMore()加載更多,然後執行resetFooterHeight()恢複Footer的高度。判斷如果是側滑那麼就結束SwipeMenu的滑動。

2.      當ACTION_MOVE事件生效的時候,分别記錄X,Y方向上的偏移。如果是X方向上的偏移,那麼對應的就是SwipeMenuListView的特效操作;如果是Y方向上的偏移, Y方向向下,那麼對應的就是下拉重新整理操作,Y方向向上,那麼對應的就是上拉操作。

Touch事件相關代碼如下:

[java]  view plain copy

  1.  @Override  
  2.     public boolean onTouchEvent(MotionEvent ev) {  
  3.         if (mLastY == -1) {  
  4.             mLastY = ev.getRawY();  
  5.         }  
  6.         switch (ev.getAction()) {  
  7.         case MotionEvent.ACTION_DOWN:  
  8.             mLastY = ev.getRawY();  
  9.             int oldPos = mTouchPosition;  
  10.             mDownX = ev.getX();  
  11.             mDownY = ev.getY();  
  12.             mTouchState = TOUCH_STATE_NONE;  
  13.             mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());  
  14.             if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) {  
  15.                 mTouchState = TOUCH_STATE_X;  
  16.                 mTouchView.onSwipe(ev);  
  17.                 return true;  
  18.             }  
  19.             View view = getChildAt(mTouchPosition - getFirstVisiblePosition());  
  20.             if (mTouchView != null && mTouchView.isOpen()) {  
  21.                 mTouchView.smoothCloseMenu();  
  22.                 mTouchView = null;  
  23.                 return super.onTouchEvent(ev);  
  24.             }  
  25.             if (view instanceof SwipeMenuLayout) {  
  26.                 mTouchView = (SwipeMenuLayout) view;  
  27.             }  
  28.             if (mTouchView != null) {  
  29.                 mTouchView.onSwipe(ev);  
  30.             }  
  31.             break;  
  32.         case MotionEvent.ACTION_MOVE:  
  33.             final float deltaY = ev.getRawY() - mLastY;  
  34.             mLastY = ev.getRawY();  
  35.             // 非常感謝github上面的G友提出的寶貴建議和啟發,這個地方要做一個判斷。  
  36.             // 在側滑的過程當中不應該同時出現使用者本不期望的下拉操作,  
  37.             // 這樣會給使用者帶來一種很不穩定的感受,是一種非常槽糕的使用者體驗。  
  38.             // Modified on 9/16/2015  
  39.             if (mTouchView == null || !mTouchView.isActive()) {  
  40.                 // 這個地方調換了if和else if的順序,如果反過來會出現有時無法下拉重新整理的bug  
  41.                 // Modified on 9/17/2015  
  42.                 if (getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {  
  43.                     // the first item is showing, header has shown or pull down.  
  44.                     updateHeaderHeight(deltaY / OFFSET_RADIO);  
  45.                     invokeOnScrolling();  
  46.                 } else if ((mFooterView.getBottomMargin() > 0 || deltaY < 0)) {  
  47.                     // last item, already pulled up or want to pull up.  
  48.                     updateFooterHeight(-deltaY / OFFSET_RADIO);  
  49.                 }  
  50.             }  
  51.             float dy = Math.abs((ev.getY() - mDownY));  
  52.             float dx = Math.abs((ev.getX() - mDownX));  
  53.             if (mTouchState == TOUCH_STATE_X) {  
  54.                 if (mTouchView != null) {  
  55.                     mTouchView.onSwipe(ev);  
  56.                 }  
  57.                 getSelector().setState(new int[] { 0 });  
  58.                 ev.setAction(MotionEvent.ACTION_CANCEL);  
  59.                 super.onTouchEvent(ev);  
  60.                 return true;  
  61.             } else if (mTouchState == TOUCH_STATE_NONE) {  
  62.                 if (Math.abs(dy) > MAX_Y) {  
  63.                     mTouchState = TOUCH_STATE_Y;  
  64.                 } else if (dx > MAX_X) {  
  65.                     mTouchState = TOUCH_STATE_X;  
  66.                     if (mOnSwipeListener != null) {  
  67.                         mOnSwipeListener.onSwipeStart(mTouchPosition);  
  68.                     }  
  69.                 }  
  70.             }  
  71.             break;  
  72.         // default:  
  73.         // 非常感謝博友私信提出的修改意見,這個地方不應該寫default,如果寫成default的話,  
  74.         // ACTION_POINTER_DOWN和ACTION_POINTER_UP事件也會執行下面的語句,  
  75.         // 這樣就會出現兩個bug:  
  76.         // 1.當下拉未松開時,螢幕上有其它點按下,下拉狀态會變成松開;  
  77.         // 2.當向左滑動清單的時候,螢幕上有其它點按下,再全部松開,滑出的清單會停在中間狀态.  
  78.         // Modified on 8/26/2015  
  79.         case MotionEvent.ACTION_UP:  
  80.             mLastY = -1; // reset  
  81.             if (mEnablePullLoad && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA) {  
  82.                 startLoadMore();  
  83.                 resetFooterHeight();  
  84.                 // 再次感謝同一位博友的來信提出的新的bug  
  85.                 // 這個地方應該在加一個如下所示的resetHeaderHeight()異步操作,  
  86.                 // 如果不加這個操作的話,會導緻下拉接着上拉後,重新整理的Header卡住不動的狀況,  
  87.                 // 由于resetHeaderHeight()和resetFooterHeight()牽涉同一變量mScroller,  
  88.                 // 是以這裡通過Android封裝的異步類AsyncTask實作footerHeight和headerHeight的恢複。  
  89.                 // resetFooterHeight(),resetHeaderHeight()其實是同步的,前者執行完畢才執行後者。  
  90.                 // Modified on 8/28/2015  
  91.                 new ResetHeaderHeightTask().execute();  
  92.             } else if (getFirstVisiblePosition() == 0) {  
  93.                 // invoke refresh  
  94.                 if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) {  
  95.                     mPullRefreshing = true;  
  96.                     mHeaderView.setState(PullToRefreshListHeader.STATE_REFRESHING);  
  97.                     if (mListViewListener != null) {  
  98.                         mListViewListener.onRefresh();  
  99.                     }  
  100.                 }  
  101.                 resetHeaderHeight();  
  102.             }  
  103.             if (mTouchState == TOUCH_STATE_X) {  
  104.                 if (mTouchView != null) {  
  105.                     mTouchView.onSwipe(ev);  
  106.                     if (!mTouchView.isOpen()) {  
  107.                         mTouchPosition = -1;  
  108.                         mTouchView = null;  
  109.                     }  
  110.                 }  
  111.                 if (mOnSwipeListener != null) {  
  112.                     mOnSwipeListener.onSwipeEnd(mTouchPosition);  
  113.                 }  
  114.                 ev.setAction(MotionEvent.ACTION_CANCEL);  
  115.                 super.onTouchEvent(ev);  
  116.                 return true;  
  117.             }  
  118.             break;  
  119.         }  
  120.         return super.onTouchEvent(ev);  
  121.     }  
  122.     class ResetHeaderHeightTask extends AsyncTask<Void, Void, Void> {  
  123.         protected Void doInBackground(Void... params) {  
  124.             try {  
  125.                 Thread.sleep(400);  
  126.             } catch (InterruptedException e) {  
  127.                 e.printStackTrace();  
  128.             }  
  129.             return null;  
  130.         }  
  131.         protected void onPostExecute(Void result) {  
  132.             mPullRefreshing = false;  
  133.             mHeaderView.setState(PullToRefreshListHeader.STATE_NORMAL);  
  134.             resetHeaderHeight();  
  135.         }  
  136.     }  

其實在整合這兩套開源架構的時候遇到了不少的問題,歸結起來其實是對Android的Touch事件把握的不充分導緻的,真正把原理、基礎搞透徹了,做起項目來才能得心應手啊。這裡專門寫了一篇關于Touch事件的部落格:

http://blog.csdn.net/licaomengrice/article/details/48415431

繼續閱讀