Android ListView onTouchEvent源码分析
程序员文章站
2022-07-13 16:51:09
...
Android ListView onTouchEvent源码简单分析,在看代码之前先来看下代码结构图
一、onTouchEvent源码
二、ACTION_DOWN具体操作源码分析,主要是CheckForTap
三、ACTION_MOVE具体操作源码分析,主要是startScrollIfNeeded和trackMotionScroll
四、ACTION_UP具体操作源码分析,主要是PerformClick, mPendingCheckForLongPress, mFlingRunnable
说明:本文为转载并整理,某些地方讲的并不完善,比如没有提到对onInterceptTouchEvent(MotionEvent ev)方法的处理,在滚动过程中child view是如何布局的,ListView中滚动条加载机制,ListView是如何实现高效缓存的,及如何自定义ListView可显示多列并每个child View高度自适应,等等。先做个记录,后续有空继续研究。
一、onTouchEvent源码
@Override public boolean onTouchEvent(MotionEvent ev) { if (!isEnabled()) { // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return isClickable() || isLongClickable(); } // AbsListView 绘制与控制手指快速滚动的辅助类 if (mFastScroller != null) { boolean intercepted = mFastScroller.onTouchEvent(ev); if (intercepted) { return true; } } final int action = ev.getAction(); View v; int deltaY; // 获取触摸滚动时的速率 if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); // ListView触屏事件主要从ACTION操作划分 switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { ...... break; } case MotionEvent.ACTION_MOVE: { ...... break; } case MotionEvent.ACTION_UP: { switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: ...... mTouchMode = TOUCH_MODE_REST; break; case TOUCH_MODE_SCROLL: ...... break; } setPressed(false); // Need to redraw since we probably aren't drawing the selector anymore invalidate(); final Handler handler = getHandler(); if (handler != null) { handler.removeCallbacks(mPendingCheckForLongPress); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } mActivePointerId = INVALID_POINTER; if (PROFILE_SCROLLING) { if (mScrollProfilingStarted) { Debug.stopMethodTracing(); mScrollProfilingStarted = false; } } break; } case MotionEvent.ACTION_CANCEL: { mTouchMode = TOUCH_MODE_REST; ...... break; } case MotionEvent.ACTION_POINTER_UP: { ...... break; } } return true; }
二、ACTION_DOWN具体操作源码分析,主要是CheckForTap
case MotionEvent.ACTION_DOWN: { mActivePointerId = ev.getPointerId(0); final int x = (int) ev.getX(); final int y = (int) ev.getY(); // 手指按下时x,y坐标,获取当前选中的item int motionPosition = pointToPosition(x, y); // 如果ListView 数据未发生变化 if (!mDataChanged) { if ((mTouchMode != TOUCH_MODE_FLING) && (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 mTouchMode = TOUCH_MODE_DOWN; // TAP机制,主要是用于去除手指点击抖动 // 使Item处于按下状态 if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } // 添加到消息队列并延时ViewConfiguration.getTapTimeout()执行此runnable postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { if (ev.getEdgeFlags() != 0 && motionPosition < 0) { // If we couldn't find a view to click on, but the down event was touching // the edge, we will bail out and try again. This allows the edge correcting // code in ViewRoot to try to find a nearby view to select return false; } // 之前处于Fling模式 if (mTouchMode == TOUCH_MODE_FLING) { // Stopped a fling. It is a scroll. createScrollingCache(); // 更改为scroll mTouchMode = TOUCH_MODE_SCROLL; mMotionCorrection = 0; motionPosition = findMotionRow(y); reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); } } } // 对于ACTION_MOVE,ACTION_UP会使用的触屏位置信息进行记录 if (motionPosition >= 0) { // Remember where the motion event started v = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = v.getTop(); } mMotionX = x; mMotionY = y; mMotionPosition = motionPosition; mLastY = Integer.MIN_VALUE; break; }
三、ACTION_MOVE具体操作源码分析,主要是startScrollIfNeeded和trackMotionScroll
case MotionEvent.ACTION_MOVE: { final int pointerIndex = ev.findPointerIndex(mActivePointerId); final int y = (int) ev.getY(pointerIndex); // 获取y轴当前与前一次的偏移值 deltaY = y - mMotionY; switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: // 必须移动一段距离后才会执行滚动 startScrollIfNeeded(deltaY); break; case TOUCH_MODE_SCROLL: if (PROFILE_SCROLLING) { if (!mScrollProfilingStarted) { Debug.startMethodTracing("AbsListViewScroll"); mScrollProfilingStarted = true; } } // 手指移动 if (y != mLastY) { deltaY -= mMotionCorrection; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; // 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); } // ListView滚动到边界后不不能再进行移动 if (atEdge && getChildCount() > 0) { // Treat this like we're starting a new scroll from the current // position. This will let the user start scrolling back into // content immediately rather than needing to scroll back to the // point where they hit the limit first. int motionPosition = findMotionRow(y); if (motionPosition >= 0) { final View motionView = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = motionView.getTop(); } mMotionY = y; mMotionPosition = motionPosition; invalidate(); } // 记录当前Y值,用于下次计算偏移量 mLastY = y; } break; } break; }
四、ACTION_UP具体操作源码分析,主要是PerformClick, mPendingCheckForLongPress, mFlingRunnable
case MotionEvent.ACTION_UP: { switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: final int motionPosition = mMotionPosition; final View child = getChildAt(motionPosition - mFirstPosition); if (child != null && !child.hasFocusable()) { // 清理Item按下状态 if (mTouchMode != TOUCH_MODE_DOWN) { child.setPressed(false); } // 执行Item Click if (mPerformClick == null) { mPerformClick = new PerformClick(); } final AbsListView.PerformClick performClick = mPerformClick; performClick.mChild = child; performClick.mClickMotionPosition = motionPosition; performClick.rememberWindowAttachCount(); mResurrectToPosition = motionPosition; if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { final Handler handler = getHandler(); if (handler != null) { // 清理tap或者long press长按 handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap : mPendingCheckForLongPress); } mLayoutMode = LAYOUT_NORMAL; if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { mTouchMode = TOUCH_MODE_TAP; setSelectedPositionInt(mMotionPosition); layoutChildren(); child.setPressed(true); positionSelector(child); setPressed(true); if (mSelector != null) { Drawable d = mSelector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { ((TransitionDrawable) d).resetTransition(); } } postDelayed(new Runnable() { public void run() { child.setPressed(false); setPressed(false); if (!mDataChanged) { post(performClick); } mTouchMode = TOUCH_MODE_REST; } }, ViewConfiguration.getPressedStateDuration()); } else { mTouchMode = TOUCH_MODE_REST; } return true; } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { post(performClick); } } mTouchMode = TOUCH_MODE_REST; break; case TOUCH_MODE_SCROLL: final int childCount = getChildCount(); if (childCount > 0) { if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top && mFirstPosition + childCount < mItemCount && getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom) { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } else { // 是否执行ListView Scroll Fling final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); // 获取当前触屏滚动速率 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); if (Math.abs(initialVelocity) > mMinimumVelocity) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); // 执行ListView 快速滚动(Scroll Fling) mFlingRunnable.start(-initialVelocity); } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } } } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } break; } setPressed(false); // Need to redraw since we probably aren't drawing the selector anymore invalidate(); final Handler handler = getHandler(); if (handler != null) { handler.removeCallbacks(mPendingCheckForLongPress); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } mActivePointerId = INVALID_POINTER; if (PROFILE_SCROLLING) { if (mScrollProfilingStarted) { Debug.stopMethodTracing(); mScrollProfilingStarted = false; } } break; }
说明:本文为转载并整理,某些地方讲的并不完善,比如没有提到对onInterceptTouchEvent(MotionEvent ev)方法的处理,在滚动过程中child view是如何布局的,ListView中滚动条加载机制,ListView是如何实现高效缓存的,及如何自定义ListView可显示多列并每个child View高度自适应,等等。先做个记录,后续有空继续研究。
上一篇: 庖丁分词的源码分析 (6) 我自己对庖丁分词的修改应用
下一篇: 支付宝免签收款模式解析(发消息)
推荐阅读
-
Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案
-
Android 自定义相机及分析源码
-
Android中ListView下拉刷新的实现方法实例分析
-
Android开发关于Toast的源码分析
-
Android开发实现可拖动排序的ListView功能【附源码下载】
-
Android8.1 SystemUI源码分析之 电池时钟刷新
-
Android10.0-多用户切换流程源码分析
-
Android源码分析(一)-----如何快速掌握Android编译文件
-
Android源码分析(六)-----蓝牙Bluetooth源码目录分析
-
Android源码解析之应用程序框架层和系统运行库层日志系统分析