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) — 处理点击事件
  1. public boolean dispatchTouchEvent( MotionEvent ev) {
  2. boolean consume = false;
  3. if (onInterceptTouchEvent( ev)) {
  4. consume = onTouchEvent( ev);
  5. } else {
  6. consume = child. dispatchTouchEvent( ev);
  7. }
  8. return consume;
  9. }
  • 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优先级最低,处于事件的末端
  • 传递过程:Activity->Window->View。顶级view收到事件后会按照事件分发机制分发事件
    • 如果一个View的onTouchEvent返回false,那么父容器的onTouchEvent将会被调用
  1. //重写父类的onTouchEvent
  2. override fun onTouchEvent(event: MotionEvent?): Boolean {
  3. Log.d("testing", "just testing")
  4. return super.onTouchEvent(event)
  5. }
  6. //setOnTouchListener
  7. fun setOnTouchEventListenerTest() {
  8. this.setOnTouchListener(object : OnTouchListener {
  9. override fun onTouch(v: View?, event: MotionEvent?): Boolean {
  10. //do something
  11. //如果返回true, view的onTouchEvent 就不会被调用,
  12. //可见 OnTouchListener 的 优先级 高于 onTouchEvent, 这样做的好处是方便在外界处理点击 事件。
  13. return false
  14. }
  15. })
  16. }

总结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) {
      1. 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()

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()

Android Touch Event - 图1

Android Touch Event - 图2

Android Touch Event - 图3

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()