好久没做相关的UI开发,面试总被问到,这次自己看了下源码总结下,其实也并不是很复杂.
通常我们手指触摸屏幕后,会优先调用到Activity的
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
这里MotionEvent就是Touch事件,会分发到Window里,我们都知道Window实现上在Activity里就是PhoneWindow了.
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
上面会把点击事件分发到DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView实际上是一个FrameLayout,FrameLayout的爸爸是ViewGroup,ViewGroup的爸爸是View. 不过对于View中dispatchTouchEvent方法,ViewGroup自己重写了,所以上面super.dispatchTouchEvent(event)调用到的ViewGroup的dispatchTouchEvent。看看下面流程,下面代码是省略代码,为了方便理解,只写了部分关键流程
public boolean dispatchTouchEvent(MotionEvent ev) {
final boolean intercepted;
intercepted = onInterceptTouchEvent(ev);
if(!intercepted) {
//遍历子View, 走它们的dispatchTouchEvent,从而走子View的onTouchEvent函数
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//代码省略...
newTouchTarget = addTouchTarget(child, idBitsToAssign);
}
}
} else {
dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS); //这里会回掉父类的dispatchTouchEvent,走onTouchEvent函数
}
if (newTouchTarget == null) {
dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS); //这里会回掉父类的dispatchTouchEvent,走onTouchEvent函数
}
}
这里处理流程是先看onInterceptTouchEvent返回值,onInterceptTouchEvent通常在自定义View中进行重写,返回是否拦截Touch事件,如果返回true拦截了,后续都走自己父类的dispatchTouchEvent(这里也就是View),从而走onTouchEvent流程,不会进行事件分发。如果是返回false不拦截,就会向子View分发Touch事件,但是这里要注意dispatchTransformedTouchEvent返回值,如果有子View消化了该Touch事件就不会走自己父类的dispatchTouchEvent,也就不会走到自己的onTouchEvent流程里了,如果没有消化事件还是会走自己父类的dispatchTouchEvent,从而走onTouchEvent流程。
抛开onInterceptTouchEvent()回调函数不谈,无非事件分发就是从ViewGroup,向子View分发事件(当然子View也包含ViewGroup),子View收到事件后不处理,还是会交给上层的ViewGroup自己处理.
我们来看看View中的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {
//代码省略…
boolean result = false;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
//代码省略…
return result
}
这里result代表了View是否处理该事件,通常这里会调用到onTouchEvent,onTouchEvent会处理View中Touch事件,上面的两层含义是,一个是外部设置了OnTouchListener,并在监听中返回true代表了View处理该Touch事件,另外一个onTouchEvent(event)中返回true也代表了View处理该Touch事件. 接下来看看onTouchEvent中的实现.
public boolean onTouchEvent(MotionEvent event) {
//代码省略…
switch (action) {
case MotionEvent.ACTION_UP:
performClickInternal(); //执行外部设置的OnClickListener
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
}
return true;
//代码省略…
}
上面通常是返回true,说明是处理了该Touch事件.
下面是举例子时间
实现两个自定义View, 一个TouchView和一个TouchViewGroup
class TouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
Log.i("touch", "touch dispatchTouchEvent event " + event!!.actionMasked)
return super.dispatchTouchEvent(event)
}
}
class TouchViewGroup(
context: Context, attrs: AttributeSet?
) : FrameLayout(context, attrs) {
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
Log.i("touch", "dispatchTouchEvent TouchViewGroup event " + ev!!.actionMasked)
return super.dispatchTouchEvent(ev)
}
}
放入如下布局
案例一:事件的大致传递流程, 事件是从Activity传递到PhoneWindow, 再到decorView,再到TouchViewGroup,然后到TouchView
基于上面代码,添加如下代码
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
Log.i("touch", "dispatchTouchEvent MainActivity event " + ev!!.actionMasked)
return super.dispatchTouchEvent(ev)
}
}
输出:
2020-04-13 16:31:01.434 28958-28958/com.example.k_demo I/touch: dispatchTouchEvent MainActivity event 0
2020-04-13 16:31:01.434 28958-28958/com.example.k_demo I/touch: dispatchTouchEvent TouchViewGroup event 0
2020-04-13 16:31:01.434 28958-28958/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 0
2020-04-13 16:31:01.518 28958-28958/com.example.k_demo I/touch: dispatchTouchEvent MainActivity event 1
案例二,我想在TouchView处理自己事件,并不交给上层TouchViewGroup,怎么做?
处理方式有很多,实际上dispatchTouchEvent不经过不可能,我们能控制的无非是TouchViewGroup不会走它的onTouchEvent方法.
class TouchViewGroup(
context: Context, attrs: AttributeSet?
) : FrameLayout(context, attrs) {
override fun onTouchEvent(event: MotionEvent?): Boolean {
Log.i("touch", "onTouchEvent TouchViewGroup event " + event!!.actionMasked)
return super.onTouchEvent(event)
}
}
方法一
class TouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
Log.i("touch", "dispatchTouchEvent TouchView event " + event!!.actionMasked)
return true
}
}
输出
2020-04-13 16:38:52.530 29573-29573/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 0
2020-04-13 16:38:52.622 29573-29573/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 2
2020-04-13 16:38:53.112 29573-29573/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 2
2020-04-13 16:38:53.201 29573-29573/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 1
这样可以在自定义TouchView做自己的touch逻辑不会回调到TouchViewGroup中的onTouchEvent了.
方法二
class TouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
override fun onTouchEvent(event: MotionEvent?): Boolean {
Log.i("touch", "onTouchEvent TouchView event " + event!!.actionMasked)
return true
}
}
输出结果同上
案例三,如何在TouchViewGroup拦截掉Touch,不分发到子View中
class TouchViewGroup(
context: Context, attrs: AttributeSet?
) : FrameLayout(context, attrs) {
override fun onTouchEvent(event: MotionEvent?): Boolean {
Log.i("touch", "onTouchEvent TouchViewGroup event " + event!!.actionMasked)
return true
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return true
}
}
onInterceptTouchEvent拦截向下传递事件,onTouchEvent返回true,表示TouchViewGroup需要处理该Touch事件.
如上,就写到这里,一个总结。