StaggeredGridView繼承自ExtendableListView,同時ExtendableListView直接繼承了AbsListView, 也就說它本身完成了item view的建立、銷燬、更新、回收複用等環節。java
ExtendableListView主要完成了一下功能邏輯:this
滑動時更新可見view的layout(主要是x、y)動態回收由可見變爲不可見的view, 填補空白區域, spa
爲了理解代碼的邏輯,須要對一些變量作簡單解釋:code
//記錄變量意義, 通常item指adapter中的數據項, itemView指listview中的child view //第0個itemView對應的item的位置 int mFirstPosition //當前點擊的item位置 int mMotionPosition //TouchDown事件發生時的y軸位置 int mMotionY // 滑動距離糾正值,通常爲0. // 在檢測到touchTap(兩個touchdown)可是尚未處理以前,或者在發生了TouchDown可是還沒有檢測到touchLong事件前, // 若是發生了移動(touchmove)事件而且移動的距離大於mTouchSlop,那麼mMotionCorrection的值就等於sTouchSlop, // 同時這個touchmove會被當作scroll動做處理,scroll的距離等於實際距離(touchmove 與touchdown發生時的距離)減去這個糾正值 int mMotionCorrection // 處理多手指觸摸的狀況,表示當前有效的pointer id。 // 假如當前屏幕有一個手指A,那麼A的id就是mActivePointerId,後來又有另外一個手指B按下了屏幕(發生ACTION_POINTER_DOWN事件) // 這時mActivePointerId更新爲B的id, 此時A的滑動是無效的, 這是AbsListView的默認處理邏輯 int mActivePointerId // 發生Action_Down時的y值。發生pointer_down、 pointer_up後會根據狀況更新 int mLastY
ExtendableListView對touch event作了過濾,能夠響應tap等動做,具體過程能夠參考其 ontouchEvent、onInterceptTouchEvent等方法。繼承
咱們從onTouchMove 開始看:事件
private boolean onTouchMove(final MotionEvent event) { final int index = MotionEventCompat.findPointerIndex(event, mActivePointerId); if (index < 0) { ... return false; } final int y = (int) MotionEventCompat.getY(event, index); // our data's changed so we need to do a layout before moving any further if (mDataChanged) { layoutChildren(); } switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: // Check if we have moved far enough that it looks more like a // scroll than a tap startScrollIfNeeded(y); break; case TOUCH_MODE_SCROLLING: //case TOUCH_MODE_OVERSCROLL: scrollIfNeeded(y); break; } return true; }
TOUCH_MODE_XXX這些狀態是在touch down 和 touch move事件處理邏輯中更新的,咱們這裏只關注滑動這一情景,rem
因此咱們直接看TOUCH_MODE_SCROLLING就能夠,可是在TOUCH_MODE_DONE_WATING狀況下又進行了檢測,get
有可能從TOUCH_MODE_DONE_WATING狀態轉化爲TOUCH_MODE_SCROLLING,it
該過程能夠從startScrollIfNeeded( final int y )中找到答案:io
/** * Starts a scroll that moves the difference between y and our last motions y * if it's a movement that represents a big enough scroll. */ private boolean startScrollIfNeeded(final int y) { final int deltaY = y - mMotionY; final int distance = Math.abs(deltaY); // TODO : Overscroll? // final boolean overscroll = mScrollY != 0; final boolean overscroll = false; // 若是移動了足夠多的距離就把狀態改成TOUCH_MODE_SCROLLING , 讓scrollIfNeeded(final int y) 方法來處理 if (overscroll || distance > mTouchSlop) { if (overscroll) { mMotionCorrection = 0; } else { mTouchMode = TOUCH_MODE_SCROLLING; mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop; } final Handler handler = getHandler(); if (handler != null) { handler.removeCallbacks(mPendingCheckForLongPress); } setPressed(false); View motionView = getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { motionView.setPressed(false); } final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } scrollIfNeeded(y); return true; } return false; }
最終TOUCH_MODE_SCROLLING狀態是由 scrollIfNeeded(final int y) 方法處理的,其中的糾正值的意義已經在開始時解釋過。
//響應 TOUCH_MODE_SCROLLING 狀態 private void scrollIfNeeded(final int y) { final int rawDeltaY = y - mMotionY; // 實際滑動距離減去糾正值 final int deltaY = rawDeltaY - mMotionCorrection; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; if (mTouchMode == TOUCH_MODE_SCROLLING) { if (DBG) Log.d(TAG, "scrollIfNeeded TOUCH_MODE_SCROLLING"); if (y != mLastY) { // stop our parent if (Math.abs(rawDeltaY) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } final int motionIndex; if (mMotionPosition >= 0) { motionIndex = mMotionPosition - mFirstPosition; } else { // If we don't have a motion position that we can reliably track, // pick something in the middle to make a best guess at things below. motionIndex = getChildCount() / 2; } // No need to do all this work if we're not going to move anyway boolean atEdge = false; if (incrementalDeltaY != 0) { atEdge = moveTheChildren(deltaY, incrementalDeltaY); } // Check to see if we have bumped into the scroll limit View motionView = this.getChildAt(motionIndex); if (motionView != null) { if (atEdge) { // TODO : edge effect & overscroll } mMotionY = y; } mLastY = y; } } // TODO : ELSE SUPPORT OVERSCROLL! }
scrollIfNeeded方法中調用了一個很是核心的方法 moveTheChildren, 該方法完成了動態更新子view的邏輯。
下一篇分析moveTheChildren 。