StaggeredGridView 實現分析--滑動處理(一)滑動位移

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 。

相關文章
相關標籤/搜索