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 。