StaggeredGridView继承自ExtendableListView,同时ExtendableListView直接继承了AbsListView, 也就说它自己完成了item view的创建、销毁、更新、回收复用等环节。
ExtendableListView主要完成了一下功能逻辑:
滑动时更新可见view的layout(主要是x、y)动态回收由可见变为不可见的view, 填补空白区域,
为了理解代码的逻辑,需要对一些变量做简单解释:
//记录变量意义, 一般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事件处理逻辑中更新的,我们这里只关注滑动这一情景,
所以我们直接看TOUCH_MODE_SCROLLING就可以,但是在TOUCH_MODE_DONE_WATING情况下又进行了检测,
有可能从TOUCH_MODE_DONE_WATING状态转化为TOUCH_MODE_SCROLLING,
该过程可以从startScrollIfNeeded( final int y )中找到答案:
/** * 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 。