目录:
一、 setContentView
Activity中setContentView的源码,主要作用就是:
生成DecorView,并把通过Activity的theme和Java代码里面设置的Feature匹配得到的layoutResource与DecorView绑定,然后再把我们传进去的layoutResId
添加到DecorView上,最后再添加一个onContentChange()
的回调
二、 View的事件分发机制
用户的点击事件会被系统封装成一个类:MotionEvent,当MotionEvent产生后,就会在各层View之间传递,这个传递过程就是点击事件分发
其中,事件分发其主要作用的是三个方法:
- dispatchTouchEvent() —— 进行事件分发
- onInterceptTouchEvent() —— 进行事件拦截,在dispatchTouchEvent()中调用,但View没有提供这个方法(因为View一般就是最后一层,此时不必再对事件进行拦截,事件不会再继续传递下去)
- onTouchEvent() —— 用来处理点击事件,在dispatchTouchEvent()中进行调用
1. View的事件分发机制
产生点击事件后,事件会从Activity ==> PhoneWindow ==> DecorView => ViewGroup ==> (...ViewGroup...) ==> View
事件就这样一层一层的从上往下传递,我们直接从ViewGroup的dispatchTouchEvent()
开始看。
ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//...
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
//初始化之前的状态
resetTouchState();
}
// Check for interception.
final boolean intercepted;
//这里使用了mFirstTouchTarget这个属性
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
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;
}
//...
return handled;
}
在dispatchTouchEvent()方法里,会先事件进行判断,看看是否为DOWN
,如果是,则调用resetTouchState()方法。因为ACTION_DOWN
事件是新事件的开始,所以需要调用resetTouchState()方法初始化之前的状态
private void resetTouchState() {
clearTouchTargets();
//...
}
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
这里会对mFirstTouchTarget进行赋null值的操作。而mFirstTouchTarget是用来标记当前ViewGroup是否拦截了事件,如果拦截:mFirstTouchTarget=null
,如果不拦截并交给子View来处理:mFirstTouchTarget!=null
而mFirstTouchTarget在dispatchTouchEvent()方法中继续用来作为判断,==假设此时事件被拦截了==,那么mFirstTouchTarget != null
为false,如果当前点击事件为ACTION_DOWN
,那么就会就会调用onInterceptTouchEvent()
方法,如果当前事件是ACTION_UP
或ACTION_MOVE
,那么就会直接执行intercepted = true;
,此后的事件都将交由这个ViewGroup处理
//当事件被拦截时,即 mFirstTouchTarget != null 为false,此时只有ACTION_DOWN事件,才会去调用onInterceptTouchEvent()方法
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT:留给子View去禁止ViewGroup拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent()(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
//事件被拦截时,ACTION_UP和ACTION_MOVE都将会进到这里
intercepted = true;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
//默认返回false
return false;
}
除了ViewGroup自己去拦截ACTION_UP
和ACTION_MOVE
事件外,子View还可以通过FLAG_DISALLOW_INTERCEPT
标志位来禁止ViewGroup拦截ACTION_UP
和ACTION_MOVE
事件,途径就是通过子View调用requestDisallowInterceptTouchEvent()
这个方法来改变标记位
onInterceptTouchEvent()
方法默认返回false,不拦截事件,如果想要拦截,可以自定义ViewGroup重写这个方法
我们继续回到ViewGroup的dispatchTouchEvent()
上面来,看看事件是如何被分发给子View的
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//...
final View[] children = mChildren;
//遍历子元素(倒序遍历),从最上层的子View往内层遍历
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//判断子View是否能获取到点击事件
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
//子View能接收到点击事件点击事件,交由子View处理
continue;
}
childWithAccessibilityFocus = null;
//双重迭代
i = childrenCount - 1;
}
//判断触摸点位置是否在子View的范围内,或者子View是否在播放动画
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
//该子View不符合上面两个条件,开始遍历下一个
continue;
}
}
//...
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent()方法里面会对View的事件分发进行处理
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//...
}
ev.setTargetAccessibilityFocus(false);
//...
return handled;
}
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);
//如果有子View,就去调用子View的dispatchTouchEvent()方法,如果没有子View,就去调用ViewGroup的父类——View的dispatchTouchEvent方法
if (child == null) {
//调用父类——View里面的dispatchTouchEvent()方法
handled = super.dispatchTouchEvent(event);
} else {
//调用子View里面的dispatchTouchEvent()方法
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//...
}
ViewGroup的dispatchTouchEvent()
在遍历完children之后,就开始对时间进行分发,dispatchTransformedTouchEvent()
里面就是对事件分发的处理,他会先去查看自己(ViewGroup)有没有子View,有就调用自己的子View去处理,没有就交给自己的父类——View处理,所以最终都是调用到了View去处理dispatchTouchEvent()
1)View的dispatchTouchEvent():
我们来看View.java里面的这个方法
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
//把所有的监听事件封装成了一个对象,这里面存放了View所有Listener信息,如onTouchListener
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED //是否是enable,如果View设置了android:enabled="false",就都不能执行了
&& li.mOnTouchListener.onTouch(this, event)) { //如果你mOnTouchListener返回的是false,那么result就为false
result = true;
}
//这里的onTouchEvent()能不能执行,完全取决于onTouch()方法的返回值,所以onTouch()方法的优先级大于onTouchEvent()
if (!result && onTouchEvent(event)) {//如果result = false,就会执行onTouchEvent,如果result = true,就不会执行就会执行onTouchEvent
result = true;
}
}
//返回
return result;
}
boolean isAccessibilityFocusedViewOrHost() {
return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
.getAccessibilityFocusedHost() == this);
}
static class ListenerInfo {
protected OnFocusChangeListener mOnFocusChangeListener;
private ArrayList mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
private CopyOnWriteArrayList mOnAttachStateChangeListeners;
public OnClickListener mOnClickListener;
}
这个方法里面,其实就是利用 ==短路与== 的特性:当前面的条件不符合时,就不再判断后面的条件了,所以就通过这种方式,让enable属性控制mOnTouchListener方法的执行,和让result变量控制onTouchEvent的执行
在onTouch()
方法之前,会先判断View的enable
属性,如果enable被设置了android:enabled="false"
,那么后面的onTouch()、onTouxhEvent()、onClick()都不会得到执行。
2)View的onTouchEvent():
我们看源码:
public boolean onTouchEvent(MotionEvent event) {
//只要View的CLICKABLE和LONG_CLICKABLE有一个为true,clickable就会为true,那么onTouchEvent就会返回true,消耗这个事件
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
break;
}
return true;
}
return false;
}
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
public boolean performClick() {
//如果view设置了click事件,那么onClick()方法就会被执行
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//调用点击事件
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
View的CLICKABLE
和LONG_CLICKABLE
可以通过setClickable()、setLongClickable()方法来设置,也可以通过View的setOnClickListener()、setOnLongClickListener()来设置,他们会自动把View设置为CLICKABLE
和LONG_CLICKABLE
。
这里其实就解释了,为什么我们OnTouchListener里面返回false的时候,因为View的onClickListener是在OnTouch.UP后面才调用的
3)覆写onTouchEvent()
当onTouchEvent返回true后,这个方法就没有去调用super.onTouchEvent()方法,View内部的onTouchEvent()方法就不能得到执行,就不能去调用performClick()方法,那么li.mOnClickListener.onClick(this);
就不能执行。所以onClickListener就不能得到执行
所以View内部的优先级:enable > onTouch > onTouchEvent > onClick
2. View事件分发的传递规则
上面的一连串过程,我们可以归纳为几行伪代码
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
//onInterceptTouchEvent默认返回false,如果我们自定义ViewGroup时,重写了这个方法,让他返回true,那么我们就能拦截点击事件
if(onInterceptTouchEvent(ev)){
//拦截点击事件后,开始在自己内部分发点击事件
result = onTouchEvent();
}else{
//不拦截点击事件,交由子View去处理,这个步骤重复下去,最终会调用最底层的View,View是没有子View的,所以最后就会调用View的dispatchTouchEvent()方法,一般情况下,最终会调用最底层View的onTouchEvent()方法
result = child.dispatchTouchEvent(ev);
}
return result;
}
事件从Activity ==> PhoneWindow => DecorView => ViewGroup ==> .... ==> View
如果最顶层的ViewGroup开始,一直不拦截事件,事件最终会传递到最底层的View上面去,由于底层View,没有子View了,所以会调用View的onTouch方法。这就是事件由上向下传递
如果底层的View不处理这个事件,就会让自己的onTouchEvent返回false,从这里开始,事件开始向上传递,如果中途的ViewGroup也不处理这个事件,最终就会传递到顶层的ViewGroup,由顶层的ViewGroup去处理这个事件。