深入理解ListView(2) — 一天一点源码

总述

AbsListView,ListView的父类,继承了AdapterView,并且实现了TextWatcher接口。

前言


/**
 * Base class that can be used to implement virtualized lists of items. A list does
 * not have a spatial definition here. For instance, subclases of this class can
 * display the content of the list in a grid, in a carousel, as stack, etc. 
 */

可用于实现项虚拟列表基类。没有一个明确定义的的样式空间。子类可以以网格形式,圆形形式,栈列形式等等,来显示列表的内容。

内容


public interface OnScrollListener {

        public static int SCROLL_STATE_FLING = 2;

        public void onScrollStateChanged(AbsListView view, int scrollState);

        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                int totalItemCount);
}

内部定义的滑动监听接口,用于监听视图的滑动情况,具体实现,需重写其两个方法。当列表或网格滑动时调用。


public interface SelectionBoundsAdjuster {

        public void adjustListItemSelectionBounds(Rect bounds);
    }

内部定义的调整选择范围的接口,列表的顶级视图可以实现此接口。需要修改顶级视图的可选范围时调用,可以实现对该视图的可选择范围的修改。


public AbsListView(Context context) {
        super(context);
        initAbsListView();

        mOwnerThread = Thread.currentThread();

        setVerticalScrollBarEnabled(true);
        TypedArray a = context.obtainStyledAttributes(R.styleable.View);
        initializeScrollbarsInternal(a);
        a.recycle();
    }

    public AbsListView(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.absListViewStyle);
    }

    public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

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

        mOwnerThread = Thread.currentThread();

        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);

        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
        setClickable(true);
        setFocusableInTouchMode(true);
        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;
    }

AbsListView的四个构造方法,以及AbsListView的视图初始化。initAbsListView初始化构建了AbsListView的一些属性值。



    public int getCheckedItemCount() {
        return mCheckedItemCount;
    }

获得选中条目的数量。


    public boolean isItemChecked(int position) {
        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
            return mCheckStates.get(position);
        }

        return false;
    }

传入一个位置position,判断该位置的子条目是否被选中。


    public int getCheckedItemPosition() {
        if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
            return mCheckStates.keyAt(0);
        }

        return INVALID_POSITION;
    }

获得被选中的子条目的位置position。



    public SparseBooleanArray getCheckedItemPositions() {
        if (mChoiceMode != CHOICE_MODE_NONE) {
            return mCheckStates;
        }
        return null;
    }

获得列表中被选中的子条目的集合。


    public long[] getCheckedItemIds() {
        if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
            return new long[0];
        }

        final LongSparseArray idStates = mCheckedIdStates;
        final int count = idStates.size();
        final long[] ids = new long[count];

        for (int i = 0; i < count; i++) {
            ids[i] = idStates.keyAt(i);
        }

        return ids;
    }

获得列表中所有被选中的子条目的位置数组。



    public void clearChoices() {
        if (mCheckStates != null) {
            mCheckStates.clear();
        }
        if (mCheckedIdStates != null) {
            mCheckedIdStates.clear();
        }
        mCheckedItemCount = 0;
    }

清空所有子条目的选择状态,将被选中子条目的数量重置为0。


    public void setItemChecked(int position, boolean value) {
        if (mChoiceMode == CHOICE_MODE_NONE) {
            return;
        }

        // Start selection mode if needed. We don't need to if we're unchecking something.
        if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
            if (mMultiChoiceModeCallback == null ||
                    !mMultiChoiceModeCallback.hasWrappedCallback()) {
                throw new IllegalStateException("AbsListView: attempted to start selection mode " +
                        "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
                        "supplied. Call setMultiChoiceModeListener to set a callback.");
            }
            mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
        }

        final boolean itemCheckChanged;
        if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
            boolean oldValue = mCheckStates.get(position);
            mCheckStates.put(position, value);
            if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
                if (value) {
                    mCheckedIdStates.put(mAdapter.getItemId(position), position);
                } else {
                    mCheckedIdStates.delete(mAdapter.getItemId(position));
                }
            }
            itemCheckChanged = oldValue != value;
            if (itemCheckChanged) {
                if (value) {
                    mCheckedItemCount++;
                } else {
                    mCheckedItemCount--;
                }
            }
            if (mChoiceActionMode != null) {
                final long id = mAdapter.getItemId(position);
                mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
                        position, id, value);
            }
        } else {
            boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
            // Clear all values if we're checking something, or unchecking the currently
            // selected item
            itemCheckChanged = isItemChecked(position) != value;
            if (value || isItemChecked(position)) {
                mCheckStates.clear();
                if (updateIds) {
                    mCheckedIdStates.clear();
                }
            }
            // this may end up selecting the value we just cleared but this way
            // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
            if (value) {
                mCheckStates.put(position, true);
                if (updateIds) {
                    mCheckedIdStates.put(mAdapter.getItemId(position), position);
                }
                mCheckedItemCount = 1;
            } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
                mCheckedItemCount = 0;
            }
        }

        // Do not generate a data change while we are in the layout phase or data has not changed
        if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) {
            mDataChanged = true;
            rememberSyncState();
            requestLayout();
        }
    }

设定指定位置的选中状态。


private void updateOnScreenCheckedViews() {
        final int firstPos = mFirstPosition;
        final int count = getChildCount();
        final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
                >= android.os.Build.VERSION_CODES.HONEYCOMB;
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            final int position = firstPos + i;

            if (child instanceof Checkable) {
                ((Checkable) child).setChecked(mCheckStates.get(position));
            } else if (useActivated) {
                child.setActivated(mCheckStates.get(position));
            }
        }
    }

快速,就地对当前所有可见的子条目视图执行更新操作,即对其被选中和激活状态的更新。
只有当有效选择模式处于活跃状态时,才调用此方法。


public int getChoiceMode() {
    return mChoiceMode;
}

获得当前选择模式。


public void setChoiceMode(int choiceMode) {
        mChoiceMode = choiceMode;
        if (mChoiceActionMode != null) {
            mChoiceActionMode.finish();
            mChoiceActionMode = null;
        }
        if (mChoiceMode != CHOICE_MODE_NONE) {
            if (mCheckStates == null) {
                mCheckStates = new SparseBooleanArray(0);
            }
            if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
                mCheckedIdStates = new LongSparseArray(0);
            }
            // Modal multi-choice mode only has choices when the mode is active. Clear them.
            if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
                clearChoices();
                setLongClickable(true);
            }
        }
    }

设置当前选择模式。


public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
        if (mMultiChoiceModeCallback == null) {
            mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
        }
        mMultiChoiceModeCallback.setWrapped(listener);
    }

设置多种模式选择的监听器,将对模式的设置和转换进行管理。


private boolean contentFits() {
        final int childCount = getChildCount();
        if (childCount == 0) return true;
        if (childCount != mItemCount) return false;

        return getChildAt(0).getTop() >= mListPadding.top &&
                getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
    }

检测是否内容的规范,如果所有的列表内容都符合视图边界,则返回true。


public void setFastScrollEnabled(final boolean enabled) {
        if (mFastScrollEnabled != enabled) {
            mFastScrollEnabled = enabled;

            if (isOwnerThread()) {
                setFastScrollerEnabledUiThread(enabled);
            } else {
                post(new Runnable() {
                    @Override
                    public void run() {
                        setFastScrollerEnabledUiThread(enabled);
                    }
                });
            }
        }
    }

设置是否启用快速滚动,或者禁用。源码中还有一些关于快速滑动的Ui显示设置,以及属性设置,在此不多赘述。


private boolean isOwnerThread() {
        return mOwnerThread == Thread.currentThread();
    }

判断当前线程是否是视图的创建线程。


public void setSmoothScrollbarEnabled(boolean enabled) {
        mSmoothScrollbarEnabled = enabled;
    }

设置慢速滑动是否启用。源码中还有一些关于慢速滑动的Ui显示设置,以及属性设置,在此不多赘述。


void invokeOnItemScrollListener() {
        if (mFastScroll != null) {
            mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
        }
        if (mOnScrollListener != null) {
            mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
        }
        onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
    }

如果改变滑动监听的状态,调用此方法进行修改。


int getSelectionModeForAccessibility() {
        final int choiceMode = getChoiceMode();
        switch (choiceMode) {
            case CHOICE_MODE_NONE:
                return CollectionInfo.SELECTION_MODE_NONE;
            case CHOICE_MODE_SINGLE:
                return CollectionInfo.SELECTION_MODE_SINGLE;
            case CHOICE_MODE_MULTIPLE:
            case CHOICE_MODE_MULTIPLE_MODAL:
                return CollectionInfo.SELECTION_MODE_MULTIPLE;
            default:
                return CollectionInfo.SELECTION_MODE_NONE;
        }
    }

获得当前选择的访问模式。


public void setScrollingCacheEnabled(boolean enabled) {
        if (mScrollingCacheEnabled && !enabled) {
            clearScrollingCache();
        }
        mScrollingCacheEnabled = enabled;
    }

设置是否对子视图的绘制进行缓存。


    public void setTextFilterEnabled(boolean textFilterEnabled) {
        mTextFilterEnabled = textFilterEnabled;
    }

设置文本过滤器状态。


    private void useDefaultSelector() {
        setSelector(getContext().getDrawable(
                com.android.internal.R.drawable.list_selector_background));
    }

使用默认的选择器。


    static class SavedState extends BaseSavedState {
        long selectedId;
        long firstId;
        int viewTop;
        int position;
        int height;
        String filter;
        boolean inActionMode;
        int checkedItemCount;
        SparseBooleanArray checkState;
        LongSparseArray checkIdState;

        /**
         * Constructor called from {@link AbsListView#onSaveInstanceState()}
         */
        SavedState(Parcelable superState) {
            super(superState);
        }

        /**
         * Constructor called from {@link #CREATOR}
         */
        private SavedState(Parcel in) {
            super(in);
            selectedId = in.readLong();
            firstId = in.readLong();
            viewTop = in.readInt();
            position = in.readInt();
            height = in.readInt();
            filter = in.readString();
            inActionMode = in.readByte() != 0;
            checkedItemCount = in.readInt();
            checkState = in.readSparseBooleanArray();
            final int N = in.readInt();
            if (N > 0) {
                checkIdState = new LongSparseArray();
                for (int i=0; ilong key = in.readLong();
                    final int value = in.readInt();
                    checkIdState.put(key, value);
                }
            }
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeLong(selectedId);
            out.writeLong(firstId);
            out.writeInt(viewTop);
            out.writeInt(position);
            out.writeInt(height);
            out.writeString(filter);
            out.writeByte((byte) (inActionMode ? 1 : 0));
            out.writeInt(checkedItemCount);
            out.writeSparseBooleanArray(checkState);
            final int N = checkIdState != null ? checkIdState.size() : 0;
            out.writeInt(N);
            for (int i=0; iout.writeLong(checkIdState.keyAt(i));
                out.writeInt(checkIdState.valueAt(i));
            }
        }

        @Override
        public String toString() {
            return "AbsListView.SavedState{"
                    + Integer.toHexString(System.identityHashCode(this))
                    + " selectedId=" + selectedId
                    + " firstId=" + firstId
                    + " viewTop=" + viewTop
                    + " position=" + position
                    + " height=" + height
                    + " filter=" + filter
                    + " checkState=" + checkState + "}";
        }

        public static final Parcelable.Creator CREATOR
                = new Parcelable.Creator() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

一个静态的内部类,用于存储快照信息。


    private boolean acceptFilter() {
        return mTextFilterEnabled && getAdapter() instanceof Filterable &&
                ((Filterable) getAdapter()).getFilter() != null;
    }

判断过滤器是否被接受。


public void setFilterText(String filterText) {
        // TODO: Should we check for acceptFilter()?
        if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
            createTextFilter(false);
            // This is going to call our listener onTextChanged, but we might not
            // be ready to bring up a window yet
            mTextFilter.setText(filterText);
            mTextFilter.setSelection(filterText.length());
            if (mAdapter instanceof Filterable) {
                // if mPopup is non-null, then onTextChanged will do the filtering
                if (mPopup == null) {
                    Filter f = ((Filterable) mAdapter).getFilter();
                    f.filter(filterText);
                }
                // Set filtered to true so we will display the filter window when our main
                // window is ready
                mFiltered = true;
                mDataSetObserver.clearSavedState();
            }
        }
    }

设置过滤器文本信息。

    void resetList() {
        removeAllViewsInLayout();
        mFirstPosition = 0;
        mDataChanged = false;
        mPositionScrollAfterLayout = null;
        mNeedSync = false;
        mPendingSync = null;
        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;
        setSelectedPositionInt(INVALID_POSITION);
        setNextSelectedPositionInt(INVALID_POSITION);
        mSelectedTop = 0;
        mSelectorPosition = INVALID_POSITION;
        mSelectorRect.setEmpty();
        invalidate();
    }

如果列表为空,清空所有数据,均设置为无效值。


    View getAccessibilityFocusedChild(View focusedView) {
        ViewParent viewParent = focusedView.getParent();
        while ((viewParent instanceof View) && (viewParent != this)) {
            focusedView = (View) viewParent;
            viewParent = viewParent.getParent();
        }

        if (!(viewParent instanceof View)) {
            return null;
        }

        return focusedView;
    }

获得获取焦点的子视图。


   View obtainView(int position, boolean[] outMetadata) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        outMetadata[0] = false;

        // Check whether we have a transient state view. Attempt to re-bind the
        // data and discard the view if we fail.
        final View transientView = mRecycler.getTransientStateView(position);
        if (transientView != null) {
            final LayoutParams params = (LayoutParams) transientView.getLayoutParams();

            // If the view type hasn't changed, attempt to re-bind the data.
            if (params.viewType == mAdapter.getItemViewType(position)) {
                final View updatedView = mAdapter.getView(position, transientView, this);

                // If we failed to re-bind the data, scrap the obtained view.
                if (updatedView != transientView) {
                    setItemViewLayoutParams(updatedView, position);
                    mRecycler.addScrapView(updatedView, position);
                }
            }

            outMetadata[0] = true;

            // Finish the temporary detach started in addScrapView().
            transientView.dispatchFinishTemporaryDetach();
            return transientView;
        }

获得一个视图去显示,只有当我们发现之前的视图不可用时调用,这个方法会创建一个新的视图,或者复用之前可以复用的视图。


    class ListItemAccessibilityDelegate extends AccessibilityDelegate {
        @Override
        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
            super.onInitializeAccessibilityNodeInfo(host, info);

            final int position = getPositionForView(host);
            onInitializeAccessibilityNodeInfoForItem(host, position, info);
        }

        @Override
        public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
            if (super.performAccessibilityAction(host, action, arguments)) {
                return true;
            }

            final int position = getPositionForView(host);
            if (position == INVALID_POSITION || mAdapter == null) {
                // Cannot perform actions on invalid items.
                return false;
            }

            if (position >= mAdapter.getCount()) {
                // The position is no longer valid, likely due to a data set
                // change. We could fail here for all data set changes, since
                // there is a chance that the data bound to the view may no
                // longer exist at the same position within the adapter, but
                // it's more consistent with the standard touch interaction to
                // click at whatever may have moved into that position.
                return false;
            }

            final boolean isItemEnabled;
            final ViewGroup.LayoutParams lp = host.getLayoutParams();
            if (lp instanceof AbsListView.LayoutParams) {
                isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
            } else {
                isItemEnabled = false;
            }

            if (!isEnabled() || !isItemEnabled) {
                // Cannot perform actions on disabled items.
                return false;
            }

            switch (action) {
                case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
                    if (getSelectedItemPosition() == position) {
                        setSelection(INVALID_POSITION);
                        return true;
                    }
                } return false;
                case AccessibilityNodeInfo.ACTION_SELECT: {
                    if (getSelectedItemPosition() != position) {
                        setSelection(position);
                        return true;
                    }
                } return false;
                case AccessibilityNodeInfo.ACTION_CLICK: {
                    if (isItemClickable(host)) {
                        final long id = getItemIdAtPosition(position);
                        return performItemClick(host, position, id);
                    }
                } return false;
                case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
                    if (isLongClickable()) {
                        final long id = getItemIdAtPosition(position);
                        return performLongPress(host, position, id);
                    }
                } return false;
            }

            return false;
        }
    }

一个内部类,处理列表子视图访问模式授权。


    private boolean isItemClickable(View view) {
        return !view.hasFocusable();
    }

判断子视图是否可点击。


    void handleBoundsChange() {
        final int childCount = getChildCount();
        if (childCount > 0) {
            mDataChanged = true;
            rememberSyncState();
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                final ViewGroup.LayoutParams lp = child.getLayoutParams();
                // force layout child unless it has exact specs
                if (lp == null || lp.width < 1 || lp.height < 1) {
                    child.forceLayout();
                }
            }
        }
    }

当AbsListView的范围发生改变时调用。AbsListView标记数据,改变布局的设置,强制布局所有子视图。子视图不用进行精确的测量,除非子视图已有明确的规格。


    boolean touchModeDrawsInPressedState() {
        // FIXME use isPressed for this
        switch (mTouchMode) {
        case TOUCH_MODE_TAP:
        case TOUCH_MODE_DONE_WAITING:
            return true;
        default:
            return false;
        }
    }

触摸需要响应时调用,可重写此方法进行触摸响应。


    boolean shouldShowSelector() {
        return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
    }

是否显示选择器。此外,还有构建选择器的方法,如:设置选择器(setSelector),选择器属性(keyPressed or other),修改选择器状态(updateSelectorState)及绘制等等 ,在此不多赘述。


@Override
    protected void onAttachedToWindow() {…}

@Override
protected void onDetachedFromWindow() {…}

@Override
 public void onWindowFocusChanged(boolean hasWindowFocus) {…}

绑定窗口,解除绑定,以及更改窗口焦点等。


    ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
        return new AdapterContextMenuInfo(view, position, id);
    }

构建上下文菜单。


    private class WindowRunnnable {
        private int mOriginalAttachCount;

        public void rememberWindowAttachCount() {
            mOriginalAttachCount = getWindowAttachCount();
        }

        public boolean sameWindow() {
            return getWindowAttachCount() == mOriginalAttachCount;
        }
    }

一个私有内部类。WindowRunnnable是个基类,当其被实现时,检测子视图是否始终绑定最初的窗口。


    private class PerformClick extends WindowRunnnable implements Runnable {
        int mClickMotionPosition;

        @Override
        public void run() {
            // The data has changed since we posted this action in the event queue,
            // bail out before bad things happen
            if (mDataChanged) return;

            final ListAdapter adapter = mAdapter;
            final int motionPosition = mClickMotionPosition;
            if (adapter != null && mItemCount > 0 &&
                    motionPosition != INVALID_POSITION &&
                    motionPosition < adapter.getCount() && sameWindow() &&
                    adapter.isEnabled(motionPosition)) {
                final View view = getChildAt(motionPosition - mFirstPosition);
                // If there is no view, something bad happened (the view scrolled off the
                // screen, etc.) and we should cancel the click
                if (view != null) {
                    performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
                }
            }
        }
    }

    private class CheckForLongPress extends WindowRunnnable implements Runnable {
        private static final int INVALID_COORD = -1;
        private float mX = INVALID_COORD;
        private float mY = INVALID_COORD;

        private void setCoords(float x, float y) {
            mX = x;
            mY = y;
        }

        @Override
        public void run() {
            final int motionPosition = mMotionPosition;
            final View child = getChildAt(motionPosition - mFirstPosition);
            if (child != null) {
                final int longPressPosition = mMotionPosition;
                final long longPressId = mAdapter.getItemId(mMotionPosition);

                boolean handled = false;
                if (sameWindow() && !mDataChanged) {
                    if (mX != INVALID_COORD && mY != INVALID_COORD) {
                        handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
                    } else {
                        handled = performLongPress(child, longPressPosition, longPressId);
                    }
                }

                if (handled) {
                    mHasPerformedLongPress = true;
                    mTouchMode = TOUCH_MODE_REST;
                    setPressed(false);
                    child.setPressed(false);
                } else {
                    mTouchMode = TOUCH_MODE_DONE_WAITING;
                }
            }
        }
    }

    private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
        @Override
        public void run() {
            if (isPressed() && mSelectedPosition >= 0) {
                int index = mSelectedPosition - mFirstPosition;
                View v = getChildAt(index);

                if (!mDataChanged) {
                    boolean handled = false;
                    if (sameWindow()) {
                        handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
                    }
                    if (handled) {
                        setPressed(false);
                        v.setPressed(false);
                    }
                } else {
                    setPressed(false);
                    if (v != null) v.setPressed(false);
                }
            }
        }
    }

    private boolean performStylusButtonPressAction(MotionEvent ev) {
        if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
            final View child = getChildAt(mMotionPosition - mFirstPosition);
            if (child != null) {
                final int longPressPosition = mMotionPosition;
                final long longPressId = mAdapter.getItemId(mMotionPosition);
                if (performLongPress(child, longPressPosition, longPressId)) {
                    mTouchMode = TOUCH_MODE_REST;
                    setPressed(false);
                    child.setPressed(false);
                    return true;
                }
            }
        }
        return false;
    }

WindowRunnnable的子类,还实现了Runnable接口。
PerformClick,执行点击类,实现子视图的点击事件。当存在子控件且adapter不为空时,调用重写的performItemClick方法。
CheckForLongPress检查当前模式是不是长按点击。
CheckForKeyLongPress检查当前模式是不是键长按点击。


    private boolean performStylusButtonPressAction(MotionEvent ev) {
        if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
            final View child = getChildAt(mMotionPosition - mFirstPosition);
            if (child != null) {
                final int longPressPosition = mMotionPosition;
                final long longPressId = mAdapter.getItemId(mMotionPosition);
                if (performLongPress(child, longPressPosition, longPressId)) {
                    mTouchMode = TOUCH_MODE_REST;
                    setPressed(false);
                    child.setPressed(false);
                    return true;
                }
            }
        }
        return false;
    }

    boolean performLongPress(final View child,
            final int longPressPosition, final long longPressId) {
        return performLongPress(
                child,
                longPressPosition,
                longPressId,
                CheckForLongPress.INVALID_COORD,
                CheckForLongPress.INVALID_COORD);
    }

    boolean performLongPress(final View child,
            final int longPressPosition, final long longPressId, float x, float y) {
        // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
        if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
            if (mChoiceActionMode == null &&
                    (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
                setItemChecked(longPressPosition, true);
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
            }
            return true;
        }

        boolean handled = false;
        if (mOnItemLongClickListener != null) {
            handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
                    longPressPosition, longPressId);
        }
        if (!handled) {
            mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
            if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
                handled = super.showContextMenuForChild(AbsListView.this, x, y);
            } else {
                handled = super.showContextMenuForChild(AbsListView.this);
            }
        }
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    }

三个执行方法,实现了执行点击事件响应,执行长按点击的处理 。


    public long pointToRowId(int x, int y) {
        int position = pointToPosition(x, y);
        if (position >= 0) {
            return mAdapter.getItemId(position);
        }
        return INVALID_ROW_ID;
    }

    private final class CheckForTap implements Runnable {
        float x;
        float y;

        @Override
        public void run() {
            if (mTouchMode == TOUCH_MODE_DOWN) {
                mTouchMode = TOUCH_MODE_TAP;
                final View child = getChildAt(mMotionPosition - mFirstPosition);
                if (child != null && !child.hasFocusable()) {
                    mLayoutMode = LAYOUT_NORMAL;

                    if (!mDataChanged) {
                        final float[] point = mTmpPoint;
                        point[0] = x;
                        point[1] = y;
                        transformPointToViewLocal(point, child);
                        child.drawableHotspotChanged(point[0], point[1]);
                        child.setPressed(true);
                        setPressed(true);
                        layoutChildren();
                        positionSelector(mMotionPosition, child);
                        refreshDrawableState();

                        final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
                        final boolean longClickable = isLongClickable();

                        if (mSelector != null) {
                            final Drawable d = mSelector.getCurrent();
                            if (d != null && d instanceof TransitionDrawable) {
                                if (longClickable) {
                                    ((TransitionDrawable) d).startTransition(longPressTimeout);
                                } else {
                                    ((TransitionDrawable) d).resetTransition();
                                }
                            }
                            mSelector.setHotspot(x, y);
                        }

                        if (longClickable) {
                            if (mPendingCheckForLongPress == null) {
                                mPendingCheckForLongPress = new CheckForLongPress();
                            }
                            mPendingCheckForLongPress.setCoords(x, y);
                            mPendingCheckForLongPress.rememberWindowAttachCount();
                            postDelayed(mPendingCheckForLongPress, longPressTimeout);
                        } else {
                            mTouchMode = TOUCH_MODE_DONE_WAITING;
                        }
                    } else {
                        mTouchMode = TOUCH_MODE_DONE_WAITING;
                    }
                }
            }
        }
}


    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
        final int deltaY = y - mMotionY;
        final int distance = Math.abs(deltaY);
        final boolean overscroll = mScrollY != 0;
        if ((overscroll || distance > mTouchSlop) &&
                (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
            createScrollingCache();
            if (overscroll) {
                mTouchMode = TOUCH_MODE_OVERSCROLL;
                mMotionCorrection = 0;
            } else {
                mTouchMode = TOUCH_MODE_SCROLL;
                mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
            }
            removeCallbacks(mPendingCheckForLongPress);
            setPressed(false);
            final View motionView = getChildAt(mMotionPosition - mFirstPosition);
            if (motionView != null) {
                motionView.setPressed(false);
            }
            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);
            }
            scrollIfNeeded(x, y, vtev);
            return true;
        }

        return false;
    }

    private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
        int rawDeltaY = y - mMotionY;
        int scrollOffsetCorrection = 0;
        int scrollConsumedCorrection = 0;
        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;
        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;
                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) {
                                    mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
                                            (float) x / getWidth());
                                    if (!mEdgeGlowBottom.isFinished()) {
                                        mEdgeGlowBottom.onRelease();
                                    }
                                    invalidateTopGlow();
                                } else if (incrementalDeltaY < 0) {
                                    mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
                                            1.f - (float) x / getWidth());
                                    if (!mEdgeGlowTop.isFinished()) {
                                        mEdgeGlowTop.onRelease();
                                    }
                                    invalidateBottomGlow();
                                }
                            }
                        }
                    }
                    mMotionY = y + lastYCorrection + scrollOffsetCorrection;
                }
                mLastY = y + lastYCorrection + scrollOffsetCorrection;
            }
        } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
            if (y != mLastY) {
                final int oldScroll = mScrollY;
                final int newScroll = oldScroll - incrementalDeltaY;
                int newDirection = y > mLastY ? 1 : -1;

                if (mDirection == 0) {
                    mDirection = newDirection;
                }

                int overScrollDistance = -incrementalDeltaY;
                if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
                    overScrollDistance = -oldScroll;
                    incrementalDeltaY += overScrollDistance;
                } else {
                    incrementalDeltaY = 0;
                }

                if (overScrollDistance != 0) {
                    overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
                            0, mOverscrollDistance, true);
                    final int overscrollMode = getOverScrollMode();
                    if (overscrollMode == OVER_SCROLL_ALWAYS ||
                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
                                    !contentFits())) {
                        if (rawDeltaY > 0) {
                            mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
                                    (float) x / getWidth());
                            if (!mEdgeGlowBottom.isFinished()) {
                                mEdgeGlowBottom.onRelease();
                            }
                            invalidateTopGlow();
                        } else if (rawDeltaY < 0) {
                            mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
                                    1.f - (float) x / getWidth());
                            if (!mEdgeGlowTop.isFinished()) {
                                mEdgeGlowTop.onRelease();
                            }
                            invalidateBottomGlow();
                        }
                    }
                }

                if (incrementalDeltaY != 0) {
                    // Coming back to 'real' list scrolling
                    if (mScrollY != 0) {
                        mScrollY = 0;
                        invalidateParentIfNeeded();
                    }

                    trackMotionScroll(incrementalDeltaY, incrementalDeltaY);

                    mTouchMode = TOUCH_MODE_SCROLL;

                    // We did not scroll the full amount. Treat this essentially like the
                    // start of a new touch scroll
                    final int motionPosition = findClosestMotionRow(y);

                    mMotionCorrection = 0;
                    View motionView = getChildAt(motionPosition - mFirstPosition);
                    mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
                    mMotionY =  y + scrollOffsetCorrection;
                    mMotionPosition = motionPosition;
                }
                mLastY = y + lastYCorrection + scrollOffsetCorrection;
                mDirection = newDirection;
            }
        }
    }

这儿是设置AbsListView的滑动,CheckForTab是对整个屏幕的状态的检测,并设置一些模式的初始参数。
StartScrollIfNeeded是判断我们是否能移动到当前屏幕外的位置,及子视图填充后是否已经超出当前屏幕。若已经超出,则设置滑动属性,调用scrollIfNeeded方法,并return true,否则,不进行任何操作return false。
scrollIfNeeded对scroll的实现,对其相应参数进行了细化,包对滚动状态的分析,以及跨度,当前移动的视图判定,以及最终的Y轴限制等等。
对滑动实现,还写了关于VelocityTracker的速度检测器的初始化,回收服用等,以及触碰和取消触碰(Touch,unTouch)的方法,在此也不多赘述。


    private class FlingRunnable implements Runnable {
        /**
         * Tracks the decay of a fling scroll
         */
        private final OverScroller mScroller;

        /**
         * Y value reported by mScroller on the previous fling
         */
        private int mLastFlingY;

        private final Runnable mCheckFlywheel = new Runnable() {
            @Override
            public void run() {
                final int activeId = mActivePointerId;
                final VelocityTracker vt = mVelocityTracker;
                final OverScroller scroller = mScroller;
                if (vt == null || activeId == INVALID_POINTER) {
                    return;
                }

                vt.computeCurrentVelocity(1000, mMaximumVelocity);
                final float yvel = -vt.getYVelocity(activeId);

                if (Math.abs(yvel) >= mMinimumVelocity
                        && scroller.isScrollingInDirection(0, yvel)) {
                    // Keep the fling alive a little longer
                    postDelayed(this, FLYWHEEL_TIMEOUT);
                } else {
                    endFling();
                    mTouchMode = TOUCH_MODE_SCROLL;
                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                }
            }
        };

这个类的实现很有意思,这是屏幕投掷的实现类,判断了屏幕的拉动效果是否为投掷效果。
这个投掷,就是你按着屏幕,用力一滑的意思。这里面实现时候,有对起瞬间速度进行计算,还发送了延迟消息等等。(存下之后细读line 4541)


    public void smoothScrollToPosition(int position) {
        if (mPositionScroller == null) {
            mPositionScroller = createPositionScroller();
        }
        mPositionScroller.start(position);
    }

平缓移动到指定位置。


    public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
        if (mPositionScroller == null) {
            mPositionScroller = createPositionScroller();
        }
        mPositionScroller.startWithOffset(position, offset, duration);
    }

平缓从顶部移动到指定位置。


    private void createScrollingCache() {
        if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
            setChildrenDrawnWithCacheEnabled(true);
            setChildrenDrawingCacheEnabled(true);
            mCachingStarted = mCachingActive = true;
        }
    }

    private void clearScrollingCache() {
        if (!isHardwareAccelerated()) {
            if (mClearScrollingCache == null) {
                mClearScrollingCache = new Runnable() {
                    @Override
                    public void run() {
                        if (mCachingStarted) {
                            mCachingStarted = mCachingActive = false;
                            setChildrenDrawnWithCacheEnabled(false);
                            if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
                                setChildrenDrawingCacheEnabled(false);
                            }
                            if (!isAlwaysDrawnWithCacheEnabled()) {
                                invalidate();
                            }
                        }
                    }
                };
            }
            post(mClearScrollingCache);
        }

创建滑动缓存/清除滑动缓存。设置了绘制孩子缓存的状态。


    int getHeaderViewsCount() {
        return 0;
}

    int getFooterViewsCount() {
        return 0;
    }

获得头/足视图个数。


    void hideSelector() {
        if (mSelectedPosition != INVALID_POSITION) {
            if (mLayoutMode != LAYOUT_SPECIFIC) {
                mResurrectToPosition = mSelectedPosition;
            }
            if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
                mResurrectToPosition = mNextSelectedPosition;
            }
            setSelectedPositionInt(INVALID_POSITION);
            setNextSelectedPositionInt(INVALID_POSITION);
            mSelectedTop = 0;
        }
    }

隐藏selector。


private void dismissPopup() {…}

private void showPopup() {…}

private void positionPopup() {…}

static int getDistance(Rect source, Rect dest, int direction) {…}

取消或显示过滤窗口,获得目标方位以及资源方位的距离,方向,以便判断之间的关系。


    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }


    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (isTextFilterEnabled()) {
            createTextFilter(true);
            int length = s.length();
            boolean showing = mPopup.isShowing();
            if (!showing && length > 0) {
                // Show the filter popup if necessary
                showPopup();
                mFiltered = true;
            } else if (showing && length == 0) {
                // Remove the filter popup if the user has cleared all text
                dismissPopup();
                mFiltered = false;
            }
            if (mAdapter instanceof Filterable) {
                Filter f = ((Filterable) mAdapter).getFilter();
                // Filter should not be null when we reach this part
                if (f != null) {
                    f.filter(s, this);
                } else {
                    throw new IllegalStateException("You cannot call onTextChanged with a non "
                            + "filterable adapter");
                }
            }
        }
    }

    @Override
    public void afterTextChanged(Editable s) {
    }

重写的TextWatcher接口中的方法,实现对文本内容的更新。


    private void createTextFilter(boolean animateEntrance) {
        if (mPopup == null) {
            PopupWindow p = new PopupWindow(getContext());
            p.setFocusable(false);
            p.setTouchable(false);
            p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
            p.setContentView(getTextFilterInput());
            p.setWidth(LayoutParams.WRAP_CONTENT);
            p.setHeight(LayoutParams.WRAP_CONTENT);
            p.setBackgroundDrawable(null);
            mPopup = p;
            getViewTreeObserver().addOnGlobalLayoutListener(this);
            mGlobalLayoutListenerAddedFilter = true;
        }
        if (animateEntrance) {
            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
        } else {
            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
        }
    }

创建文本过滤器。还有getTextFilterInput()(返回的是一个EditText),clearTextFilter()方法获得和取消文本过滤器。
以及hasTextFilter()对是否定义文本过滤器进行判断。


    public void setCacheColorHint(@ColorInt int color) {
        if (color != mCacheColorHint) {
            mCacheColorHint = color;
            int count = getChildCount();
            for (int i = 0; i < count; i++) {
                getChildAt(i).setDrawingCacheBackgroundColor(color);
            }
            mRecycler.setCacheColorHint(color);
        }
    }

设置提示的颜色缓存,还有getCacheColorHint获取当前提示颜色的缓存。


    public void reclaimViews(List views) {
        int childCount = getChildCount();
        RecyclerListener listener = mRecycler.mRecyclerListener;

        // Reclaim views on screen
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
            // Don't reclaim header or footer views, or views that should be ignored
            if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
                views.add(child);
                child.setAccessibilityDelegate(null);
                if (listener != null) {
                    // Pretend they went through the scrap heap
                    listener.onMovedToScrapHeap(child);
                }
            }
        }
        mRecycler.reclaimScrapViews(views);
        removeAllViewsInLayout();
    }

将该AbsListView持有的所有视图放入到等待填充的集合中(缓存状态),包括屏幕中显示的和回收的视图。
即这个方法ListView的回收机制。(line 6215)


    public void setRemoteViewsAdapter(Intent intent) {
        // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
        // service handling the specified intent.
        if (mRemoteAdapter != null) {
            Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
            Intent.FilterComparison fcOld = new Intent.FilterComparison(
                    mRemoteAdapter.getRemoteViewsServiceIntent());
            if (fcNew.equals(fcOld)) {
                return;
            }
        }
        mDeferNotifyDataSetChanged = false;
        // Otherwise, create a new RemoteViewsAdapter for binding
        mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
        if (mRemoteAdapter.isDataReady()) {
            setAdapter(mRemoteAdapter);
        }
    }

设置远程的视图适配器。


    public void setRecyclerListener(RecyclerListener listener) {
        mRecycler.mRecyclerListener = listener;
    }


    class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();
            if (mFastScroll != null) {
                mFastScroll.onSectionsChanged();
            }
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            if (mFastScroll != null) {
                mFastScroll.onSectionsChanged();
            }
        }
    }

设置回收监听器,为适配器数据设置观察者。每当视图被回收后发送通知。此监听壳用于释放与视图关联的资源。


public interface MultiChoiceModeListener extends ActionMode.Callback {

        public void onItemCheckedStateChanged(ActionMode mode,
                int position, long id, boolean checked);
    }

    class MultiChoiceModeWrapper implements MultiChoiceModeListener {
        private MultiChoiceModeListener mWrapped;

        public void setWrapped(MultiChoiceModeListener wrapped) {
            mWrapped = wrapped;
        }

        public boolean hasWrappedCallback() {
            return mWrapped != null;
        }

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            if (mWrapped.onCreateActionMode(mode, menu)) {
                // Initialize checked graphic state?
                setLongClickable(false);
                return true;
            }
            return false;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return mWrapped.onPrepareActionMode(mode, menu);
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            return mWrapped.onActionItemClicked(mode, item);
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            mWrapped.onDestroyActionMode(mode);
            mChoiceActionMode = null;

            // Ending selection mode means deselecting everything.
            clearChoices();

            mDataChanged = true;
            rememberSyncState();
            requestLayout();

            setLongClickable(true);
        }

        @Override
        public void onItemCheckedStateChanged(ActionMode mode,
                int position, long id, boolean checked) {
            mWrapped.onItemCheckedStateChanged(mode, position, id, checked);

            // If there are no items selected we no longer need the selection mode.
            if (getCheckedItemCount() == 0) {
                mode.finish();
            }
        }
    }

内部定义的一个接口,用于事件的接受。在选择模式时调用。
内部定义了一个选择模式包装类来实现这个接口。当子控件的模式以及被选中状态改变时,都会调用该方法。


    public static class LayoutParams extends ViewGroup.LayoutParams {
        /**
         * View type for this view, as returned by
         * {@link android.widget.Adapter#getItemViewType(int) }
         */
        @ViewDebug.ExportedProperty(category = "list", mapping = {
            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
        })
        int viewType;

        /**
         * When this boolean is set, the view has been added to the AbsListView
         * at least once. It is used to know whether headers/footers have already
         * been added to the list view and whether they should be treated as
         * recycled views or not.
         */
        @ViewDebug.ExportedProperty(category = "list")
        boolean recycledHeaderFooter;

        /**
         * When an AbsListView is measured with an AT_MOST measure spec, it needs
         * to obtain children views to measure itself. When doing so, the children
         * are not attached to the window, but put in the recycler which assumes
         * they've been attached before. Setting this flag will force the reused
         * view to be attached to the window rather than just attached to the
         * parent.
         */
        @ViewDebug.ExportedProperty(category = "list")
        boolean forceAdd;

        /**
         * The position the view was removed from when pulled out of the
         * scrap heap.
         * @hide
         */
        int scrappedFromPosition;

        /**
         * The ID the view represents
         */
        long itemId = -1;

        /** Whether the adapter considers the item enabled. */
        boolean isEnabled;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int w, int h) {
            super(w, h);
        }

        public LayoutParams(int w, int h, int viewType) {
            super(w, h);
            this.viewType = viewType;
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        /** @hide */
        @Override
        protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
            super.encodeProperties(encoder);

            encoder.addProperty("list:viewType", viewType);
            encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
            encoder.addProperty("list:forceAdd", forceAdd);
            encoder.addProperty("list:isEnabled", isEnabled);
        }
    }

内部定义的LayoutParams类继承ViewGroup的LayoutParams。在其基础上进行了扩展,增加了viewType,recycledHeaderFooter,forceAdd,isEnabled等属性。


public static interface RecyclerListener {
        /**
         * Indicates that the specified View was moved into the recycler's scrap heap.
         * The view is not displayed on screen any more and any expensive resource
         * associated with the view should be discarded.
         *
         * @param view
         */
        void onMovedToScrapHeap(View view);
    }

    /**
     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
     * start of a layout. By construction, they are displaying current information. At the end of
     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
     * could potentially be used by the adapter to avoid allocating views unnecessarily.
     *
     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
     * @see android.widget.AbsListView.RecyclerListener
     */
    class RecycleBin {
        private RecyclerListener mRecyclerListener;

        /**
         * The position of the first view stored in mActiveViews.
         */
        private int mFirstActivePosition;

        /**
         * Views that were on screen at the start of layout. This array is populated at the start of
         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
         * Views in mActiveViews represent a contiguous range of Views, with position of the first
         * view store in mFirstActivePosition.
         */
        private View[] mActiveViews = new View[0];

        /**
         * Unsorted views that can be used by the adapter as a convert view.
         */
        private ArrayList[] mScrapViews;

        private int mViewTypeCount;

        private ArrayList mCurrentScrap;

        private ArrayList mSkippedScrap;

        private SparseArray mTransientStateViews;
        private LongSparseArray mTransientStateViewsById;

        public void setViewTypeCount(int viewTypeCount) {
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            }
            //noinspection unchecked
            ArrayList[] scrapViews = new ArrayList[viewTypeCount];
            for (int i = 0; i < viewTypeCount; i++) {
                scrapViews[i] = new ArrayList();
            }
            mViewTypeCount = viewTypeCount;
            mCurrentScrap = scrapViews[0];
            mScrapViews = scrapViews;
        }

        public void markChildrenDirty() {
            if (mViewTypeCount == 1) {
                final ArrayList scrap = mCurrentScrap;
                final int scrapCount = scrap.size();
                for (int i = 0; i < scrapCount; i++) {
                    scrap.get(i).forceLayout();
                }
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList scrap = mScrapViews[i];
                    final int scrapCount = scrap.size();
                    for (int j = 0; j < scrapCount; j++) {
                        scrap.get(j).forceLayout();
                    }
                }
            }
            if (mTransientStateViews != null) {
                final int count = mTransientStateViews.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViews.valueAt(i).forceLayout();
                }
            }
            if (mTransientStateViewsById != null) {
                final int count = mTransientStateViewsById.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViewsById.valueAt(i).forceLayout();
                }
            }
        }

        public boolean shouldRecycleViewType(int viewType) {
            return viewType >= 0;
        }

        /**
         * Clears the scrap heap.
         */
        void clear() {
            if (mViewTypeCount == 1) {
                final ArrayList scrap = mCurrentScrap;
                clearScrap(scrap);
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList scrap = mScrapViews[i];
                    clearScrap(scrap);
                }
            }

            clearTransientStateViews();
        }

        /**
         * Fill ActiveViews with all of the children of the AbsListView.
         *
         * @param childCount The minimum number of views mActiveViews should hold
         * @param firstActivePosition The position of the first view that will be stored in
         *        mActiveViews
         */
        void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;

            //noinspection MismatchedReadAndWriteOfArray
            final View[] activeViews = mActiveViews;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                // Don't put header or footer views into the scrap heap
                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                    // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
                    //        However, we will NOT place them into scrap views.
                    activeViews[i] = child;
                    // Remember the position so that setupChild() doesn't reset state.
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }

        /**
         * Get the view corresponding to the specified position. The view will be removed from
         * mActiveViews if it is found.
         *
         * @param position The position to look up in mActiveViews
         * @return The view if it is found, null otherwise
         */
        View getActiveView(int position) {
            int index = position - mFirstActivePosition;
            final View[] activeViews = mActiveViews;
            if (index >=0 && index < activeViews.length) {
                final View match = activeViews[index];
                activeViews[index] = null;
                return match;
            }
            return null;
        }

        View getTransientStateView(int position) {
            if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
                long id = mAdapter.getItemId(position);
                View result = mTransientStateViewsById.get(id);
                mTransientStateViewsById.remove(id);
                return result;
            }
            if (mTransientStateViews != null) {
                final int index = mTransientStateViews.indexOfKey(position);
                if (index >= 0) {
                    View result = mTransientStateViews.valueAt(index);
                    mTransientStateViews.removeAt(index);
                    return result;
                }
            }
            return null;
        }

        /**
         * Dumps and fully detaches any currently saved views with transient
         * state.
         */
        void clearTransientStateViews() {
            final SparseArray viewsByPos = mTransientStateViews;
            if (viewsByPos != null) {
                final int N = viewsByPos.size();
                for (int i = 0; i < N; i++) {
                    removeDetachedView(viewsByPos.valueAt(i), false);
                }
                viewsByPos.clear();
            }

            final LongSparseArray viewsById = mTransientStateViewsById;
            if (viewsById != null) {
                final int N = viewsById.size();
                for (int i = 0; i < N; i++) {
                    removeDetachedView(viewsById.valueAt(i), false);
                }
                viewsById.clear();
            }
        }

        /**
         * @return A view from the ScrapViews collection. These are unordered.
         */
        View getScrapView(int position) {
            final int whichScrap = mAdapter.getItemViewType(position);
            if (whichScrap < 0) {
                return null;
            }
            if (mViewTypeCount == 1) {
                return retrieveFromScrap(mCurrentScrap, position);
            } else if (whichScrap < mScrapViews.length) {
                return retrieveFromScrap(mScrapViews[whichScrap], position);
            }
            return null;
        }

        /**
         * Puts a view into the list of scrap views.
         * 

* If the list data hasn't changed or the adapter has stable IDs, views * with transient state will be preserved for later retrieval. * * @param scrap The view to add * @param position The view's position within its parent */ void addScrapView(View scrap, int position) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); if (lp == null) { // Can't recycle, but we don't know anything about the view. // Ignore it completely. return; } lp.scrappedFromPosition = position; // Remove but don't scrap header or footer views, or views that // should otherwise not be recycled. final int viewType = lp.viewType; if (!shouldRecycleViewType(viewType)) { // Can't recycle. If it's not a header or footer, which have // special handling and should be ignored, then skip the scrap // heap and we'll fully detach the view later. if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { getSkippedScrap().add(scrap); } return; } scrap.dispatchStartTemporaryDetach(); // The the accessibility state of the view may change while temporary // detached and we do not allow detached views to fire accessibility // events. So we are announcing that the subtree changed giving a chance // to clients holding on to a view in this subtree to refresh it. notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { if (mAdapter != null && mAdapterHasStableIds) { // If the adapter has stable IDs, we can reuse the view for // the same data. if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<>(); } mTransientStateViewsById.put(lp.itemId, scrap); } else if (!mDataChanged) { // If the data hasn't changed, we can reuse the views at // their old positions. if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<>(); } mTransientStateViews.put(position, scrap); } else { // Otherwise, we'll have to remove the view and start over. getSkippedScrap().add(scrap); } } else { if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } } private ArrayList getSkippedScrap() { if (mSkippedScrap == null) { mSkippedScrap = new ArrayList<>(); } return mSkippedScrap; } /** * Finish the removal of any views that skipped the scrap heap. */ void removeSkippedScrap() { if (mSkippedScrap == null) { return; } final int count = mSkippedScrap.size(); for (int i = 0; i < count; i++) { removeDetachedView(mSkippedScrap.get(i), false); } mSkippedScrap.clear(); } /** * Move all views remaining in mActiveViews to mScrapViews. */ void scrapActiveViews() { final View[] activeViews = mActiveViews; final boolean hasListener = mRecyclerListener != null; final boolean multipleScraps = mViewTypeCount > 1; ArrayList scrapViews = mCurrentScrap; final int count = activeViews.length; for (int i = count - 1; i >= 0; i--) { final View victim = activeViews[i]; if (victim != null) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) victim.getLayoutParams(); final int whichScrap = lp.viewType; activeViews[i] = null; if (victim.hasTransientState()) { // Store views with transient state for later use. victim.dispatchStartTemporaryDetach(); if (mAdapter != null && mAdapterHasStableIds) { if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray(); } long id = mAdapter.getItemId(mFirstActivePosition + i); mTransientStateViewsById.put(id, victim); } else if (!mDataChanged) { if (mTransientStateViews == null) { mTransientStateViews = new SparseArray(); } mTransientStateViews.put(mFirstActivePosition + i, victim); } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // The data has changed, we can't keep this view. removeDetachedView(victim, false); } } else if (!shouldRecycleViewType(whichScrap)) { // Discard non-recyclable views except headers/footers. if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { removeDetachedView(victim, false); } } else { // Store everything else on the appropriate scrap heap. if (multipleScraps) { scrapViews = mScrapViews[whichScrap]; } lp.scrappedFromPosition = mFirstActivePosition + i; removeDetachedView(victim, false); scrapViews.add(victim); if (hasListener) { mRecyclerListener.onMovedToScrapHeap(victim); } } } } pruneScrapViews(); } /** * At the end of a layout pass, all temp detached views should either be re-attached or * completely detached. This method ensures that any remaining view in the scrap list is * fully detached. */ void fullyDetachScrapViews() { final int viewTypeCount = mViewTypeCount; final ArrayList[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList scrapPile = scrapViews[i]; for (int j = scrapPile.size() - 1; j >= 0; j--) { final View view = scrapPile.get(j); if (view.isTemporarilyDetached()) { removeDetachedView(view, false); } } } } /** * Makes sure that the size of mScrapViews does not exceed the size of * mActiveViews, which can happen if an adapter does not recycle its * views. Removes cached transient state views that no longer have * transient state. */ private void pruneScrapViews() { final int maxViews = mActiveViews.length; final int viewTypeCount = mViewTypeCount; final ArrayList[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList scrapPile = scrapViews[i]; int size = scrapPile.size(); while (size > maxViews) { scrapPile.remove(--size); } } final SparseArray transViewsByPos = mTransientStateViews; if (transViewsByPos != null) { for (int i = 0; i < transViewsByPos.size(); i++) { final View v = transViewsByPos.valueAt(i); if (!v.hasTransientState()) { removeDetachedView(v, false); transViewsByPos.removeAt(i); i--; } } } final LongSparseArray transViewsById = mTransientStateViewsById; if (transViewsById != null) { for (int i = 0; i < transViewsById.size(); i++) { final View v = transViewsById.valueAt(i); if (!v.hasTransientState()) { removeDetachedView(v, false); transViewsById.removeAt(i); i--; } } } } /** * Puts all views in the scrap heap into the supplied list. */ void reclaimScrapViews(List views) { if (mViewTypeCount == 1) { views.addAll(mCurrentScrap); } else { final int viewTypeCount = mViewTypeCount; final ArrayList[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList scrapPile = scrapViews[i]; views.addAll(scrapPile); } } } /** * Updates the cache color hint of all known views. * * @param color The new cache color hint. */ void setCacheColorHint(int color) { if (mViewTypeCount == 1) { final ArrayList scrap = mCurrentScrap; final int scrapCount = scrap.size(); for (int i = 0; i < scrapCount; i++) { scrap.get(i).setDrawingCacheBackgroundColor(color); } } else { final int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { final ArrayList scrap = mScrapViews[i]; final int scrapCount = scrap.size(); for (int j = 0; j < scrapCount; j++) { scrap.get(j).setDrawingCacheBackgroundColor(color); } } } // Just in case this is called during a layout pass final View[] activeViews = mActiveViews; final int count = activeViews.length; for (int i = 0; i < count; ++i) { final View victim = activeViews[i]; if (victim != null) { victim.setDrawingCacheBackgroundColor(color); } } } private View retrieveFromScrap(ArrayList scrapViews, int position) { final int size = scrapViews.size(); if (size > 0) { // See if we still have a view for this position or ID. for (int i = 0; i < size; i++) { final View view = scrapViews.get(i); final AbsListView.LayoutParams params = (AbsListView.LayoutParams) view.getLayoutParams(); if (mAdapterHasStableIds) { final long id = mAdapter.getItemId(position); if (id == params.itemId) { return scrapViews.remove(i); } } else if (params.scrappedFromPosition == position) { final View scrap = scrapViews.remove(i); clearAccessibilityFromScrap(scrap); return scrap; } } final View scrap = scrapViews.remove(size - 1); clearAccessibilityFromScrap(scrap); return scrap; } else { return null; } } private void clearScrap(final ArrayList scrap) { final int scrapCount = scrap.size(); for (int j = 0; j < scrapCount; j++) { removeDetachedView(scrap.remove(scrapCount - 1 - j), false); } } private void clearAccessibilityFromScrap(View view) { view.clearAccessibilityFocus(); view.setAccessibilityDelegate(null); } private void removeDetachedView(View child, boolean animate) { child.setAccessibilityDelegate(null); AbsListView.this.removeDetachedView(child, animate); } }

定义了一个静态内部接口,回收监听器,内部定义了一个方法,onMovedToScrapHeap(View),里面传入一个视图,当指定的视图被放入回收栈时,视图不再显示的屏幕上,任何与之相关昂贵的资源都应该被丢弃。
RecycleBin,主要实现了ListView的资源回收和重用的功能。
RecycleBin有两个层次的存储:activeviews和scrapviews。activeviews的视图,屏幕在布局的开始时,通过构造,显示它们的当前信息。在布局的结束, activeviews所有视图降级到scrapviews。scrapviews里是一些可能被重用的视图,通过adapter复用他们,可避免重新构造视图分配不必要的资源。

总结

AbsListView,ListView的父类,继承了AdapterView,并且实现了TextWatcher接口。主要实现了视图显示方面的功能,包括复用,滑动,抛掷,点击模式,文本显示,子视图填充..等等的功能。

AbsListView主要是对视图进行操作,AdapterView主要对数据进行操作。

你可能感兴趣的:(一天一点源代码)