StaggeredGridView 实现分析--滑动处理(一)滑动位移

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 。

转载于:https://my.oschina.net/u/255456/blog/343575

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值