PRE_andevcon_mastering-the-android-touch-system.pdf
From Dave Smith && 《Android开发艺术探索》
点击事件的传递规则
- public boolean dispatchTouchEvent( MotionEvent ev) — 进行事件的分发
- public boolean onInterceptTouchEvent( MotionEvent event) — 是否拦截事件
- public boolean onTouchEvent( MotionEvent event) — 处理点击事件
public boolean dispatchTouchEvent( MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent( ev)) {
consume = onTouchEvent( ev);
} else {
consume = child. dispatchTouchEvent( ev);
}
return consume;
}
- ViewGroup
- onIntercepTouchEvent返回true,拦截当前事件,事件会交给该ViewGroup处理
- onIntercepTouchEvent返回false,不拦截当前事件,事件会传递给子元素处理,子元素的dispatchTouchEvent方法会被调用
- View
- 设置了onTouchListener, onTouchListener优先级比onTouchEvent要高
- onTouchListener的onTouch返回true, view的onTouchEvent不会被调用
- onTouchListener的onTouch返回false, view的onTouchEvent会被调用
- 没有设置onTouchListener,则调用onTouchEvent
- 如果设置了onClickListener,那么会调用其onClick方法—OnClickListener优先级最低,处于事件的末端
- 设置了onTouchListener, onTouchListener优先级比onTouchEvent要高
- 传递过程:Activity->Window->View。顶级view收到事件后会按照事件分发机制分发事件
- 如果一个View的onTouchEvent返回false,那么父容器的onTouchEvent将会被调用
//重写父类的onTouchEvent
override fun onTouchEvent(event: MotionEvent?): Boolean {
Log.d("testing", "just testing")
return super.onTouchEvent(event)
}
//setOnTouchListener
fun setOnTouchEventListenerTest() {
this.setOnTouchListener(object : OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
//do something
//如果返回true, view的onTouchEvent 就不会被调用,
//可见 OnTouchListener 的 优先级 高于 onTouchEvent, 这样做的好处是方便在外界处理点击 事件。
return false
}
})
}
总结11条
- 同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。
- 正常情况下,一个事件序列只能被一个View拦截且消耗。因为一旦一个元素拦截了某此事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。
- 某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用。
- 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理,那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来处理了,这就好比上级交给程序员一件事,如果这件事没有处理好,短期内上级就不敢再把事情交给这个程序员做了,二者是类似的道理。
- 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。
- ViewGroup默认不拦截任何事件。Android源码中ViewGroup的onInterceptTouch-Event方法默认返回false。
- View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。
- View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false。
- View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true。
- onClick会发生的前提是当前View是可点击的,并且它收到了down和up的事件。
- 事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外
ACTION_CANCEL事件
- 如果上层viewgroup拦截了事件,down事件还是会发到view,这样子view就有机会requestDisallowInterceptTouchEvent
- 如果没有requestDisallowInterceptTouchEvent,只发cancel给view,后续的move/up事件不会再发给view
```java
/**
- Tries to claim the user’s drag motion, and requests disallowing any
- ancestors from stealing events in the drag.
*/
private void attemptClaimDrag() {
//mParent = getParent();
if (mParent != null) {
} }mParent.requestDisallowInterceptTouchEvent(true);
@Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (iWantToKeepThisEventForMyself(event)) { attemptClaimDrag(); } //your logic here } else { //your logic here } } ```
how android handles touches
- Each user touch event is wrapped up as a MotionEvent
- Describes user’s current action:
- ACTION_DOWN
- ACTION_UP
- ACTION_MOVE
- ACTION_POINTER_DOWN
- ACTION_POINTER_UP
- ACTION_CANCEL
- Event metadaa included
- touch location
- number of pointers
- event time
- A “gesture” is defined as beginning with ACTION_DOWN and ending with ACTION_UP
how android handles touches
- envent starts at the activity with dispatchTouchEvent()
- Events flow top down through views
- Parents(ViewGroup) dispatch events to their children
- Can intercept events at any time
- Events flow down the chain (and back up) until consumed
- Views must declare interest by consuming ACTION_DOWN
- Further events not delivered for efficiency
- Any unconsumed events end at Activity with onTouchEvent()
- Optional External OnToucherListener can intercept touches on any View/ViewGroup
View
- Activity.dispatchTouchEvent()
- Always first to be called
- Sends events to root view attached to Window
- onTouchEvent()
- called if no views consume the event
- always last to be called
- View.dispatchTouchEvent()
- Sends envent to listener first, if exist
- View.OnTouchListener.onTouch()
- If not consumed processes the touch itself
- View.onTouchEvent()
- Sends envent to listener first, if exist
ViewGroup
- ViewGroup.dispatchTouchEvent()
- onInterceptTouchEvent()
- check if it should s supersede children
- Passes ACTION_CANCEL to active child
- return true once consumes all subsequent events
- For each child view, in reverse order they were added
- if touch is relevant(inside view), child.dispatchTouchEvent()
- if not handled by previous , dispatch to next view
- if no children handle event, listener gets a chance
- OnTouchListener.onTouch()
- if no listener, or not handled
- onTouchEvent()
- onInterceptTouchEvent()
Custom Touch Handling
- return true with ACTION_DOWN to show interest, even if you aren’t interested in ACTION_DOWN , return true;
- for other events, returning true simply stops further processing
- Call through to super whenever possible
- Don’t intercept events until you’re ready to take them all
- always handle ACTION_CANCEL
multi-touch handling
- MotionEvent.getPointerCount() - How many pointers are currently on the screen
- Use MotionEvent methods that take a pointer index parameter to get data for a specific pointer
System Touch Handlers
- don’t jump right to custom touch handling if you don’t have to…
- simple use:
- OnClickListener
- OnLongClickListener
- OnTouchListener
- OnScrollListener/View.onScrollChanged()
- complex use GestureDetector:
- onDown(), onSingleTapUp(), onDoubleTap()
- onLongPress()
- onScroll()
- onFling()
- complex use ScaleGestureDetector:
- onScaleBegin()
- onScale()
- onScaleEnd()