带着百分之百的热情去做你正在做的事,久而久之也能成为一部分天赋
1. 简介
MotionEvent继承自InputEvent,通常一次完整的MotionEvent有ACTION_DOWN、ACTION_MOVE、ACTION_UP,对于单点操作通过getX()获取坐标,多点操作通过getX(pointerIndex)获取坐标
| 操作 | 事件 |
|---|---|
| 单指操作 | ACTION_DOWN->ACTION_MOVE->ACTION_UP |
| 多指操作 | ACTION_DOWN->ACTION_POINTER_DOWN->ACTION_MOVE->ACTION_POINTER_UP->ACTION_UP |
/**
* Constant for {@link #getActionMasked}: A pressed gesture has started, the
* motion contains the initial starting location.
* <p>
* This is also a good time to check the button state to distinguish
* secondary and tertiary button clicks and handle them appropriately.
* Use {@link #getButtonState} to retrieve the button state.
* </p>
*/
public static final int ACTION_DOWN = 0;
/**
* Constant for {@link #getActionMasked}: A pressed gesture has finished, the
* motion contains the final release location as well as any intermediate
* points since the last down or move event.
*/
public static final int ACTION_UP = 1;
/**
* Constant for {@link #getActionMasked}: A change has happened during a
* press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
* The motion contains the most recent point, as well as any intermediate
* points since the last down or move event.
*/
public static final int ACTION_MOVE = 2;
/**
* Constant for {@link #getActionMasked}: The current gesture has been aborted.
* You will not receive any more points in it. You should treat this as
* an up event, but not perform any action that you normally would.
*/
public static final int ACTION_CANCEL = 3;
/**
* Constant for {@link #getActionMasked}: A movement has happened outside of the
* normal bounds of the UI element. This does not provide a full gesture,
* but only the initial location of the movement/touch.
* <p>
* Note: Because the location of any event will be outside the
* bounds of the view hierarchy, it will not get dispatched to
* any children of a ViewGroup by default. Therefore,
* movements with ACTION_OUTSIDE should be handled in either the
* root {@link View} or in the appropriate {@link Window.Callback}
* (e.g. {@link android.app.Activity} or {@link android.app.Dialog}).
* </p>
*/
public static final int ACTION_OUTSIDE = 4;
/**
* Constant for {@link #getActionMasked}: A non-primary pointer has gone down.
* <p>
* Use {@link #getActionIndex} to retrieve the index of the pointer that changed.
* </p><p>
* The index is encoded in the {@link #ACTION_POINTER_INDEX_MASK} bits of the
* unmasked action returned by {@link #getAction}.
* </p>
*/
public static final int ACTION_POINTER_DOWN = 5;
/**
* Constant for {@link #getActionMasked}: A non-primary pointer has gone up.
* <p>
* Use {@link #getActionIndex} to retrieve the index of the pointer that changed.
* </p><p>
* The index is encoded in the {@link #ACTION_POINTER_INDEX_MASK} bits of the
* unmasked action returned by {@link #getAction}.
* </p>
*/
public static final int ACTION_POINTER_UP = 6;
/**
* Constant for {@link #getActionMasked}: A change happened but the pointer
* is not down (unlike {@link #ACTION_MOVE}). The motion contains the most
* recent point, as well as any intermediate points since the last
* hover move event.
* <p>
* This action is always delivered to the window or view under the pointer.
* </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
* </p>
*/
public static final int ACTION_HOVER_MOVE = 7;
/**
* Constant for {@link #getActionMasked}: The motion event contains relative
* vertical and/or horizontal scroll offsets. Use {@link #getAxisValue(int)}
* to retrieve the information from {@link #AXIS_VSCROLL} and {@link #AXIS_HSCROLL}.
* The pointer may or may not be down when this event is dispatched.
* <p>
* This action is always delivered to the window or view under the pointer, which
* may not be the window or view currently touched.
* </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
* </p>
*/
public static final int ACTION_SCROLL = 8;
/**
* Constant for {@link #getActionMasked}: The pointer is not down but has entered the
* boundaries of a window or view.
* <p>
* This action is always delivered to the window or view under the pointer.
* </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
* </p>
*/
public static final int ACTION_HOVER_ENTER = 9;
/**
* Constant for {@link #getActionMasked}: The pointer is not down but has exited the
* boundaries of a window or view.
* <p>
* This action is always delivered to the window or view that was previously under the pointer.
* </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
* </p>
*/
public static final int ACTION_HOVER_EXIT = 10;
/**
* Constant for {@link #getActionMasked}: A button has been pressed.
*
* <p>
* Use {@link #getActionButton()} to get which button was pressed.
* </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
* </p>
*/
public static final int ACTION_BUTTON_PRESS = 11;
/**
* Constant for {@link #getActionMasked}: A button has been released.
*
* <p>
* Use {@link #getActionButton()} to get which button was released.
* </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
* </p>
*/
public static final int ACTION_BUTTON_RELEASE = 12;
2. 概要点
2.1 Touch事件对比
| onTouchEvent方法 | View | ViewGroup | Activity |
|---|---|---|---|
| dispatchTouchEvent | ✔ | ✔ | ✔ |
| onInterceptTouchEvent | ❌ | ✔ | ❌ |
| onTouchEvent | ✔ | ✔ | ✔ |
方法说明
| 方法 | 说明 |
|---|---|
| dispatchTouchEvent | return true:表示该View内部消化掉了所有事件 return false:表示事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费 return super.dispatchTouchEvent(ev):默认事件将分发给本层的事件拦截onInterceptTouchEvent方法进行处理 |
| onInterceptTouchEvent | return true:表示将事件进行拦截,并将拦截到的事件交由本层控件的onTouchEvent进行处理 return false:表示不对事件进行拦截,事件得以成功分发到子View return super.onInterceptTouchEvent(ev):默认表示不拦截该事件,并将事件传递给下一层View的dispatchTouchEvent |
| onTouchEvent | return true:表示onTouchEvent处理完事件后消费了此次事件 return fasle:表示不响应事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的onTouchEvent方法返回true return super.dispatchTouchEvent(ev):表示不响应事件,结果与return false一样 |
从以上过程中可以看出,dispatchTouchEvent无论返回true还是false,事件都不再进行分发,只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望,但是是否能够分发成功,则需要经过事件拦截onInterceptTouchEvent的审核。事件是否向上传递处理是由onTouchEvent的返回值决定的。
1. dispatchTouchEvent:
//View.java
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
2. onTouchEvent:
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
3. onInterceptTouchEvent:
//ViewGroup.java
/**
* Implement this method to intercept all touch screen motion events. This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
*
* <p>Using this function takes some care, as it has a fairly complicated
* interaction with {@link View#onTouchEvent(MotionEvent)
* View.onTouchEvent(MotionEvent)}, and using it requires implementing
* that method as well as this one in the correct way. Events will be
* received in the following order:
*
* <ol>
* <li> You will receive the down event here.
* <li> The down event will be handled either by a child of this view
* group, or given to your own onTouchEvent() method to handle; this means
* you should implement onTouchEvent() to return true, so you will
* continue to see the rest of the gesture (instead of looking for
* a parent view to handle it). Also, by returning true from
* onTouchEvent(), you will not receive any following
* events in onInterceptTouchEvent() and all touch processing must
* happen in onTouchEvent() like normal.
* <li> For as long as you return false from this function, each following
* event (up to and including the final up) will be delivered first here
* and then to the target's onTouchEvent().
* <li> If you return true from here, you will not receive any
* following events: the target view will receive the same event but
* with the action {@link MotionEvent#ACTION_CANCEL}, and all further
* events will be delivered to your onTouchEvent() method and no longer
* appear here.
* </ol>
*
* @param ev The motion event being dispatched down the hierarchy.
* @return Return true to steal motion events from the children and have
* them dispatched to this ViewGroup through onTouchEvent().
* The current target will receive an ACTION_CANCEL event, and no further
* messages will be delivered here.
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
4. dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount;
}
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
- 可以通过复写onInterceptTouchEvent(ev)方法,拦截子View的事件(即return true),把事件交给自己处理,则会执行自己对应的onTouchEvent方法。
- 子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦截;
-
- 一个点击事件产生后,它的传递过程如下:
Activity->Window->View。顶级View接收到事件之后,就会按相应规则去分发事件。如果一个View的onTouchEvent方法返回false,那么将会交给父容器的onTouchEvent方法进行处理,逐级往上,如果所有的View都不处理该事件,则交由Activity的onTouchEvent进行处理。
- 一个点击事件产生后,它的传递过程如下:
- 如果某一个View开始处理事件,如果他不消耗ACTION_DOWN事件(也就是onTouchEvent返回false),则同一事件序列比如接下来进行ACTION_MOVE,则不会再交给该View处理。
- ViewGroup默认不拦截任何事件。
- 诸如TextView、ImageView这些不作为容器的View,一旦接受到事件,就调用onTouchEvent方法,它们本身没有onInterceptTouchEvent方法。正常情况下,它们都会消耗事件(返回true),除非它们是不可点击的(clickable和longClickable都为false),那么就会交由父容器的onTouchEvent处理。
- 点击事件分发过程如下 dispatchTouchEvent—->OnTouchListener的onTouch方法—->onTouchEvent–>OnClickListener的onClick方法。也就是说,我们平时调用的setOnClickListener,优先级是最低的,所以,onTouchEvent或OnTouchListener的onTouch方法如果返回true,则不响应onClick方法…
2.2 事件消费流程
3. 常见问题
3.1 纵向RecyclerView嵌套横向RecyclerView 划动冲突
【前置原理】
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mLayoutSuppressed) {
// When layout is suppressed, RV does not intercept the motion event.
// A child view e.g. a button may still get the click.
return false;
}
// Clear the active onInterceptTouchListener. None should be set at this time, and if one
// is, it's because some other code didn't follow the standard contract.
mInterceptingOnItemTouchListener = null;
if (findInterceptingOnItemTouchListener(e)) {
cancelScroll();
return true;
}
if (mLayout == null) {
return false;
}
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
final boolean canScrollVertically = mLayout.canScrollVertically();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(e);
final int action = e.getActionMasked();
final int actionIndex = e.getActionIndex();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mIgnoreMotionEventTillDown) {
mIgnoreMotionEventTillDown = false;
}
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
if (mScrollState == SCROLL_STATE_SETTLING) {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
stopNestedScroll(TYPE_NON_TOUCH);
}
// Clear the nested offsets
mNestedOffsets[0] = mNestedOffsets[1] = 0;
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
break;
case MotionEvent.ACTION_POINTER_DOWN:
mScrollPointerId = e.getPointerId(actionIndex);
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
break;
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id "
+ mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
if (mScrollState != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
mLastTouchX = x;
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = y;
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
}
break;
case MotionEvent.ACTION_POINTER_UP: {
onPointerUp(e);
}
break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.clear();
stopNestedScroll(TYPE_TOUCH);
}
break;
case MotionEvent.ACTION_CANCEL: {
cancelScroll();
}
}
return mScrollState == SCROLL_STATE_DRAGGING;
}
RecyclerView接收到的滑动只要滑动的距离绝对值大于阈值mTouchSlop,就会触发拦截
【分析方法】 此处比较重要的是弄清楚滑动的需求,根据事件分发流程的逻辑,在dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent中对事件进行拦截分发处理,使之满足需求按照设想的逻辑运行。
【解决方法】
- 纵向RecyclerView
/**
* 纵向RecyclerView
*/
class ParentRecyclerView(context: Context, attrs: AttributeSet) : RecyclerView(context, attrs){
companion object {
private const val TAG = "ParentRecyclerView"
}
private var mInitialTouchX = 0
private var mInitialTouchY = 0
private var mScrollPointerId = -1
private var mTouchSlop = 0
init {
mTouchSlop = ViewConfiguration.get(getContext()).scaledTouchSlop
}
override fun setScrollingTouchSlop(slopConstant: Int) {
val vc: ViewConfiguration = ViewConfiguration.get(this.context)
when (slopConstant) {
TOUCH_SLOP_DEFAULT -> {
this.mTouchSlop = vc.scaledTouchSlop
}
TOUCH_SLOP_PAGING -> {
this.mTouchSlop = vc.scaledPagingTouchSlop
}
else -> Log.w(
TAG,
"setScrollingTouchSlop(): bad argument constant $slopConstant; using default value"
)
}
super.setScrollingTouchSlop(slopConstant)
}
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
when (e.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mScrollPointerId = e.getPointerId(0)
this.mInitialTouchX = (e.x + 0.5f).toInt()
this.mInitialTouchY = (e.y + 0.5f).toInt()
return super.onInterceptTouchEvent(e)
}
MotionEvent.ACTION_MOVE -> {
val index = e.findPointerIndex(this.mScrollPointerId)
if (index < 0) {
Log.e(
TAG,
"Error processing scroll; pointer index for id " + this.mScrollPointerId + " not found. Did any MotionEvents get skipped?"
)
return false
}
val x = (e.getX(index) + 0.5f).toInt()
val y = (e.getY(index) + 0.5f).toInt()
val dx = x - this.mInitialTouchX
val dy = y - this.mInitialTouchY
//横向划动, 停止滚动,不拦截事件,将事件传给子View
if (abs(dx) > mTouchSlop && abs(dx) > abs(dy)) {
stopScroll()
return false
}
if (scrollState != SCROLL_STATE_DRAGGING) {
var startScroll = false
if (abs(dy) > this.mTouchSlop && abs(dy) > abs(dx)) {
startScroll = true
}
return startScroll && super.onInterceptTouchEvent(e)
}
}
MotionEvent.ACTION_UP -> {
}
MotionEvent.ACTION_CANCEL -> {
}
}
return super.onInterceptTouchEvent(e)
}
override fun onTouchEvent(e: MotionEvent): Boolean {
when (e.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mScrollPointerId = e.getPointerId(0)
this.mInitialTouchX = (e.x + 0.5f).toInt()
this.mInitialTouchY = (e.y + 0.5f).toInt()
}
MotionEvent.ACTION_MOVE -> {
val index = e.findPointerIndex(this.mScrollPointerId)
if (index < 0) {
Log.e(
TAG,
"Error processing scroll; pointer index for id " + this.mScrollPointerId + " not found. Did any MotionEvents get skipped?"
)
return false
}
val x = (e.getX(index) + 0.5f).toInt()
val y = (e.getY(index) + 0.5f).toInt()
val dx = x - this.mInitialTouchX
val dy = y - this.mInitialTouchY
//横向划动则不拦截
if (abs(dx) > mTouchSlop && abs(dx) > abs(dy)) {
stopScroll()
return false
}
if (scrollState != SCROLL_STATE_DRAGGING) {
var startScroll = false
if (abs(dy) > this.mTouchSlop && abs(dy) > abs(dx)) {
startScroll = true
}
return startScroll && super.onInterceptTouchEvent(e)
}
}
MotionEvent.ACTION_UP -> {
}
MotionEvent.ACTION_CANCEL -> {
}
}
return super.onTouchEvent(e)
}
}
- 横向RecyclerView
/**
* RecyclerView中嵌套的横向RecyclerView
*/
class ChildRecyclerView(context: Context, attrs: AttributeSet) : RecyclerView(context, attrs) {
companion object {
private const val TAG = "ChildRecyclerView"
}
private var mInitialTouchX = 0
private var mInitialTouchY = 0
private var mScrollPointerId = -1
private var mTouchSlop = 0
init {
val vc = ViewConfiguration.get(context)
mTouchSlop = vc.scaledTouchSlop
}
override fun setScrollingTouchSlop(slopConstant: Int) {
val vc: ViewConfiguration = ViewConfiguration.get(this.context)
when (slopConstant) {
TOUCH_SLOP_DEFAULT -> {
this.mTouchSlop = vc.scaledTouchSlop
}
TOUCH_SLOP_PAGING -> {
this.mTouchSlop = vc.scaledPagingTouchSlop
}
else -> Log.w(
TAG,
"setScrollingTouchSlop(): bad argument constant $slopConstant; using default value"
)
}
super.setScrollingTouchSlop(slopConstant)
}
override fun dispatchTouchEvent(e: MotionEvent): Boolean {
when (e.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mScrollPointerId = e.getPointerId(0)
this.mInitialTouchX = (e.x + 0.5f).toInt()
this.mInitialTouchY = (e.y + 0.5f).toInt()
Log.i(
TAG,
"dispatchTouchEvent ACTION_DOWN mInitialTouchX:$mInitialTouchX, mInitialTouchY:$mInitialTouchY"
)
}
MotionEvent.ACTION_MOVE -> {
val index = e.findPointerIndex(this.mScrollPointerId)
if (index < 0) {
Log.e(
TAG,
"Error processing scroll; pointer index for id " + this.mScrollPointerId + " not found. Did any MotionEvents get skipped?"
)
return false
}
val x = (e.getX(index) + 0.5f).toInt()
val y = (e.getY(index) + 0.5f).toInt()
val dx = x - this.mInitialTouchX
val dy = y - this.mInitialTouchY
Log.i(
TAG,
"dispatchTouchEvent ACTION_MOVE, dx:$dx, dy:$dy, mTouchSlop:$mTouchSlop"
)
//如果纵向划动,则停止滚动,不再分发
if (abs(dy) > this.mTouchSlop && abs(dy) > abs(dx)) {
stopScroll()
return false
}
}
}
return super.dispatchTouchEvent(e)
}
}