Android开发之View事件分发机制

Android开发之View事件分发机制

View相关内容学习总结

目录

  1. View事件
    1.1. 位置坐标参数
    1.2. 触控事件
    1.3. Activity构成
    1.4. 事件分发机制

1、View事件

1.1、位置参数
a.Android坐标系: 屏幕左上角为原点,向右为 x轴正方向,向下为y轴正方向,通过
getRawX(),getRawY() 获取坐标

b.View坐标系:位置由它的四个顶点确定,是相对于父容器的坐标。

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,用于显示内容。

Activity构成

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( )方法。完成整个事件传递

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值