Android开发之View事件分发机制
View相关内容学习总结
目录
- View事件
1.1. 位置坐标参数
1.2. 触控事件
1.3. Activity构成
1.4. 事件分发机制
1、View事件
1.1、位置参数
a.Android坐标系: 屏幕左上角为原点,向右为 x轴正方向,向下为y轴正方向,通过
getRawX(),getRawY() 获取坐标
b.View坐标系:位置由它的四个顶点确定,是相对于父容器的坐标。
1.2、触摸事件
当我们点击屏幕或滑动时,将会产生触摸事件MotionEvent,一般触摸事件是以ACTION_DOWN开始,ACTION_UP结束,中间可包含若干个ACTION_MOVE事件。
1.3、Activity构成
因为触摸事件最先传递给Activity,先来看看Activity构成。
a、在Activity的onCreate()方法中会调用setContentView(),
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
其内部调用的是getWindow().setContentView(layoutResID)
public Window getWindow() {
return mWindow;
}
getWindow返回mWindow是一个 PhoneWindow对象。PhoneWindow继承抽象类Window。
b、接着看PhoneWindow.setContentView()
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor(); //1
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
注释1:installDecor()
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1); //generateDecor()返回DecorView对象
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
...
}
}
上述代码中generateDecor()返回DecorView,DecorView为PhoneWindow的内部类,是Activity的根View,继承FrameLayout。DecorView包含一个TitleView,来显示标题;一个ContentView,用于显示内容。
1.4、事件分发机制
a. 分发流程: Activity – ViewGroup – View
b. 主要方法:
- dispatchTouchEvent( ),触摸事件入口
- onInterceptTouchEvent( ),用来进行事件拦截,只有ViewGroup提供该方法
- onTouchEvent( ),处理点击事件,在dispatchTouchEvent( )中调用
b. 源码分析:
Activity:
点击事件最先到达Activity的dispatchTouchEvent ()
public boolean dispatchTouchEvent(MotionEvent ev) {
// 一般事件列开始都是DOWN事件 = 按下事件,故此处基本是true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
// 实现屏保功能,空方法
}
// >>分析1
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
// 若getWindow().superDispatchTouchEvent(ev)的返回true
// 则Activity.dispatchTouchEvent()就返回true,则方法结束。事件传递过程结束
// 否则:继续往下调用Activity.onTouchEvent ()
}
// ->>分析2
return onTouchEvent(ev);
}
-
分析1:getWindow().superDispatchTouchEvent(ev)
说明:
a. getWindow() 获取Window类的对象b. 由于Window类是抽象类,其唯一实现类 PhoneWindow类;getWindow() 返回 PhoneWindow类对象
c. Window类的superDispatchTouchEvent() 由子类PhoneWindow类实现
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
// mDecor = 顶层View(DecorView)的实例对象
}
/*
DecorView是PhoineView的内部类
DecorView继承自FrameLayout
mDecor.superDispatchTouchView(event)
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
// super.dispatchTouchEvent(event) = ViewGroup的dispatchTouchEvent()
// 即 将事件传递到ViewGroup去处理,实现将事件传递到ViewGroup()
}
- 分析2:Activity.onTouchEvent()
触发条件:事件未能被Activity下的子View接收消费,则调用Activity的onTouchEvent()
应用场景:处理发生在Window边界外的触摸事件
//Activity的onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
// ->> 分析3
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
// 即 只有在点击事件在Window边界外才会返回true,一般情况都返回false
}
- 分析3:mWindow.shouldCloseOnTouch(this,event)
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
// 主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
// 返回true:说明事件在边界外,即 消费事件
// 返回false:未消费(默认)
}
ViewGroup
ViewGroup的事件分发也是从dispatchTouchEvent()开始
//ViewGroup dispatchTouchEvent()部分代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();//将mFirstTouchTarget置为null,初始化
}
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {//若mFirstTouchTarget != null,说明事件交由子View处理,不进行拦截
//FLAG_DISALLOW_INTERCEPT标志位用于禁止ViewGroup拦截事件,子View通过requestDisallowIntercrptTouchEvent来设置。
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev); //调用ViewGroup的onInterceptTouchEvent( ),默认返回false,不拦截
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
//当ViewGroup拦截了事件后,后续事件序列均交由ViewGroup处理
intercepted = true;
}
...
return handled;
}
ViewGroup的dispatchTouchEvent(( )其余代码
...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) { // 1
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) { // 2
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 3
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
注释1:倒序遍历子view,即从最外层开始遍历
注释2:判断事件是否在子View的范围内或者子View是否在播放动画,若君满足,执行continue。继续遍历下一个View
注释3:dispatchTransformedTouchEvent( )用于判断遍历结果是否存在接收事件的子View,若存在,则调用子View的dispatchTouchEvent( ),不存在就调用super.dispatchTouchEvent( ),因为ViewGroup是继承View的,这样就实现了触摸事件从ViewGroup向View的传递
View
接下来,我们看看View的dispatchTouchEvent( )
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//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;
}
}
...
}
当OnTouchListener != null 并且 onTouch( )返回true时,表示事件被消费,就不会执行onTouchEvent( ),由此看出,onTouch执行优先级高于onTouchEvent( )
//onTouchEvent 部分源码
public boolean onTouchEvent(MotionEvent event) {
...
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
...
return true;
}
return false;
}
View设置了点击监听,则在onTouchEvent( )中Action_up事件触发时会执行performClick( ),performClick( )方法中回调onClick( )方法。完成整个事件传递