事件分發原始碼分析

醉墨重生發表於2018-02-25

View的TouchEvent事件分發原始碼

View 與 Touch 相關的有兩個非常重要的方法

  • dispatchTouchEvent 事件分發
//預設是false
boolean result = false;
// ListenerInfo li = mListenerInfo;
ListenerInfo li = mListenerInfo;

//如果是enabled而且觸控事件返回為true,則返回true
 if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
 }
 //如果result為false則執行onTouchEvent方法,若執行的onTouchEvent為true,則result為true
if (!result && onTouchEvent(event)) {
       result = true;
  }

點選事件——>在View的onTouchEvent -> case MotionEvent.ACTION_UP: 裡面呼叫了 performClick()——>li.mOnClickListener.onClick(this); 點選事件

  • onTouchEvent方法(一般都會被我們複寫)

測試
首先自定義view

public class TouchView extends View {
    public TouchView(Context context) {
        super(context);
    }

    public TouchView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TouchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("TAG", "onTouchEvent--->" + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("TAG", "dispatchTouchEvent--->" + event.getAction());
        return super.dispatchTouchEvent(event);
    }
}

使用自定義view

    View view = findViewById(R.id.touch_view);
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("TAG","onTouch-->"+event.getAction());
                return false;
            }
        });
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG","onClick");
            }
        });

分析(0代表down,1代表up,2代表move)

第一種場景:OnTouchListener onTouchEvent dispatchTouchEvent OnClickListener 四個都有的情況下 前提是OnTouchListener 返回false

結果:
dispatchTouchEvent—>0
onTouch–>0
onTouchEvent—>0
dispatchTouchEvent—>2
onTouch–>2
onTouchEvent—>2
dispatchTouchEvent—>1
onTouch–>1
onTouchEvent—>1
onClick

第二種場景:OnTouchListener onTouchEvent dispatchTouchEvent OnClickListener 四個都有的情況下 前提是OnTouchListener 返回true,其他不動

View view = findViewById(R.id.touch_view);
            view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("TAG","onTouch-->"+event.getAction());
                return true;
            }
});

此時li.mOnTouchListener.onTouch(this, event)) 方法返回true則不會執行onTouchEvent方法

結果
dispatchTouchEvent—>0
onTouch–>0
dispatchTouchEvent—>2
onTouch–>2
dispatchTouchEvent—>1
onTouch–>1

第三種場景:onTouchEvent dispatchTouchEvent OnClickListener 三個的情況下 設定OnTouchEvent為true
OnTouchEvent預設點選了返回的是true

結果
dispatchTouchEvent—>0
onTouchEvent—>0
dispatchTouchEvent—>2
onTouchEvent—>2
dispatchTouchEvent—>2
dispatchTouchEvent—>1
onTouchEvent—>1

return super.onTouchEvent(event)和return true是有區別的,當設定為true時不會有onClick方法,而預設值會
原因:當設定為true時不會進入view中的onTouchEvent方法

第四種場景:OnTouchListener onTouchEvent dispatchTouchEvent OnClickListener 四個的情況下 設定dispatchTouchEvent 為true,其他的返回原樣

 @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("TAG", "dispatchTouchEvent--->" + event.getAction());
        return true;
    }

結果
dispatchTouchEvent—>0
dispatchTouchEvent—>2
dispatchTouchEvent—>1

view的dispatch的方法不會被執行

ViewGroup 的事件分發 原始碼分析

首先準備工作
將原本activity_main佈局中的LinearLayout換成自定義View的TouchViewGroup

public class TouchViewGroup extends LinearLayout {
    public TouchViewGroup(Context context) {
        super(context);
    }

    public TouchViewGroup(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TouchViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //事件分發
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("TAG", "ViewGroup dispatchTouchEvent--->" + event.getAction());//這是自己的處理事件
        return super.dispatchTouchEvent(event);
    }

    //事件攔截
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("TAG", "ViewGroup onInterceptTouchEvent--->" + ev.getAction());//這是自己的處理事件
        return super.onInterceptTouchEvent(ev);
    }

    //事件觸控
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("TAG", "ViewGroup onTouchEvent--->" + event.getAction());//這是自己的處理事件
        return super.onTouchEvent(event);
    }
}

並修改其中的view列印事件

 View view = findViewById(R.id.touch_view);
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("TAG", "View onTouch-->" + event.getAction());
                return false;
            }
        });
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG", "onClick");
            }
        });

public class TouchView extends View {
    public TouchView(Context context) {
        super(context);
    }

    public TouchView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TouchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("TAG", "View onTouchEvent--->" + event.getAction());//這是自己的處理事件
        return super.onTouchEvent(event);//預設返回true
        //而點選事件是系統的UP中的事件,所以你返回true的時候並沒有進入系統中的
        //return true;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("TAG", "View dispatchTouchEvent--->" + event.getAction());
        return super.dispatchTouchEvent(event);
    }
}

dispatchTouchEvent() 原始碼看看

boolean handled = false;

 // Handle an initial down.
 if (actionMasked == MotionEvent.ACTION_DOWN) {
    // 清除TouchTargets 只要知道 mFirstTouchTarget = null
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

 final boolean intercepted;
   if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
         final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {//disallowIntercept==false
                    intercepted = onInterceptTouchEvent(ev);//預設返回false
                    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;
            }

TouchTarget newTouchTarget = null;
 if (!canceled && !intercepted) {
      if (newTouchTarget == null && childrenCount != 0) {//ACTION_DOWN
      for (int i = childrenCount - 1; i >= 0; i--) {// 反序的for迴圈  獲取子View child
       newTouchTarget = getTouchTarget(child);
       if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // 如果子 View 返回true 就會進來 主要給 mFirstTouchTarget = target; 賦值  
            addTouchTarget(child, idBitsToAssign);                             
       }
     }
   }

}
/** 
* Clears all touch targets. * mFirstTouchTarget = null; 這句話核心清除 mFirstTouchTarget 
*/
private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
        final boolean handled;
          // Canceling motions is a special case.  We don't need to perform any transformations
         // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                // child == null 會呼叫 自己的 super View.dispatchTouchEvent(event)
                handled = super.dispatchTouchEvent(event);
            } else {
                 // child == null 會呼叫 自己的 super View.dispatchTouchEvent(event)
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
}

onInterceptTouchEvent() 原始碼看看

onTouchEvent() 原始碼看看

場景分析

第一種常見,View和ViewGroup的TouchEvent全部設定為預設

結果:
ViewGroup dispatchTouchEvent—>0
ViewGroup onInterceptTouchEvent—>0
View dispatchTouchEvent—>0
View onTouch–>0
View onTouchEvent—>0
ViewGroup dispatchTouchEvent—>2
ViewGroup onInterceptTouchEvent—>2
View dispatchTouchEvent—>2
View onTouch–>2
View onTouchEvent—>2
ViewGroup dispatchTouchEvent—>1
ViewGroup onInterceptTouchEvent—>1
View dispatchTouchEvent—>1
View onTouch–>1
View onTouchEvent—>1

即正常情況下:

  • 第一次DOWN
ViewGroup.dispatchTouchEvent ->ViewGroup.onInterceptTouchEvent -> View.dispatchTouchEvent ->View.onTouch -> View.onTouchEvent
  • 第二次MOVE
 ViewGroup.dispatchTouchEvent -> ViewGroup onInterceptTouchEvent -> View.dispatchTouchEvent -> View.onTouch ->View.onTouchEvent
  • 第三次UP
ViewGroup.dispatchTouchEvent -> ViewGroup onInterceptTouchEvent    ->  View.onTouch -> View.onTouchEvent -> View.onclick

第二種情況:onClick 沒有 理解為沒有消費事件,即沒有進入onClick方法不返回true

ViewGroup.dispatchTouchEvent -> ViewGroup.onInterceptTouchEvent -> View.dispatchTouchEvent -> View.onTouch -> View onTouchEvent -> ViewGroup.onTouchEvent

第三種情況:在 View 的 onTouchEvent() 方法裡面返回true 的情況下

第一次DOWN ViewGroup.dispatchTouchEvent -> ViewGroup.onInterceptTouchEvent -> View.dispatchTouchEvent -> View.onTouch -> View.onTouchEvent
第二次MOVE ViewGroup.dispatchTouchEvent -> ViewGroup.onInterceptTouchEvent -> View.dispatchTouchEvent -> View.onTouch -> View.onTouchEvent
第三次UP ViewGroup.dispatchTouchEvent -> ViewGroup.onInterceptTouchEvent -> View.onTouch -> View.onTouchEvent

第四種情況:在 ViewGroup 的 onInterceptTouchEvent() 方法裡面返回 true 的情況下

ViewGroup.dispatchTouchEvent -> ViewGroup.onInterceptTouchEvent -> ViewGroup.onTouchEvent

總結

1.如果說子 View 沒有一個地方返回 true ,只會進來一次只會響應 DOWN 事件,代表不需要消費該事件,如果你想響應 MOVE,UP 必須找個地方ture

2.對於ViewGroup來講,如果你想攔截子 View 的 Touch 事件,可以覆寫 onInterceptTouchEvent 返回 true 即可 , 如果說 onInterceptTouchEvent 返回的是 true 會執行該 ViewGroup 的 onTouchEvent 方法 , 如果子 View 沒有消費 touch 事件也會呼叫該 ViewGroup 的 onTouchEvent 方法

相關文章