浅析Android View事件分发机制(应用侧)

基础概念

  1. 触摸事件:手指触摸屏幕时生成的事件,即MotionEvent。常见的触摸事件有:ACTION_DOWNACTION_MOVEACTION_UP以及ACTION_CANCEL,当存在多个手指触摸屏幕时,还会触发ACTION_POINTER_DOWNACTION_POINTER_UP事件。
  2. 焦点事件:ACTION_DOWNACTION_POINTER_DOWN属于焦点事件,通过MotionEvent中的PointerId进行描述;
  3. 触摸事件序列:从手指触摸屏幕开始到手指离开屏幕结束的过程中触发的一系列事件,通常以ACTION_DOWN事件开始、ACTION_UP事件结束,中间有不定数量的ACTION_MOVEACTION_POINTER_DOWN或者ACTION_POINTER_UP事件的一系列事件。
  4. 滑动冲突:View树中相邻的层级上均存在可滑动的View,当用户触摸屏幕时触发了ACTION_MOVE事件导致有多个View可以处理的情况。
  5. 事件分发机制:触摸屏幕产生的事件MotionEvent在整个View树上分发处理的逻辑,理解事件分发机制的实现原理才能知道如何解决View滑动冲突问题。

源码分析

事件分发流程

当用户开始触摸手机屏幕时,经过传感器等一系列硬件处理,最终生成触摸事件并由SystemServer进程的InputMangerService服务通过Socket发送到目标应用进程,经过多个的InputStage分发之后触摸事件被传递到DecorView#dispatchPointerEvent方法,dispatchPointerEvent方法内部调用了dispatchTouchEvent方法,通过PhoneWindow#getCallback方法拿到目标Activity,最终调用Activity#dispatchTouchEvent方法进行处理。

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
   
   
    public final boolean dispatchPointerEvent(MotionEvent event) {
   
   
        if (event.isTouchEvent()) {
   
   
            return dispatchTouchEvent(event);
        } else {
   
   
            return dispatchGenericMotionEvent(event);
        }
    }
}

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
   
   
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
   
   
   		// mWindow是PhoneWindow,cb是Activity
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }
}
    
public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback,
        ContentCaptureManager.ContentCaptureClient {
   
   
	/**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     *
     * @see #onTouchEvent(MotionEvent)
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
   
   
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
   
   
            onUserInteraction();
        }
        // 通过Window进行分发最终交由根布局DecorView进行处理,如果处理成功则将事件从队列中移除,否则交由onTouchEvent继续处理;
        if (getWindow().superDispatchTouchEvent(ev)) {
   
    
            return true;
        }
        // 如果整个View树都没有处理触摸事件,则由Activity的onTouchEvent处理
        return onTouchEvent(ev); 
    }

	/**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
   
   
        return mWindow;
    }

	final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
   
   
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        mActivityInfo = info;

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        
		// 省略其他代码
	}
}

接着,触摸事件会被分发给目标ActivityPhoneWindow(在Activity实例创建之后调用attach方法中进行创建)进行处理。经过PhoneWindow#superDispatchTouchEvent方法将触摸事件交由DecorView.superDispatchTouchEvent。因为DecorView继承自FrameLayout,而FrameLayout继承自ViewGroup,所以触摸事件最终由ViewGroup.dispatchTouchEvent开始分发处理。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
   
   
	@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
   
   
        return mDecor.superDispatchTouchEvent(event);
    }

	private void installDecor() {
   
   
        mForceDecorInstall = false;
        if (mDecor == null) {
   
   
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
   
   
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
   
   
            mDecor.setWindow(this);
        }
        // 省略其他代码
    }
 }
 
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
   
   
	public boolean superDispatchTouchEvent(MotionEvent event) {
   
   
        return super.dispatchTouchEvent(event);
    }
}

至此,触摸事件已经被分发到View树的根节点,开始在View树上进行遍历(深度遍历)和分发处理,总结流程图如下:
在这里插入图片描述

为了侧重分析触摸事件的分发流程,下面主要围绕关键代码进行分析,和事件分发流程关联不大的部分直接略过;

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
   
   

	// First touch target in the linked list of touch targets.
	// 记录当前父View下第一个处理触摸事件的View对象,内部通过链表的形式进行维护
    @UnsupportedAppUsage
    private TouchTarget mFirstTouchTarget;
    
	@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
   
   
        boolean handled = false;
        // 过滤不安全的触摸事件
        if (onFilterTouchEventForSecurity(ev)) {
   
   
            final int action = ev.getAction(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值