一、View的基礎(chǔ)知識
1、View是界面層的控件的一種抽象,包括ViewGroup
2、View的位置參數(shù)
VIew的位置主要由四個頂點來決定,對應(yīng)View的四個屬性:top,left,right,bottom
VIew的坐標系:X軸和Y軸正方向分別為下和右;
注意:以下參數(shù)都是相對于View的父布局坐標
Left = getLeft() 獲取左上角橫坐標
Top = getTop() 獲取左上角縱坐標
Right = getRight() 獲取右下角橫坐標
Bottom = getBottom() 獲取右下角縱坐標
x = getX() 獲取左上角x坐標
y= getY() 獲取左上角y坐標
translationX = getTranslationX() 獲取左上角相對于父容器偏移量
translationY = getTranslationY() 獲取左上角相對于父容器偏移量
注意:這兩個參數(shù)是相對于手機屏幕左上角的坐標
rawX = getRawX() 獲取左上角x坐標
rawY = getRawY() 獲取左上角y坐標
View事件相關(guān)對象
1、MotionEvent:
觸摸事件對象,包括坐標
2等浊、TouchSlop:
系統(tǒng)所能識別的被認為是滑動的最小距離
獲取方式:ViewConfiguration.get(Context).getScaledTouchSlop();
3、VelocityTracker:
速度追蹤,用于追蹤手指在滑動過程中的速度,包括水平和垂直方向速度,由上往下為正值;
速度 =(終點位置- 起點位置)/時間
4摹蘑、GestureDetoctor:
手勢檢測,用于輔助檢測用戶的單擊筹燕、滑動、長按衅鹿、雙擊等行為;一般使用檢測雙擊;
5撒踪、Scroller:
彈性滑動對象,用于實現(xiàn)View的彈性滑動.
二、View的滑動
View滑動方式三種:
1大渤、使用scrollTo/scrollBy,滑動的是內(nèi)容
scroolTo:絕對滑動
mScrollX = view的邊緣- view中內(nèi)容邊緣
所以從左往右滑動,mScrollX為負值,反之為正值;
從上往下滑動,mScroolY為負值,反之為正值;
scroolBy:相對滑動,其內(nèi)部調(diào)用的scrollTo方法
2制妄、平移動畫
translationX,translationY
3、改變布局參數(shù)MarginLayoutParams
三泵三、View的彈性滑動
即滑動漸進式滑動,三種方式
1耕捞、使用Scroller
2、使用動畫
3烫幕、使用延時策略
四俺抽、View的事件分發(fā)機制
事件分發(fā)機制包括三部分:
- 從屏幕到App;
- 從App到對應(yīng)頁面较曼;
- 頁面內(nèi)具體分發(fā)磷斧。
1、事件分發(fā)從屏幕到App
- 硬件與內(nèi)核部分
Linux內(nèi)核會將硬件產(chǎn)生的觸摸事件包裝為Event存到/dev/input/event[x]目錄下捷犹; - SystemServer部分
在SystemServer進程中管理事件輸入的InputManagerService弛饭,會啟動一個讀線程,也就是InputReader萍歉,它會從系統(tǒng)也就是/dev/input/目錄拿到任務(wù)侣颂,并且分發(fā)給InputDispatcher線程,然后進行統(tǒng)一的事件分發(fā)調(diào)度翠桦。 - 跨進程通信傳遞給App
我們的App中的Window與InputManagerService之間的通信實際上使用的InputChannel(在ViewRootImpl.setView()過程中横蜒,也會同時注冊InputChannel)胳蛮;InputChannel是一個pipe销凑,底層實際是通過socket進行通信。最終會走到InputEventReceiver.dispachInputEvent方法仅炊。
2斗幼、事件分發(fā)從App到對應(yīng)頁面
1. 事件回傳到ViewRootImpl
InputEventReceiver.dispachInputEvent方法即ViewRootImpl中WindowInputEventReceiver實現(xiàn)了InputEventReceiver接口,最終調(diào)用
ViewRootImpl.enqueueInputEvent方法
//InputEventReceiver.java
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
//ViewRootImpl.java ::WindowInputEventReceiver
final class WindowInputEventReceiver extends InputEventReceiver {
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
}
//ViewRootImpl.java
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
if (processImmediately) {
//責(zé)任鏈分發(fā)
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
2. 第一次責(zé)任鏈分發(fā)
調(diào)用doProcessInputEvents()方法抚垄,涉及到事件分發(fā)中的第一次責(zé)任鏈分發(fā)(InputStage的責(zé)任鏈ViewRootImpl.setView方法中就已經(jīng)組裝好了)
不同責(zé)任鏈作用:
SyntheticInputStage蜕窿。綜合處理事件階段谋逻,比如處理導(dǎo)航面板、操作桿等事件桐经。
ViewPostImeInputStage毁兆。視圖輸入處理階段,比如按鍵阴挣、手指觸摸等運動事件气堕,我們熟知的view事件分發(fā)就發(fā)生在這個階段。
NativePostImeInputStage畔咧。本地方法處理階段茎芭,主要構(gòu)建了可延遲的隊列。
EarlyPostImeInputStage誓沸。輸入法早期處理階段梅桩。
ImeInputStage。輸入法事件處理階段拜隧,處理輸入法字符宿百。
ViewPreImeInputStage。視圖預(yù)處理輸入法事件階段洪添,調(diào)用視圖view的dispatchKeyEventPreIme方法犀呼。
NativePreImeInputStage。本地方法預(yù)處理輸入法事件階段薇组。
3. View觸摸事件分發(fā)給ViewPostImeInputStage
最終ViewPostImeInputStage中會調(diào)用mView.dispatchPointerEvent方法外臂;(ViewRootImpl中的mView就是DecorView)
//View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
4. 事件在Activity,Window,DecorView中的傳遞
最終調(diào)用DecorView的dispatchTouchEvent
//DecorView.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//cb其實就是對應(yīng)的Activity/Dialog
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
事件分發(fā)經(jīng)過了: ViewRootImpl -> DecorView -> Activity -> PhoneWindow -> DecorView
3、事件分發(fā)頁面內(nèi)部
1. ViewGroup分發(fā)事件
@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();
}
//檢查是否攔截:只有ActionDown或者mFirstTouchTarget為空時才會判斷是否攔截
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;
//事件傳遞給子view
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;
//遍歷子View
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 - 1;
}
//2.判斷事件坐標
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);
//3.傳遞事件
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();
//如果子View消耗了則給mFirstTouchTarget賦值
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;
}
}
}
//mFirstTouchTarget不為空時會調(diào)用dispatchTransformendTouchEvent
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;
}
private boolean dispatchTransformedTouchEvent(View child) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
}
- 只有當(dāng)Action_Down或者mFirstTouchTarget不為空時才判斷是否攔截律胀;
- mFirstTouchTarget是個鏈表結(jié)構(gòu)宋光,代表某個子View消費了事件,為null則表示沒有子View消費事件
- disallowIntercept字段是用來是否不攔截炭菌;由子View調(diào)用方法控制罪佳;
getParent().requestDisallowInterceptTouchEvent(true);
- mFirstTouchTarget==null有兩種情況,一是ViewGroup攔截黑低,二是子View沒有處理事件,兩種情況最后都回調(diào)到ViewGroup.onTouchEvent赘艳;
- dispatchTransformedTouchEvent做的主要就是兩個事,如果child不為null克握,則事件分發(fā)到child,否則調(diào)用super.dispatchTouchEvent,并最終返回結(jié)果
- 利用dispatchTransformedTouchEvent,如果返回true,則通過addTouchTarget對mFirstTouchTarget賦值蕾管。
2. 子View分發(fā)事件
public boolean dispatchTouchEvent(MotionEvent event) {
//如果事件應(yīng)該首先由可訪問性焦點處理。
if (event.isTargetAccessibilityFocus()) {
// 我們沒有焦點或沒有虛擬后代有它菩暗,不要處理事件掰曾。
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// 我們有焦點并得到了事件,然后使用正常的事件調(diào)度停团。
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 新手勢的防御性清理
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// 如果這是手勢的結(jié)束旷坦,則在嵌套滾動后清理掏熬;如果我們嘗試了 ACTION_DOWN 但我們不想要其余的手勢,也可以取消它秒梅。
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
子View的onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED
&& (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= mAmbiguousGestureMultiplier;
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(0 ,x,y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
- 如果設(shè)置了setOnTouchListener并且返回為true旗芬,那么onTouchEvent就不再執(zhí)行
- 否則執(zhí)行onTouchEvent,我們常用的OnClickListenr就是在onTouchEvent里的Up事件中觸發(fā)的捆蜀。
總結(jié):
1岗屏、事件分發(fā)的本質(zhì)就是一個遞歸方法,通過往下傳遞漱办,調(diào)用dispatchTouchEvent方法这刷,找到事件的處理者,這也就是項目中常見的責(zé)任鏈模式娩井。
2暇屋、在分發(fā)過程中,ViewGroup通過onInterceptTouchEvent判斷是否攔截事件
3洞辣、在分發(fā)過程中咐刨,View的默認通過onTouchEvent處理事件
4、如果底層View不消費扬霜,則默認一步步往上執(zhí)行父元素onTouchEvent方法定鸟。
5、如果所有View的onTouchEvent方法都返回false著瓶,則最后會執(zhí)行到Activity的onTouchEvent方法联予,事件分發(fā)也就結(jié)束了。
參考:
Android事件分發(fā)8連問
Android事件分發(fā)
五材原、View的滑動沖突
解決方法:
1沸久、外部攔截法
核心思想:父容器需要的事件攔截,不需要的不攔截;
重寫父容器的onInterceptTouchEvent方法,在內(nèi)部MotionEvent.ACTION_MOVE事件中做相應(yīng)攔截;
/**
* 攔截事件
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
/*如果攔截了Down事件,則子類不會拿到這個事件序列*/
case MotionEvent.ACTION_DOWN:
lastXIntercept = x;
lastYIntercept = y;
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
final int deltaX = x - lastXIntercept;
final int deltaY = y - lastYIntercept;
/*根據(jù)條件判斷是否攔截該事件*/
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
lastXIntercept = x;
lastYIntercept = y;
return intercepted;
}
2、內(nèi)部攔截法
核心思想:父容器不攔截所有事件,子元素需要此事件就消耗掉,否則交給父容器處理;
重寫子View的dispatchTouchEvent方法,在內(nèi)部MotionEvent.ACTION_DOWN事件中做相應(yīng)攔截;
MotionEvent.ACTION_MOVE事件中根據(jù)條件取消攔截;
ViewGroup中onInterceptTouchEvent方法處理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//內(nèi)部攔截法
int action = ev.getAction();
if (action==MotionEvent.ACTION_DOWN)//down事件不進行攔截
{
return false;
}else {
return true;
}
}
子View中dispatchTouchEvent方法處理
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
parent= getParent();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (viewgroup需要此事件) //根據(jù)實際情況換成你自己的判定條件
{
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}