其实 Android 事件分发机制在早几年一直都困扰着我,那时候处理事件分发的自定义 View 脑子都是一片白,老感受处理很差。后来本身看了 android 源码,也阅读了不少大牛的文章才算完全明白,总之掌握 Android 事件分发机制是必不可少的,而 Android 事件分发机制绝对不是三言两语就能说得清的。android
而今天因为咱们自定义 View 进阶的须要,本身也是筹备了好久。目前虽然网上相关的文章也很多,不少也写得很是详细,可是多数文章只是讲了讲理论,而后配合 Log 打印一下结果而已。而我准备不只带着你们从源码的角度进行分析,还须要理论结合实践写几个关于这方面的效果,这样相信咱们会有更深的理解。阅读源码讲究由浅入深,按部就班,咱们就不像其余文章同样搞混合了,先讲 View 的 Touch 事件分发,而后再讲 ViewGroup 的事件分发,最后再写个几回效果。咱们一向的套路都是理论结合实践,由浅入深面试
先来看几个效果,如前几回咱们写自定义评分控件的 RatingBar 复写了 onTouchEvent(),这里只是举个例子:bash
public class RatingBar extends View {
public RatingBar(Context context) {
super(context);
}
public RatingBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public RatingBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("TAG", "onTouchEvent execute -> " + event.getAction());
return super.onTouchEvent(event);
}
}
复制代码
若是想要给这个控件注册一个点击事件,只须要调用:ide
mRatingBar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG","onClick execute");
}
});
复制代码
若是想给这个按钮再添加一个touch事件,只须要调用:ui
mRatingBar.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute -> " + event.getAction());
return false;
}
});
复制代码
上面的代码若是运行起来,哪个会先执行呢? 若是是靠猜,那么咱们来试一下就知道了,运行程序点击,打印结果以下:this
能够看到,onTouch 是优先于优先于 onTouchEvent 优先于 onClick 执行的,而且 onTouch 和 onTouchEvent 执行了两次,一次是 ACTION_DOWN ,一次是 ACTION_UP (你还可能会有屡次 ACTION_MOVE 的执行,若是你在上面触摸)。所以事件传递的顺序是先通过 onTouch ,而后通过 onTouchEvent ,再传递到 onClick 。spa
若是留心观察你会发现 setOnTouchListener 是有返回值的,若是返回 ture ,再次运行一下会怎样?code
咱们发现,onTouchEvent 和 onClick 方法再也不执行了!为何会这样呢?你能够先理解成 onTouch 方法返回 true 就认为这个事件被 onTouch 消费掉了,于是不会再继续 onTouchEvent 和 onClick 。到目前位置若是你清楚了,那么面试的时候基本靠背,那么本身写效果的时候基本靠蒙。咱们确定不能局限于这个装态,接下了咱们就带着疑问从源码的角度分析一下,为何会出现上述状况?orm
首先咱们须要知道,你点击或者或者触摸任何一个 View 都会调用 View 的 dispatchTouchEvent() 方法,咱们就从这里开始分析源码:视频
public boolean dispatchTouchEvent(MotionEvent event) {
// 省略部分代码 ...
boolean result = false;
// 省略部分代码 ...
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
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;
}
}
// 返回 result
return result;
}
复制代码
省略掉部分代码以后,这个方法就变得很是的简洁了,只有短短几行代码!咱们能够看到,在这个方法内,首先是进行了一个判断,若是li != null,mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED 和 mOnTouchListener.onTouch(this, event) 这三个条件都为真,result 就是 true,不然就去执行 onTouchEvent(event) 方法并返回。 那么 ListenerInfo 究竟是什么?咱们能够看下源码,这其实就是有关 View 全部事件的一个集合类,如 OnFocusChangeListener , OnScrollChangeListener , OnClickListener 、、、
static class ListenerInfo {
/**
* Listener used to dispatch focus change events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnFocusChangeListener mOnFocusChangeListener;
/**
* Listeners for layout change events.
*/
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
/**
* Listeners for attach events.
*/
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnLongClickListener mOnLongClickListener;
/**
* Listener used to dispatch context click events. This field should be made private, so it
* is hidden from the SDK.
* {@hide}
*/
protected OnContextClickListener mOnContextClickListener;
/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnCreateContextMenuListener mOnCreateContextMenuListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
private OnHoverListener mOnHoverListener;
private OnGenericMotionListener mOnGenericMotionListener;
private OnDragListener mOnDragListener;
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}
复制代码
先看一下条件 li.mOnTouchListener 这个变量是在哪里赋值的呢?咱们寻找以后在View里发现了以下方法:
/**
* Register a callback to be invoked when a touch event is sent to this view.
* @param l the touch listener to attach to this view
*/
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
复制代码
第二个条件 mOnTouchListener 正是在 setOnTouchListener 方法里赋值的,也就是说只要咱们给控件注册了 touch 事件,mListenerInfo 和 mListenerInfo.mOnTouchListener 就必定被赋值了。
第三个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是不是enable的,默认都是enable的,所以这个条件恒定为 true 。
第四个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册 touch 事件时的 onTouch 方法。也就是说若是咱们在 onTouch 方法里返回true,就会让这三个条件所有成立,从而 result 是 true , 那么 onTouchEvent 就不会被执行 。若是咱们在 onTouch 方法里返回 false,就会去执行 onTouchEvent() 方法。
如今咱们能够结合前面的例子来分析一下了,首先在 dispatchTouchEvent 中最早执行的就是 onTouch 方法,所以 onTouch 确定是要优先于 onTouchEvent 方法,也是印证了刚刚的打印结果。而若是在 onTouch 方法里返回了 true,不会再执行 onTouchEvent 。可是到目前位置咱们尚未看到 onClick 执行,可是咱们能够猜到,onClick的调用确定是在onTouchEvent(event)方法中的!那咱们立刻来看下onTouchEvent的源码,以下所示:
public boolean onTouchEvent(MotionEvent event) {
// 省略部分代码
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
performClick();
}
mIgnoreNextUpEvent = false;
break;
// 省略部分代码
}
return true;
}
return false;
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
复制代码
相较于刚才的 dispatchTouchEvent 方法,onTouchEvent 方法复杂了不少,不过不要紧,咱们只挑重点看就能够了。switch 中若是当前的事件是抬起手指,则会进入到 MotionEvent.ACTION_UP 这个 case 当中。在通过种种判断以后,会执行到 performClick() 方法,能够看到,只要 mListenerInfo.mOnClickListener 不是 null,就会去调用它的 onClick 方法,那 mListenerInfo.mOnClickListener 又是在哪里赋值的呢?咱们大概能猜到确定在 setOnclickLstener 方法中:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
复制代码
View 的 Touch 事件分发咱们就讲到这里了,下一节我将带着你们一块儿了解 ViewGroup 的事件分发和事件拦截,在源码的基础上写几个效果,我想应该能够说就堪称完美了。
全部分享大纲:Android进阶之旅 - 自定义View篇
视频讲解地址:pan.baidu.com/s/1hr6ql72