android高級ui之ListView源碼分析

ListView源碼分析

項目中使用ListView仍是挺多的,以前看過幾回,非常容易遺忘,今特作記錄以下android

  • Android 6.0 & API Level 23
  • Github: wxq
  • Android 6.0 & API Level 23

主要從如下幾點分析

  • 構造函數
  • onMeasure()
  • onLayout()
  • listview.setAdapter()以及adapter.notifyDataSetChanged()
  • onInterceptTouchEvent和onTouchEvent

簡要

ListView繼承之AbsListView抽象類,因此大部分分析的源碼都在這兩個類中git

構造函數初始化過程:

父類AbsListView的初始化:

public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    // 初始化設置一些額外屬性值
    initAbsListView();

    mOwnerThread = Thread.currentThread();

    // 初始化XML文件中設置的某些默認屬性值
    final TypedArray a = context.obtainStyledAttributes(
            attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);

    final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
    if (selector != null) {
        setSelector(selector);
    }

    mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
    
    // 初始化設置mStackFromBottom,這個影響到佈局子view的順序方式,默認爲false
    setStackFromBottom(a.getBoolean(
            R.styleable.AbsListView_stackFromBottom, false));
    setScrollingCacheEnabled(a.getBoolean(
            R.styleable.AbsListView_scrollingCache, true));
    setTextFilterEnabled(a.getBoolean(
            R.styleable.AbsListView_textFilterEnabled, false));
    setTranscriptMode(a.getInt(
            R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
    setCacheColorHint(a.getColor(
            R.styleable.AbsListView_cacheColorHint, 0));
    setSmoothScrollbarEnabled(a.getBoolean(
            R.styleable.AbsListView_smoothScrollbar, true));
    setChoiceMode(a.getInt(
            R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
    setFastScrollEnabled(a.getBoolean(
            R.styleable.AbsListView_fastScrollEnabled, false));
    setFastScrollStyle(a.getResourceId(
            R.styleable.AbsListView_fastScrollStyle, 0));
    setFastScrollAlwaysVisible(a.getBoolean(
            R.styleable.AbsListView_fastScrollAlwaysVisible, false));
    a.recycle();
}

private void initAbsListView() {
    // Setting focusable in touch mode will set the focusable property to true
    // 設置ListView自己能夠點擊便可以消耗父View分發的事件
    setClickable(true);
    setFocusableInTouchMode(true);
    // 由於向上父類還繼承之ViewGroup,ViewGroup默認不須要重寫draw()方法, 
    // 從而setWillNotDraw(true),可是AbsListView爲了滾動效果,自身重寫了View的
    // draw(),主要用於實現滾動到最底部或最頂部的非OVER_SCROLL_NEVER模式的效果
    setWillNotDraw(false);
    setAlwaysDrawnWithCacheEnabled(false);
    setScrollingCacheEnabled(true);

    // 事件處理相關變量初始化
    final ViewConfiguration configuration = ViewConfiguration.get(mContext);
    mTouchSlop = configuration.getScaledTouchSlop();
    mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    mOverscrollDistance = configuration.getScaledOverscrollDistance();
    mOverflingDistance = configuration.getScaledOverflingDistance();

    mDensityScale = getContext().getResources().getDisplayMetrics().density;
}
複製代碼

ListView初始化:

public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);

    final TypedArray a = context.obtainStyledAttributes(
            attrs, R.styleable.ListView, defStyleAttr, defStyleRes);

    final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);
    if (entries != null) {
        setAdapter(new ArrayAdapter<>(context, R.layout.simple_list_item_1, entries));
    }
    // 獲取item分割線的drawable對象
    final Drawable d = a.getDrawable(R.styleable.ListView_divider);
    if (d != null) {
        // Use an implicit divider height which may be explicitly
        // overridden by android:dividerHeight further down.
        setDivider(d);
    }

    final Drawable osHeader = a.getDrawable(R.styleable.ListView_overScrollHeader);
    if (osHeader != null) {
        setOverscrollHeader(osHeader);
    }

    final Drawable osFooter = a.getDrawable(R.styleable.ListView_overScrollFooter);
    if (osFooter != null) {
        setOverscrollFooter(osFooter);
    }

    // Use an explicit divider height, if specified.
    // item分割線的高度
    if (a.hasValueOrEmpty(R.styleable.ListView_dividerHeight)) {
        final int dividerHeight = a.getDimensionPixelSize(
                R.styleable.ListView_dividerHeight, 0);
        if (dividerHeight != 0) {
            setDividerHeight(dividerHeight);
        }
    }

    mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
    mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);

    a.recycle();
}

複製代碼

onMeasure():

ListView 的onMeasure()方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // Sets up mListPadding
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int childWidth = 0;
    int childHeight = 0;
    int childState = 0;

    mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
    if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
            || heightMode == MeasureSpec.UNSPECIFIED)) {
        final View child = obtainView(0, mIsScrap);

        // Lay out child directly against the parent measure spec so that
        // we can obtain exected minimum width and height.
        measureScrapChild(child, 0, widthMeasureSpec, heightSize);

        childWidth = child.getMeasuredWidth();
        childHeight = child.getMeasuredHeight();
        childState = combineMeasuredStates(childState, child.getMeasuredState());

        if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
                ((LayoutParams) child.getLayoutParams()).viewType)) {
            mRecycler.addScrapView(child, 0);
        }
    }

    if (widthMode == MeasureSpec.UNSPECIFIED) {
        widthSize = mListPadding.left + mListPadding.right + childWidth +
                getVerticalScrollbarWidth();
    } else {
        widthSize |= (childState & MEASURED_STATE_MASK);
    }

    if (heightMode == MeasureSpec.UNSPECIFIED) {
        heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                getVerticalFadingEdgeLength() * 2;
    }

    // 有時候須要使ListView的高度等於全部子item view 能夠重寫onMeasure()方法使其調用以
    // 下代碼
    if (heightMode == MeasureSpec.AT_MOST) {
        // TODO: after first layout we should maybe start at the first visible position, not 0
        heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
    }

    setMeasuredDimension(widthSize, heightSize);

    mWidthMeasureSpec = widthMeasureSpec;
}

複製代碼

onlayout()

ListView由adapter.getView()獲取的子view的layout方式在此實現github

父類AbsListView的onLayout():

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);

    mInLayout = true;

    final int childCount = getChildCount();
    if (changed) {
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).forceLayout();
        }
        mRecycler.markChildrenDirty();
    }

    // 由子類ListView 和 GridView實現,是核心佈局方法代碼,也是listview與adapter交互數據
    // 的主要入口函數
    layoutChildren();
    mInLayout = false;

    mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;

    // TODO: Move somewhere sane. This doesn't belong in onLayout(). if (mFastScroll != null) { mFastScroll.onItemCountChanged(getChildCount(), mItemCount); } } 複製代碼

ListView的layoutChildren

@Override
protected void layoutChildren() {
    final boolean blockLayoutRequests = mBlockLayoutRequests;
    if (blockLayoutRequests) {
        return;
    }

    mBlockLayoutRequests = true;

    try {
        super.layoutChildren();

        invalidate();

        if (mAdapter == null) {
            resetList();
            invokeOnItemScrollListener();
            return;
        }

        final int childrenTop = mListPadding.top;
        final int childrenBottom = mBottom - mTop - mListPadding.bottom;
        // 每次即將進行layout子item view的時候先記錄當前listview已有的child view個數
        final int childCount = getChildCount();

        int index = 0;
        int delta = 0;

        View sel;
        View oldSel = null;
        View oldFirst = null;
        View newSel = null;

        // Remember stuff we will need down below
        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            index = mNextSelectedPosition - mFirstPosition;
            if (index >= 0 && index < childCount) {
                newSel = getChildAt(index);
            }
            break;
        case LAYOUT_FORCE_TOP:
        case LAYOUT_FORCE_BOTTOM:
        case LAYOUT_SPECIFIC:
        case LAYOUT_SYNC:
            break;
        case LAYOUT_MOVE_SELECTION:
        default:
            // Remember the previously selected view
            index = mSelectedPosition - mFirstPosition;
            if (index >= 0 && index < childCount) {
                oldSel = getChildAt(index);
            }

            // Remember the previous first child
            oldFirst = getChildAt(0);

            if (mNextSelectedPosition >= 0) {
                delta = mNextSelectedPosition - mSelectedPosition;
            }

            // Caution: newSel might be null
            newSel = getChildAt(index + delta);
        }


        boolean dataChanged = mDataChanged;
        if (dataChanged) {
            handleDataChanged();
        }

        // Handle the empty set by removing all views that are visible
        // and calling it a day
        if (mItemCount == 0) {
            resetList();
            invokeOnItemScrollListener();
            return;
        } else if (mItemCount != mAdapter.getCount()) {
            throw new IllegalStateException("The content of the adapter has changed but "
                    + "ListView did not receive a notification. Make sure the content of "
                    + "your adapter is not modified from a background thread, but only from "
                    + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
                    + "when its content changes. [in ListView(" + getId() + ", " + getClass()
                    + ") with Adapter(" + mAdapter.getClass() + ")]");
        }

        setSelectedPositionInt(mNextSelectedPosition);

        AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
        View accessibilityFocusLayoutRestoreView = null;
        int accessibilityFocusPosition = INVALID_POSITION;

        // Remember which child, if any, had accessibility focus. This must
        // occur before recycling any views, since that will clear
        // accessibility focus.
        final ViewRootImpl viewRootImpl = getViewRootImpl();
        if (viewRootImpl != null) {
            final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
            if (focusHost != null) {
                final View focusChild = getAccessibilityFocusedChild(focusHost);
                if (focusChild != null) {
                    if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
                            || focusChild.hasTransientState() || mAdapterHasStableIds) {
                        // The views won't be changing, so try to maintain // focus on the current host and virtual view. accessibilityFocusLayoutRestoreView = focusHost; accessibilityFocusLayoutRestoreNode = viewRootImpl .getAccessibilityFocusedVirtualView(); } // If all else fails, maintain focus at the same // position. accessibilityFocusPosition = getPositionForView(focusChild); } } } View focusLayoutRestoreDirectChild = null; View focusLayoutRestoreView = null; // Take focus back to us temporarily to avoid the eventual call to // clear focus when removing the focused child below from messing // things up when ViewAncestor assigns focus back to someone else. final View focusedChild = getFocusedChild(); if (focusedChild != null) { // TODO: in some cases focusedChild.getParent() == null // We can remember the focused view to restore after re-layout // if the data hasn't changed, or if the focused position is a
            // header or footer.
            if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)
                    || focusedChild.hasTransientState() || mAdapterHasStableIds) {
                focusLayoutRestoreDirectChild = focusedChild;
                // Remember the specific view that had focus.
                focusLayoutRestoreView = findFocus();
                if (focusLayoutRestoreView != null) {
                    // Tell it we are going to mess with it.
                    focusLayoutRestoreView.onStartTemporaryDetach();
                }
            }
            requestFocus();
        }
        
        // Pull all children into the RecycleBin.
        // These views will be reused if possible
        final int firstPosition = mFirstPosition;
        final RecycleBin recycleBin = mRecycler;
        // 只有在調用adapter.notifyDatasetChanged()方法一直到layout()佈局結束,
        // dataChanged爲true,默認爲false
        if (dataChanged) {
            // dataChanged爲true,說明當前listview是有數據的了,把當前全部的item view
            // 存放到RecycleBin對象的mScrapViews中保存
            for (int i = 0; i < childCount; i++) {
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            // dataChanged默認爲false,第一次執行此方法走這裏
            recycleBin.fillActiveViews(childCount, firstPosition);
        }

        // Clear out old views
        // 清除當前listview全部的子view
        detachAllViewsFromParent();
        recycleBin.removeSkippedScrap();

        // 在咱們調用listview.setAdapter()時候,已經將mLayoutMode = LAYOUT_NORMAL;
        // 因此一般狀況下可認爲mLayoutMode == LAYOUT_NORMAL
        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            if (newSel != null) {
                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
            } else {
                sel = fillFromMiddle(childrenTop, childrenBottom);
            }
            break;
        case LAYOUT_SYNC:
            sel = fillSpecific(mSyncPosition, mSpecificTop);
            break;
        case LAYOUT_FORCE_BOTTOM:
            sel = fillUp(mItemCount - 1, childrenBottom);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_FORCE_TOP:
            mFirstPosition = 0;
            sel = fillFromTop(childrenTop);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_SPECIFIC:
            sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
            break;
        case LAYOUT_MOVE_SELECTION:
            sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
            break;
        default:
            // 一般狀況都走這裏
            if (childCount == 0) {
                // listview第一次佈局childCount必然爲0走這裏
                if (!mStackFromBottom) {
                    // 一般咱們沒有外部調用listview.setStackFromBottom()
                    // 成員變量mStackFromBottom均爲false都走這裏
                    final int position = lookForSelectablePosition(0, true);
                    setSelectedPositionInt(position);
                    // 從上到上佈局listview能顯示得下的子view
                    sel = fillFromTop(childrenTop);
                } else {
                    final int position = lookForSelectablePosition(mItemCount - 1, false);
                    setSelectedPositionInt(position);
                    sel = fillUp(mItemCount - 1, childrenBottom);
                }
            } else {
                // 非第一次layout,以前記錄的存在的子view個數childCount不爲0
                // 包括兩種狀況:1.listview首次佈局中的第二次執行的onlayout();
                // 2.在後續listview已經顯示存在子view而後數據改變時候調用
                // adapter.nitifyDatasetChanged()方法時候
                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                    sel = fillSpecific(mSelectedPosition,
                            oldSel == null ? childrenTop : oldSel.getTop());
                } else if (mFirstPosition < mItemCount) {
                    // 一般狀況走這裏,fillSpecific()會調用fillUp()和fillDown()佈局子view
                    sel = fillSpecific(mFirstPosition,
                            oldFirst == null ? childrenTop : oldFirst.getTop());
                } else {
                    sel = fillSpecific(0, childrenTop);
                }
            }
            break;
        }

        // Flush any cached views that did not get reused above
        // 至此,listview已經佈局完成可以顯示得下的子view,將recycleBin可能剩餘的
        // mActiveViews中view移動到mScrapViews以便於listview滑動時候複用
        recycleBin.scrapActiveViews();

        if (sel != null) {
            // The current selected item should get focus if items are
            // focusable.
            if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
                final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
                        focusLayoutRestoreView != null &&
                        focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
                if (!focusWasTaken) {
                    // Selected item didn't take focus, but we still want to // make sure something else outside of the selected view // has focus. final View focused = getFocusedChild(); if (focused != null) { focused.clearFocus(); } positionSelector(INVALID_POSITION, sel); } else { sel.setSelected(false); mSelectorRect.setEmpty(); } } else { positionSelector(INVALID_POSITION, sel); } mSelectedTop = sel.getTop(); } else { final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING; if (inTouchMode) { // If the user's finger is down, select the motion position.
                final View child = getChildAt(mMotionPosition - mFirstPosition);
                if (child != null) {
                    positionSelector(mMotionPosition, child);
                }
            } else if (mSelectorPosition != INVALID_POSITION) {
                // If we had previously positioned the selector somewhere,
                // put it back there. It might not match up with the data,
                // but it's transitioning out so it's not a big deal.
                final View child = getChildAt(mSelectorPosition - mFirstPosition);
                if (child != null) {
                    positionSelector(mSelectorPosition, child);
                }
            } else {
                // Otherwise, clear selection.
                mSelectedTop = 0;
                mSelectorRect.setEmpty();
            }

            // Even if there is not selected position, we may need to
            // restore focus (i.e. something focusable in touch mode).
            if (hasFocus() && focusLayoutRestoreView != null) {
                focusLayoutRestoreView.requestFocus();
            }
        }

        // Attempt to restore accessibility focus, if necessary.
        if (viewRootImpl != null) {
            final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
            if (newAccessibilityFocusedView == null) {
                if (accessibilityFocusLayoutRestoreView != null
                        && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
                    final AccessibilityNodeProvider provider =
                            accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
                    if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
                        final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
                                accessibilityFocusLayoutRestoreNode.getSourceNodeId());
                        provider.performAction(virtualViewId,
                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
                    } else {
                        accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
                    }
                } else if (accessibilityFocusPosition != INVALID_POSITION) {
                    // Bound the position within the visible children.
                    final int position = MathUtils.constrain(
                            accessibilityFocusPosition - mFirstPosition, 0,
                            getChildCount() - 1);
                    final View restoreView = getChildAt(position);
                    if (restoreView != null) {
                        restoreView.requestAccessibilityFocus();
                    }
                }
            }
        }

        // Tell focus view we are done mucking with it, if it is still in
        // our view hierarchy.
        if (focusLayoutRestoreView != null
                && focusLayoutRestoreView.getWindowToken() != null) {
            focusLayoutRestoreView.onFinishTemporaryDetach();
        }
        
        // 佈局完成以後的操做
        mLayoutMode = LAYOUT_NORMAL;
        mDataChanged = false;
        if (mPositionScrollAfterLayout != null) {
            post(mPositionScrollAfterLayout);
            mPositionScrollAfterLayout = null;
        }
        mNeedSync = false;
        setNextSelectedPositionInt(mSelectedPosition);

        updateScrollIndicators();

        if (mItemCount > 0) {
            checkSelectionChanged();
        }

        invokeOnItemScrollListener();
    } finally {
        if (!blockLayoutRequests) {
            mBlockLayoutRequests = false;
        }
    }
}

複製代碼

RecycleBin是AbsListView的一個非靜態內部類,主要有一個數組成員變量View[] mActiveViews 和 ArrayList [] mScrapViews.mActiveViews存放的是當前ListView能夠使用的待激活的子item view,而mScrapViews存放的是在ListView滑動過程當中滑出屏幕來回收以便下次利用的子item view 數組

onInterceptTouchEvent():

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    final int actionMasked = ev.getActionMasked();
    View v;

    if (mPositionScroller != null) {
        mPositionScroller.stop();
    }

    if (mIsDetaching || !isAttachedToWindow()) {
        // Something isn't right. // Since we rely on being attached to get data set change notifications, // don't risk doing anything where we might try to resync and find things
        // in a bogus state.
        return false;
    }

    if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
        return true;
    }

    switch (actionMasked) {
    case MotionEvent.ACTION_DOWN: {
        int touchMode = mTouchMode;
        // 若是手指放下時候 listview正出於fling滾動狀態或者OVERSCROLL,則立刻攔截事件交
        // 由自身ontouchEvent()處理
        if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
            mMotionCorrection = 0;
            return true;
        }

        final int x = (int) ev.getX();
        final int y = (int) ev.getY();
        mActivePointerId = ev.getPointerId(0);
        // 獲取當前觸摸事件在對應的item view的positon,在listview中實現了該方法
        int motionPosition = findMotionRow(y);
        if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
            // User clicked on an actual view (and was not stopping a fling).
            // Remember where the motion event started
            v = getChildAt(motionPosition - mFirstPosition);
            mMotionViewOriginalTop = v.getTop();
            mMotionX = x;
            // 記錄down事件時候的mMotionY初始值
            mMotionY = y;
            mMotionPosition = motionPosition;
            // 設置觸摸事件模式爲TOUCH_MODE_DOWN
            mTouchMode = TOUCH_MODE_DOWN;
            clearScrollingCache();
        }
        // 初試設置down事件時候mLastY值
        mLastY = Integer.MIN_VALUE;
        initOrResetVelocityTracker();
        mVelocityTracker.addMovement(ev);
        mNestedYOffset = 0;
        startNestedScroll(SCROLL_AXIS_VERTICAL);
        if (touchMode == TOUCH_MODE_FLING) {
            return true;
        }
        break;
    }

    case MotionEvent.ACTION_MOVE: {
        switch (mTouchMode) {
        case TOUCH_MODE_DOWN:
            int pointerIndex = ev.findPointerIndex(mActivePointerId);
            if (pointerIndex == -1) {
                pointerIndex = 0;
                mActivePointerId = ev.getPointerId(pointerIndex);
            }
            final int y = (int) ev.getY(pointerIndex);
            initVelocityTrackerIfNotExists();
            mVelocityTracker.addMovement(ev);
            // 判斷是否攔截事件本身處理,此爲onInterceptTouchEvent()方法核心代碼
            if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
                return true;
            }
            break;
        }
        break;
    }

    case MotionEvent.ACTION_CANCEL:
    case MotionEvent.ACTION_UP: {
        // touchMode恢復默認狀態
        mTouchMode = TOUCH_MODE_REST;
        mActivePointerId = INVALID_POINTER;
        // 回收速度VelocityTracker相關資源
        recycleVelocityTracker();
        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
        stopNestedScroll();
        break;
    }

    case MotionEvent.ACTION_POINTER_UP: {
        onSecondaryPointerUp(ev);
        break;
    }
    }

    return false;
}


// 這個方法在onInterceptTouchEvent的move事件中調用,在onTouchEvent()的onTouchMove()方法
// 中開始時候也會調用
private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
    // Check if we have moved far enough that it looks more like a
    // scroll than a tap
    // 獲得當前事件的y值與down事件時候設置的值的差值
    final int deltaY = y - mMotionY;
    final int distance = Math.abs(deltaY);
    // mScrollY!=0即overscroll爲true ,核心爲distance > mTouchSlop即攔截事件本身處理
    // mTouchSlop在構造函數中初始化並賦值了
    final boolean overscroll = mScrollY != 0;
    if ((overscroll || distance > mTouchSlop) &&
            (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
        createScrollingCache();
        if (overscroll) {
            mTouchMode = TOUCH_MODE_OVERSCROLL;
            mMotionCorrection = 0;
        } else {
            // 設置觸摸模式爲TOUCH_MODE_SCROLL,在onTouchEvent()用到
            mTouchMode = TOUCH_MODE_SCROLL;
            mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
        }
        // 取消子view的長按監聽觸發
        removeCallbacks(mPendingCheckForLongPress);
        setPressed(false);
        final View motionView = getChildAt(mMotionPosition - mFirstPosition);
        // listview攔截了事件自己處理,因此恢復可能設置子view的press狀態
        if (motionView != null) {
            motionView.setPressed(false);
        }
        // 通知ScrollState狀態變化回調
        reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
        // Time to start stealing events! Once we've stolen them, don't let anyone
        // steal from us
        final ViewParent parent = getParent();
        if (parent != null) {
            parent.requestDisallowInterceptTouchEvent(true);
        }
        // 做用如名,若是知足條件,滾動listview
        scrollIfNeeded(x, y, vtev);
        return true;
    }

    return false;
}

onTouchEvent()之onTouchDown():
private void onTouchDown(MotionEvent ev) {
    mActivePointerId = ev.getPointerId(0);

    if (mTouchMode == TOUCH_MODE_OVERFLING) {
        // Stopped the fling. It is a scroll.
        // 若是已經正在出於TOUCH_MODE_OVERFLING則down事件瞬間中斷fling
        mFlingRunnable.endFling();
        if (mPositionScroller != null) {
            mPositionScroller.stop();
        }
        mTouchMode = TOUCH_MODE_OVERSCROLL;
        mMotionX = (int) ev.getX();
        mMotionY = (int) ev.getY();
        mLastY = mMotionY;
        mMotionCorrection = 0;
        mDirection = 0;
    } else {
        final int x = (int) ev.getX();
        final int y = (int) ev.getY();
        int motionPosition = pointToPosition(x, y);

        if (!mDataChanged) {
            if (mTouchMode == TOUCH_MODE_FLING) {
                // Stopped a fling. It is a scroll.
                createScrollingCache();
                mTouchMode = TOUCH_MODE_SCROLL;     
                mMotionCorrection = 0;
                motionPosition = findMotionRow(y);
                // 根據y速度判斷是否立刻中斷fling
                mFlingRunnable.flywheelTouch();
            } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
                // User clicked on an actual view (and was not stopping a
                // fling). It might be a click or a scroll. Assume it is a
                // click until proven otherwise.
                // 設置touch模式爲TOUCH_MODE_DOWN
                mTouchMode = TOUCH_MODE_DOWN;

                // FIXME Debounce
                if (mPendingCheckForTap == null) {
                    mPendingCheckForTap = new CheckForTap();
                }

                mPendingCheckForTap.x = ev.getX();
                mPendingCheckForTap.y = ev.getY();
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
            }
        }

        if (motionPosition >= 0) {
            // Remember where the motion event started
            final View v = getChildAt(motionPosition - mFirstPosition);
            mMotionViewOriginalTop = v.getTop();
        }

        mMotionX = x;
        // 記錄mMotionY
        mMotionY = y; 
        mMotionPosition = motionPosition;
        // 記錄mLastY = Integer.MIN_VALUE
        mLastY = Integer.MIN_VALUE;
    }

    if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
            && performButtonActionOnTouchDown(ev)) {
            removeCallbacks(mPendingCheckForTap);
    }
}

複製代碼

onTouchEvent()之onTouchMove():

private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
    int pointerIndex = ev.findPointerIndex(mActivePointerId);
    if (pointerIndex == -1) {
        pointerIndex = 0;
        mActivePointerId = ev.getPointerId(pointerIndex);
    }

    if (mDataChanged) {
        // Re-sync everything if data has been changed
        // since the scroll operation can query the adapter.
        layoutChildren();
    }

    final int y = (int) ev.getY(pointerIndex);

    switch (mTouchMode) {
        // 剛開始進入這裏 touchMode爲TOUCH_MODE_DOWN
        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. If so, we'll enter scrolling mode. // 剛開始還麼有處於可滾動狀態,故進入判斷是否能夠滾動,核心判斷調節爲固然事件y // 與down事件mMotionY值的差值絕對值是否大於mTouchSlop if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) { break; } // Otherwise, check containment within list bounds. If we're
            // outside bounds, cancel any active presses.
            final View motionView = getChildAt(mMotionPosition - mFirstPosition);
            final float x = ev.getX(pointerIndex);
            if (!pointInView(x, y, mTouchSlop)) {
                setPressed(false);
                if (motionView != null) {
                    motionView.setPressed(false);
                }
                removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
                        mPendingCheckForTap : mPendingCheckForLongPress);
                mTouchMode = TOUCH_MODE_DONE_WAITING;
                updateSelectorState();
            } else if (motionView != null) {
                // Still within bounds, update the hotspot.
                final float[] point = mTmpPoint;
                point[0] = x;
                point[1] = y;
                transformPointToViewLocal(point, motionView);
                motionView.drawableHotspotChanged(point[0], point[1]);
            }
            break;
        case TOUCH_MODE_SCROLL:
        case TOUCH_MODE_OVERSCROLL:
            // 若是已經進入到listview的滾動狀態,則直接執行scrollIfNeeded根據條件判斷是否
            // 進行滾動
            scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
            break;
    }
}


 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
    int rawDeltaY = y - mMotionY;
    int scrollOffsetCorrection = 0;
    int scrollConsumedCorrection = 0;
    // mLastY==Integer.MIN_VALUE代表剛達到條件進入滾動狀態
    if (mLastY == Integer.MIN_VALUE) {
        // 保證了狀態過分時候平穩滾動
        rawDeltaY -= mMotionCorrection;
    }
    if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
            mScrollConsumed, mScrollOffset)) {
        rawDeltaY += mScrollConsumed[1];
        scrollOffsetCorrection = -mScrollOffset[1];
        scrollConsumedCorrection = mScrollConsumed[1];
        if (vtev != null) {
            vtev.offsetLocation(0, mScrollOffset[1]);
            mNestedYOffset += mScrollOffset[1];
        }
    }
    final int deltaY = rawDeltaY;
    // 兩次連續下發事件的y差值.若是mLastY==Integer.MIN_VALUE代表剛達到條件進入滾動狀態
    // 此時incrementalDeltaY = rawDeltaY,而rawDeltaY已經在上面進行了
    // (rawDeltaY -= mMotionCorrection),保證了平穩過分
    int incrementalDeltaY =
            mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
    int lastYCorrection = 0;
    if (mTouchMode == TOUCH_MODE_SCROLL) {
        if (PROFILE_SCROLLING) {
            if (!mScrollProfilingStarted) {
                Debug.startMethodTracing("AbsListViewScroll");
                mScrollProfilingStarted = true;
            }
        }

        if (mScrollStrictSpan == null) {
            // If it's non-null, we're already in a scroll.
            mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
        }

        if (y != mLastY) {
            // We may be here after stopping a fling and continuing to scroll.
            // If so, we haven't disallowed intercepting touch events yet. // Make sure that we do so in case we're in a parent that can intercept.
            if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
                    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; } int motionViewPrevTop = 0; View motionView = this.getChildAt(motionIndex); if (motionView != null) { motionViewPrevTop = motionView.getTop(); } // No need to do all this work if we're not going to move anyway
            boolean atEdge = false;
            // trackMotionScroll()方法真正進行滾動處理
            if (incrementalDeltaY != 0) {
                atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
            }

            // Check to see if we have bumped into the scroll limit
            motionView = this.getChildAt(motionIndex);
            if (motionView != null) {
                // Check if the top of the motion view is where it is
                // supposed to be
                final int motionViewRealTop = motionView.getTop();
                // 滾動到最頂部或者最底部
                if (atEdge) {
                    // Apply overscroll

                    int overscroll = -incrementalDeltaY -
                            (motionViewRealTop - motionViewPrevTop);
                    if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
                            mScrollOffset)) {
                        lastYCorrection -= mScrollOffset[1];
                        if (vtev != null) {
                            vtev.offsetLocation(0, mScrollOffset[1]);
                            mNestedYOffset += mScrollOffset[1];
                        }
                    } else {
                        final boolean atOverscrollEdge = overScrollBy(0, overscroll,
                                0, mScrollY, 0, 0, 0, mOverscrollDistance, true);

                        if (atOverscrollEdge && mVelocityTracker != null) {
                            // Don't allow overfling if we're at the edge
                            mVelocityTracker.clear();
                        }

                        final int overscrollMode = getOverScrollMode();
                        if (overscrollMode == OVER_SCROLL_ALWAYS ||
                                (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
                                        !contentFits())) {
                            if (!atOverscrollEdge) {
                                mDirection = 0; // Reset when entering overscroll.
                                mTouchMode = TOUCH_MODE_OVERSCROLL;
                            }
                            if (incrementalDeltaY > 0) {
                                // 頂部 OVER_SCROLL效果,draw()方法中實現
                                mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
                                        (float) x / getWidth());
                                if (!mEdgeGlowBottom.isFinished()) {
                                    mEdgeGlowBottom.onRelease();
                                }
                                invalidateTopGlow();
                            } else if (incrementalDeltaY < 0) {
                                // 底部 OVER_SCROLL效果,draw()方法中實現
                                mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
                                        1.f - (float) x / getWidth());
                                if (!mEdgeGlowTop.isFinished()) {
                                    mEdgeGlowTop.onRelease();
                                }
                                invalidateBottomGlow();
                            }
                        }
                    }
                }
                mMotionY = y + lastYCorrection + scrollOffsetCorrection;
            }
            // 記錄當前事件的y
            mLastY = y + lastYCorrection + scrollOffsetCorrection;
        }
    }else if (mTouchMode == TOUCH_MODE_OVERSCROLL){.....}
複製代碼
複製代碼
複製代碼
相關文章
相關標籤/搜索