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主要对数据进行操作。