android高级ui之ListView源码分析

ListView源码分析

项目中使用ListView还是挺多的,之前看过几次,很是容易遗忘,今特做记录如下

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

主要从以下几点分析

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

简要

ListView继承之AbsListView抽象类,所以大部分分析的源码都在这两个类中

构造函数初始化过程:

父类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方式在此实现

父类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){.....}
复制代码

复制代码

复制代码

转载于:https://juejin.im/post/5c7f6a7c518825407b2b6063

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值