项目中使用ListView仍是挺多的,以前看过几回,非常容易遗忘,今特作记录以下android
ListView继承之AbsListView抽象类,因此大部分分析的源码都在这两个类中git
public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
// 初始化设置一些额外属性值
initAbsListView();
mOwnerThread = Thread.currentThread();
// 初始化XML文件中设置的某些默认属性值
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
if (selector != null) {
setSelector(selector);
}
mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
// 初始化设置mStackFromBottom,这个影响到布局子view的顺序方式,默认为false
setStackFromBottom(a.getBoolean(
R.styleable.AbsListView_stackFromBottom, false));
setScrollingCacheEnabled(a.getBoolean(
R.styleable.AbsListView_scrollingCache, true));
setTextFilterEnabled(a.getBoolean(
R.styleable.AbsListView_textFilterEnabled, false));
setTranscriptMode(a.getInt(
R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
setCacheColorHint(a.getColor(
R.styleable.AbsListView_cacheColorHint, 0));
setSmoothScrollbarEnabled(a.getBoolean(
R.styleable.AbsListView_smoothScrollbar, true));
setChoiceMode(a.getInt(
R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
setFastScrollEnabled(a.getBoolean(
R.styleable.AbsListView_fastScrollEnabled, false));
setFastScrollStyle(a.getResourceId(
R.styleable.AbsListView_fastScrollStyle, 0));
setFastScrollAlwaysVisible(a.getBoolean(
R.styleable.AbsListView_fastScrollAlwaysVisible, false));
a.recycle();
}
private void initAbsListView() {
// Setting focusable in touch mode will set the focusable property to true
// 设置ListView自己能够点击便可以消耗父View分发的事件
setClickable(true);
setFocusableInTouchMode(true);
// 由于向上父类还继承之ViewGroup,ViewGroup默认不须要重写draw()方法,
// 从而setWillNotDraw(true),可是AbsListView为了滚动效果,自身重写了View的
// draw(),主要用于实现滚动到最底部或最顶部的非OVER_SCROLL_NEVER模式的效果
setWillNotDraw(false);
setAlwaysDrawnWithCacheEnabled(false);
setScrollingCacheEnabled(true);
// 事件处理相关变量初始化
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mOverscrollDistance = configuration.getScaledOverscrollDistance();
mOverflingDistance = configuration.getScaledOverflingDistance();
mDensityScale = getContext().getResources().getDisplayMetrics().density;
}
复制代码
public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ListView, defStyleAttr, defStyleRes);
final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);
if (entries != null) {
setAdapter(new ArrayAdapter<>(context, R.layout.simple_list_item_1, entries));
}
// 获取item分割线的drawable对象
final Drawable d = a.getDrawable(R.styleable.ListView_divider);
if (d != null) {
// Use an implicit divider height which may be explicitly
// overridden by android:dividerHeight further down.
setDivider(d);
}
final Drawable osHeader = a.getDrawable(R.styleable.ListView_overScrollHeader);
if (osHeader != null) {
setOverscrollHeader(osHeader);
}
final Drawable osFooter = a.getDrawable(R.styleable.ListView_overScrollFooter);
if (osFooter != null) {
setOverscrollFooter(osFooter);
}
// Use an explicit divider height, if specified.
// item分割线的高度
if (a.hasValueOrEmpty(R.styleable.ListView_dividerHeight)) {
final int dividerHeight = a.getDimensionPixelSize(
R.styleable.ListView_dividerHeight, 0);
if (dividerHeight != 0) {
setDividerHeight(dividerHeight);
}
}
mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
a.recycle();
}
复制代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childWidth = 0;
int childHeight = 0;
int childState = 0;
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0, mIsScrap);
// Lay out child directly against the parent measure spec so that
// we can obtain exected minimum width and height.
measureScrapChild(child, 0, widthMeasureSpec, heightSize);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, 0);
}
}
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = mListPadding.left + mListPadding.right + childWidth +
getVerticalScrollbarWidth();
} else {
widthSize |= (childState & MEASURED_STATE_MASK);
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
// 有时候须要使ListView的高度等于全部子item view 能够重写onMeasure()方法使其调用以
// 下代码
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
setMeasuredDimension(widthSize, heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}
复制代码
ListView由adapter.getView()获取的子view的layout方式在此实现github
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
mRecycler.markChildrenDirty();
}
// 由子类ListView 和 GridView实现,是核心布局方法代码,也是listview与adapter交互数据
// 的主要入口函数
layoutChildren();
mInLayout = false;
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
// TODO: Move somewhere sane. This doesn't belong in onLayout(). if (mFastScroll != null) { mFastScroll.onItemCountChanged(getChildCount(), mItemCount); } } 复制代码
@Override
protected void layoutChildren() {
final boolean blockLayoutRequests = mBlockLayoutRequests;
if (blockLayoutRequests) {
return;
}
mBlockLayoutRequests = true;
try {
super.layoutChildren();
invalidate();
if (mAdapter == null) {
resetList();
invokeOnItemScrollListener();
return;
}
final int childrenTop = mListPadding.top;
final int childrenBottom = mBottom - mTop - mListPadding.bottom;
// 每次即将进行layout子item view的时候先记录当前listview已有的child view个数
final int childCount = getChildCount();
int index = 0;
int delta = 0;
View sel;
View oldSel = null;
View oldFirst = null;
View newSel = null;
// Remember stuff we will need down below
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
index = mNextSelectedPosition - mFirstPosition;
if (index >= 0 && index < childCount) {
newSel = getChildAt(index);
}
break;
case LAYOUT_FORCE_TOP:
case LAYOUT_FORCE_BOTTOM:
case LAYOUT_SPECIFIC:
case LAYOUT_SYNC:
break;
case LAYOUT_MOVE_SELECTION:
default:
// Remember the previously selected view
index = mSelectedPosition - mFirstPosition;
if (index >= 0 && index < childCount) {
oldSel = getChildAt(index);
}
// Remember the previous first child
oldFirst = getChildAt(0);
if (mNextSelectedPosition >= 0) {
delta = mNextSelectedPosition - mSelectedPosition;
}
// Caution: newSel might be null
newSel = getChildAt(index + delta);
}
boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
}
// Handle the empty set by removing all views that are visible
// and calling it a day
if (mItemCount == 0) {
resetList();
invokeOnItemScrollListener();
return;
} else if (mItemCount != mAdapter.getCount()) {
throw new IllegalStateException("The content of the adapter has changed but "
+ "ListView did not receive a notification. Make sure the content of "
+ "your adapter is not modified from a background thread, but only from "
+ "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
+ "when its content changes. [in ListView(" + getId() + ", " + getClass()
+ ") with Adapter(" + mAdapter.getClass() + ")]");
}
setSelectedPositionInt(mNextSelectedPosition);
AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
View accessibilityFocusLayoutRestoreView = null;
int accessibilityFocusPosition = INVALID_POSITION;
// Remember which child, if any, had accessibility focus. This must
// occur before recycling any views, since that will clear
// accessibility focus.
final ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
if (focusHost != null) {
final View focusChild = getAccessibilityFocusedChild(focusHost);
if (focusChild != null) {
if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
|| focusChild.hasTransientState() || mAdapterHasStableIds) {
// The views won't be changing, so try to maintain // focus on the current host and virtual view. accessibilityFocusLayoutRestoreView = focusHost; accessibilityFocusLayoutRestoreNode = viewRootImpl .getAccessibilityFocusedVirtualView(); } // If all else fails, maintain focus at the same // position. accessibilityFocusPosition = getPositionForView(focusChild); } } } View focusLayoutRestoreDirectChild = null; View focusLayoutRestoreView = null; // Take focus back to us temporarily to avoid the eventual call to // clear focus when removing the focused child below from messing // things up when ViewAncestor assigns focus back to someone else. final View focusedChild = getFocusedChild(); if (focusedChild != null) { // TODO: in some cases focusedChild.getParent() == null // We can remember the focused view to restore after re-layout // if the data hasn't changed, or if the focused position is a
// header or footer.
if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)
|| focusedChild.hasTransientState() || mAdapterHasStableIds) {
focusLayoutRestoreDirectChild = focusedChild;
// Remember the specific view that had focus.
focusLayoutRestoreView = findFocus();
if (focusLayoutRestoreView != null) {
// Tell it we are going to mess with it.
focusLayoutRestoreView.onStartTemporaryDetach();
}
}
requestFocus();
}
// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
// 只有在调用adapter.notifyDatasetChanged()方法一直到layout()布局结束,
// dataChanged为true,默认为false
if (dataChanged) {
// dataChanged为true,说明当前listview是有数据的了,把当前全部的item view
// 存放到RecycleBin对象的mScrapViews中保存
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
// dataChanged默认为false,第一次执行此方法走这里
recycleBin.fillActiveViews(childCount, firstPosition);
}
// Clear out old views
// 清除当前listview全部的子view
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
// 在咱们调用listview.setAdapter()时候,已经将mLayoutMode = LAYOUT_NORMAL;
// 因此一般状况下可认为mLayoutMode == LAYOUT_NORMAL
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition, mSpecificTop);
break;
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
case LAYOUT_SPECIFIC:
sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
break;
case LAYOUT_MOVE_SELECTION:
sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
break;
default:
// 一般状况都走这里
if (childCount == 0) {
// listview第一次布局childCount必然为0走这里
if (!mStackFromBottom) {
// 一般咱们没有外部调用listview.setStackFromBottom()
// 成员变量mStackFromBottom均为false都走这里
final int position = lookForSelectablePosition(0, true);
setSelectedPositionInt(position);
// 从上到上布局listview能显示得下的子view
sel = fillFromTop(childrenTop);
} else {
final int position = lookForSelectablePosition(mItemCount - 1, false);
setSelectedPositionInt(position);
sel = fillUp(mItemCount - 1, childrenBottom);
}
} else {
// 非第一次layout,以前记录的存在的子view个数childCount不为0
// 包括两种状况:1.listview首次布局中的第二次执行的onlayout();
// 2.在后续listview已经显示存在子view而后数据改变时候调用
// adapter.nitifyDatasetChanged()方法时候
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition,
oldSel == null ? childrenTop : oldSel.getTop());
} else if (mFirstPosition < mItemCount) {
// 一般状况走这里,fillSpecific()会调用fillUp()和fillDown()布局子view
sel = fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
} else {
sel = fillSpecific(0, childrenTop);
}
}
break;
}
// Flush any cached views that did not get reused above
// 至此,listview已经布局完成可以显示得下的子view,将recycleBin可能剩余的
// mActiveViews中view移动到mScrapViews以便于listview滑动时候复用
recycleBin.scrapActiveViews();
if (sel != null) {
// The current selected item should get focus if items are
// focusable.
if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
focusLayoutRestoreView != null &&
focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
if (!focusWasTaken) {
// Selected item didn't take focus, but we still want to // make sure something else outside of the selected view // has focus. final View focused = getFocusedChild(); if (focused != null) { focused.clearFocus(); } positionSelector(INVALID_POSITION, sel); } else { sel.setSelected(false); mSelectorRect.setEmpty(); } } else { positionSelector(INVALID_POSITION, sel); } mSelectedTop = sel.getTop(); } else { final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING; if (inTouchMode) { // If the user's finger is down, select the motion position.
final View child = getChildAt(mMotionPosition - mFirstPosition);
if (child != null) {
positionSelector(mMotionPosition, child);
}
} else if (mSelectorPosition != INVALID_POSITION) {
// If we had previously positioned the selector somewhere,
// put it back there. It might not match up with the data,
// but it's transitioning out so it's not a big deal.
final View child = getChildAt(mSelectorPosition - mFirstPosition);
if (child != null) {
positionSelector(mSelectorPosition, child);
}
} else {
// Otherwise, clear selection.
mSelectedTop = 0;
mSelectorRect.setEmpty();
}
// Even if there is not selected position, we may need to
// restore focus (i.e. something focusable in touch mode).
if (hasFocus() && focusLayoutRestoreView != null) {
focusLayoutRestoreView.requestFocus();
}
}
// Attempt to restore accessibility focus, if necessary.
if (viewRootImpl != null) {
final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
if (newAccessibilityFocusedView == null) {
if (accessibilityFocusLayoutRestoreView != null
&& accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
final AccessibilityNodeProvider provider =
accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
accessibilityFocusLayoutRestoreNode.getSourceNodeId());
provider.performAction(virtualViewId,
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
} else {
accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
}
} else if (accessibilityFocusPosition != INVALID_POSITION) {
// Bound the position within the visible children.
final int position = MathUtils.constrain(
accessibilityFocusPosition - mFirstPosition, 0,
getChildCount() - 1);
final View restoreView = getChildAt(position);
if (restoreView != null) {
restoreView.requestAccessibilityFocus();
}
}
}
}
// Tell focus view we are done mucking with it, if it is still in
// our view hierarchy.
if (focusLayoutRestoreView != null
&& focusLayoutRestoreView.getWindowToken() != null) {
focusLayoutRestoreView.onFinishTemporaryDetach();
}
// 布局完成以后的操做
mLayoutMode = LAYOUT_NORMAL;
mDataChanged = false;
if (mPositionScrollAfterLayout != null) {
post(mPositionScrollAfterLayout);
mPositionScrollAfterLayout = null;
}
mNeedSync = false;
setNextSelectedPositionInt(mSelectedPosition);
updateScrollIndicators();
if (mItemCount > 0) {
checkSelectionChanged();
}
invokeOnItemScrollListener();
} finally {
if (!blockLayoutRequests) {
mBlockLayoutRequests = false;
}
}
}
复制代码
RecycleBin是AbsListView的一个非静态内部类,主要有一个数组成员变量View[] mActiveViews 和 ArrayList
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int actionMasked = ev.getActionMasked();
View v;
if (mPositionScroller != null) {
mPositionScroller.stop();
}
if (mIsDetaching || !isAttachedToWindow()) {
// Something isn't right. // Since we rely on being attached to get data set change notifications, // don't risk doing anything where we might try to resync and find things
// in a bogus state.
return false;
}
if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
return true;
}
switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
int touchMode = mTouchMode;
// 若是手指放下时候 listview正出于fling滚动状态或者OVERSCROLL,则立刻拦截事件交
// 由自身ontouchEvent()处理
if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
mMotionCorrection = 0;
return true;
}
final int x = (int) ev.getX();
final int y = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
// 获取当前触摸事件在对应的item view的positon,在listview中实现了该方法
int motionPosition = findMotionRow(y);
if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
// User clicked on an actual view (and was not stopping a fling).
// Remember where the motion event started
v = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = v.getTop();
mMotionX = x;
// 记录down事件时候的mMotionY初始值
mMotionY = y;
mMotionPosition = motionPosition;
// 设置触摸事件模式为TOUCH_MODE_DOWN
mTouchMode = TOUCH_MODE_DOWN;
clearScrollingCache();
}
// 初试设置down事件时候mLastY值
mLastY = Integer.MIN_VALUE;
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
mNestedYOffset = 0;
startNestedScroll(SCROLL_AXIS_VERTICAL);
if (touchMode == TOUCH_MODE_FLING) {
return true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
pointerIndex = 0;
mActivePointerId = ev.getPointerId(pointerIndex);
}
final int y = (int) ev.getY(pointerIndex);
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
// 判断是否拦截事件本身处理,此为onInterceptTouchEvent()方法核心代码
if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
return true;
}
break;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
// touchMode恢复默认状态
mTouchMode = TOUCH_MODE_REST;
mActivePointerId = INVALID_POINTER;
// 回收速度VelocityTracker相关资源
recycleVelocityTracker();
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
stopNestedScroll();
break;
}
case MotionEvent.ACTION_POINTER_UP: {
onSecondaryPointerUp(ev);
break;
}
}
return false;
}
// 这个方法在onInterceptTouchEvent的move事件中调用,在onTouchEvent()的onTouchMove()方法
// 中开始时候也会调用
private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
// Check if we have moved far enough that it looks more like a
// scroll than a tap
// 获得当前事件的y值与down事件时候设置的值的差值
final int deltaY = y - mMotionY;
final int distance = Math.abs(deltaY);
// mScrollY!=0即overscroll为true ,核心为distance > mTouchSlop即拦截事件本身处理
// mTouchSlop在构造函数中初始化并赋值了
final boolean overscroll = mScrollY != 0;
if ((overscroll || distance > mTouchSlop) &&
(getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
createScrollingCache();
if (overscroll) {
mTouchMode = TOUCH_MODE_OVERSCROLL;
mMotionCorrection = 0;
} else {
// 设置触摸模式为TOUCH_MODE_SCROLL,在onTouchEvent()用到
mTouchMode = TOUCH_MODE_SCROLL;
mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
}
// 取消子view的长按监听触发
removeCallbacks(mPendingCheckForLongPress);
setPressed(false);
final View motionView = getChildAt(mMotionPosition - mFirstPosition);
// listview拦截了事件自己处理,因此恢复可能设置子view的press状态
if (motionView != null) {
motionView.setPressed(false);
}
// 通知ScrollState状态变化回调
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
// Time to start stealing events! Once we've stolen them, don't let anyone
// steal from us
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
// 做用如名,若是知足条件,滚动listview
scrollIfNeeded(x, y, vtev);
return true;
}
return false;
}
onTouchEvent()之onTouchDown():
private void onTouchDown(MotionEvent ev) {
mActivePointerId = ev.getPointerId(0);
if (mTouchMode == TOUCH_MODE_OVERFLING) {
// Stopped the fling. It is a scroll.
// 若是已经正在出于TOUCH_MODE_OVERFLING则down事件瞬间中断fling
mFlingRunnable.endFling();
if (mPositionScroller != null) {
mPositionScroller.stop();
}
mTouchMode = TOUCH_MODE_OVERSCROLL;
mMotionX = (int) ev.getX();
mMotionY = (int) ev.getY();
mLastY = mMotionY;
mMotionCorrection = 0;
mDirection = 0;
} else {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
int motionPosition = pointToPosition(x, y);
if (!mDataChanged) {
if (mTouchMode == TOUCH_MODE_FLING) {
// Stopped a fling. It is a scroll.
createScrollingCache();
mTouchMode = TOUCH_MODE_SCROLL;
mMotionCorrection = 0;
motionPosition = findMotionRow(y);
// 根据y速度判断是否立刻中断fling
mFlingRunnable.flywheelTouch();
} else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
// User clicked on an actual view (and was not stopping a
// fling). It might be a click or a scroll. Assume it is a
// click until proven otherwise.
// 设置touch模式为TOUCH_MODE_DOWN
mTouchMode = TOUCH_MODE_DOWN;
// FIXME Debounce
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = ev.getX();
mPendingCheckForTap.y = ev.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
}
}
if (motionPosition >= 0) {
// Remember where the motion event started
final View v = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = v.getTop();
}
mMotionX = x;
// 记录mMotionY
mMotionY = y;
mMotionPosition = motionPosition;
// 记录mLastY = Integer.MIN_VALUE
mLastY = Integer.MIN_VALUE;
}
if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
&& performButtonActionOnTouchDown(ev)) {
removeCallbacks(mPendingCheckForTap);
}
}
复制代码
private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
pointerIndex = 0;
mActivePointerId = ev.getPointerId(pointerIndex);
}
if (mDataChanged) {
// Re-sync everything if data has been changed
// since the scroll operation can query the adapter.
layoutChildren();
}
final int y = (int) ev.getY(pointerIndex);
switch (mTouchMode) {
// 刚开始进入这里 touchMode为TOUCH_MODE_DOWN
case TOUCH_MODE_DOWN:
case TOUCH_MODE_TAP:
case TOUCH_MODE_DONE_WAITING:
// Check if we have moved far enough that it looks more like a
// scroll than a tap. If so, we'll enter scrolling mode. // 刚开始还么有处于可滚动状态,故进入判断是否能够滚动,核心判断调节为固然事件y // 与down事件mMotionY值的差值绝对值是否大于mTouchSlop if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) { break; } // Otherwise, check containment within list bounds. If we're
// outside bounds, cancel any active presses.
final View motionView = getChildAt(mMotionPosition - mFirstPosition);
final float x = ev.getX(pointerIndex);
if (!pointInView(x, y, mTouchSlop)) {
setPressed(false);
if (motionView != null) {
motionView.setPressed(false);
}
removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
mPendingCheckForTap : mPendingCheckForLongPress);
mTouchMode = TOUCH_MODE_DONE_WAITING;
updateSelectorState();
} else if (motionView != null) {
// Still within bounds, update the hotspot.
final float[] point = mTmpPoint;
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, motionView);
motionView.drawableHotspotChanged(point[0], point[1]);
}
break;
case TOUCH_MODE_SCROLL:
case TOUCH_MODE_OVERSCROLL:
// 若是已经进入到listview的滚动状态,则直接执行scrollIfNeeded根据条件判断是否
// 进行滚动
scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
break;
}
}
private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
int rawDeltaY = y - mMotionY;
int scrollOffsetCorrection = 0;
int scrollConsumedCorrection = 0;
// mLastY==Integer.MIN_VALUE代表刚达到条件进入滚动状态
if (mLastY == Integer.MIN_VALUE) {
// 保证了状态过分时候平稳滚动
rawDeltaY -= mMotionCorrection;
}
if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
mScrollConsumed, mScrollOffset)) {
rawDeltaY += mScrollConsumed[1];
scrollOffsetCorrection = -mScrollOffset[1];
scrollConsumedCorrection = mScrollConsumed[1];
if (vtev != null) {
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
}
final int deltaY = rawDeltaY;
// 两次连续下发事件的y差值.若是mLastY==Integer.MIN_VALUE代表刚达到条件进入滚动状态
// 此时incrementalDeltaY = rawDeltaY,而rawDeltaY已经在上面进行了
// (rawDeltaY -= mMotionCorrection),保证了平稳过分
int incrementalDeltaY =
mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
int lastYCorrection = 0;
if (mTouchMode == TOUCH_MODE_SCROLL) {
if (PROFILE_SCROLLING) {
if (!mScrollProfilingStarted) {
Debug.startMethodTracing("AbsListViewScroll");
mScrollProfilingStarted = true;
}
}
if (mScrollStrictSpan == null) {
// If it's non-null, we're already in a scroll.
mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
}
if (y != mLastY) {
// We may be here after stopping a fling and continuing to scroll.
// If so, we haven't disallowed intercepting touch events yet. // Make sure that we do so in case we're in a parent that can intercept.
if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
Math.abs(rawDeltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
final int motionIndex;
if (mMotionPosition >= 0) {
motionIndex = mMotionPosition - mFirstPosition;
} else {
// If we don't have a motion position that we can reliably track, // pick something in the middle to make a best guess at things below. motionIndex = getChildCount() / 2; } int motionViewPrevTop = 0; View motionView = this.getChildAt(motionIndex); if (motionView != null) { motionViewPrevTop = motionView.getTop(); } // No need to do all this work if we're not going to move anyway
boolean atEdge = false;
// trackMotionScroll()方法真正进行滚动处理
if (incrementalDeltaY != 0) {
atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
}
// Check to see if we have bumped into the scroll limit
motionView = this.getChildAt(motionIndex);
if (motionView != null) {
// Check if the top of the motion view is where it is
// supposed to be
final int motionViewRealTop = motionView.getTop();
// 滚动到最顶部或者最底部
if (atEdge) {
// Apply overscroll
int overscroll = -incrementalDeltaY -
(motionViewRealTop - motionViewPrevTop);
if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
mScrollOffset)) {
lastYCorrection -= mScrollOffset[1];
if (vtev != null) {
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
} else {
final boolean atOverscrollEdge = overScrollBy(0, overscroll,
0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
if (atOverscrollEdge && mVelocityTracker != null) {
// Don't allow overfling if we're at the edge
mVelocityTracker.clear();
}
final int overscrollMode = getOverScrollMode();
if (overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
!contentFits())) {
if (!atOverscrollEdge) {
mDirection = 0; // Reset when entering overscroll.
mTouchMode = TOUCH_MODE_OVERSCROLL;
}
if (incrementalDeltaY > 0) {
// 顶部 OVER_SCROLL效果,draw()方法中实现
mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
(float) x / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
invalidateTopGlow();
} else if (incrementalDeltaY < 0) {
// 底部 OVER_SCROLL效果,draw()方法中实现
mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
1.f - (float) x / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
invalidateBottomGlow();
}
}
}
}
mMotionY = y + lastYCorrection + scrollOffsetCorrection;
}
// 记录当前事件的y
mLastY = y + lastYCorrection + scrollOffsetCorrection;
}
}else if (mTouchMode == TOUCH_MODE_OVERSCROLL){.....}
复制代码
复制代码
复制代码