Android触摸事件派发机制源码分析之Activity

    xiaoxiao2021-03-25  207

    上篇分析

    ViewGroup分析

    贴上代码 MainActivity中的代码如下

    package com.sparkhuu.testevent; import android.nfc.Tag; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.RelativeLayout; import junit.framework.Test; import java.io.Serializable; public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnClickListener { public static final String TAG = "test"; RelativeLayout rl_layout; TestButton btn_view; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); rl_layout = (RelativeLayout) this.findViewById(R.id.rl_layout); btn_view = (TestButton) this.findViewById(R.id.btn_view); btn_view.setOnTouchListener(this); rl_layout.setOnTouchListener(this); btn_view.setOnClickListener(this); rl_layout.setOnClickListener(this); } @Override public boolean onTouch(View view, MotionEvent motionEvent) { Log.i(TAG, "OnTouchListener----onTouch----action" + motionEvent.getAction() + "-----" + view); return false; } @Override public void onClick(View view) { Log.i(TAG, "OnClickListener---onClick---" + view); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i(TAG, "MainActivity ----- dispatchTouchEvent --- action" + ev.getAction()); return super.dispatchTouchEvent(ev); } @Override public void onUserInteraction() { Log.i(TAG, "MainActivity --- onUserInteraction "); super.onUserInteraction(); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(TAG, "MainActivity --- onTouchEvent --- action" + event.getAction()); return super.onTouchEvent(event); } }

    自定义Button和Relativelayout的代码如下

    package com.sparkhuu.testevent; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.Button; /** * author:sparkhuu * email:sparkhuu@gmail.com */ public class TestButton extends Button { public TestButton(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(MainActivity.TAG, "TestButton ----dispatchTouchEvent -- action" + event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(MainActivity.TAG, "TestButton ----onTouchEvent -- action" + event.getAction()); return super.onTouchEvent(event); } } package com.sparkhuu.testevent; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.RelativeLayout; /** * author:sparkhuu * email:sparkhuu@gmail.com */ public class TestRelatvieLayout extends RelativeLayout { public TestRelatvieLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.i(MainActivity.TAG, "TestRelatvieLayout -- onInterceptTouchEvent----action" + ev.getAction()); return super.onInterceptTouchEvent(ev); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i(MainActivity.TAG, "TestRelatvieLayout -- dispatchTouchEvent----action" + ev.getAction()); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(MainActivity.TAG, "TestRelatvieLayout -- onTouchEvent----action" + event.getAction()); return super.onTouchEvent(event); } }

    运行截图如下 可以发现,除了Activity中新添加的几个方法以外,其他的方法和之前分析的View和ViewGroup完全一致,对于Activity来说,Action_Down首先会触发dispatchTouchEvent,然后出发onUserInteraction,再次onTouchEvent,接着Action_Up事件触发dispatchTouchEvent后直接触发onTouchEvent也就说比down事件少触发了onInterceptaction事件。 点击Button外其他区域,日志如下 显而易见,和上面Button的结果类似 照样,我们从Activity的dispatchTouchEvent分析,源码走起

    /** * 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. */ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }

    源码虽少,五脏俱全啊,显而易见,当触发事件是Action_down时,会调用onUserInteraction这就很好的解释了上面的困惑。下面分析下if (getWindow().superDispatchTouchEvent(ev)) { return true; }这个if判断,通过activity的attach方法可以发现这里的getWindow返回的就是PhoneWindow对象(PhoneWindow是Window的实现类),也就相当于调用PhoneWindow的superDispatchTouchEvent方法,我们先看下他的抽象类Window中的抽象方法如下

    /** * Used by custom windows, such as Dialog, to pass the touch screen event * further down the view hierarchy. Application developers should * not need to implement or call this. * */ public abstract boolean superDispatchTouchEvent(MotionEvent event);

    从注释可以看出,用户不需要实现的方法,实际上也不行,在Activity中没有提供重写的机会,因为Window是以组合模式和Activity建立关系的,我们在看下PhoneWindow中的实现

    public boolean superDispatchTouchEvent(MotionEvent event){ return mDecor.superDispatchTouchEvent(event); };

    我们可以发现PhoneWindow的superDispatchTouchEvent方法内又调用了 mDecor.superDispatchTouchEvent,那么mDecor是什么呢?我们在PhoneWindow中可以发现mDecor是DecorView,DecorView本身是一个PhoneWindow的内部类,同时继承了FrameLayout实现了RootViewSurfaceTaker,他是一个真正的Activity的rootview,怎么验证它是不是rootview呢?我们可以通过Hierarchy Viewer来查看如下 可以看出,我们上面例子中setContentView,在xml中放入一个Relativelayout,这个Relativelayout中包含一个Button,上面显示这个RelatviLayout放在一个id为content的Framelayout中,我们还记得上面PhoneWindow的superDispatchTouchEvent返回了DecorView的superDispatchTouchEvent,而DecorView又是Framelayout的子类,Framelayout又是ViewGroup的子类,我们接下来看下DecorView的superDispatchTouchEvent方法

    public boolean superDispatchTouchEvent(MotionEvent event){ return super.dispatchTouchEvent(event); };

    搞了半天Activity中的dispatchTouchEvent的 if (getWindow().superDispatchTouchEvent(ev)) 本质上是ViewGroup的dispatchTouchEvent方法(这个ViewGroup是activity持有的root view 也就是id为content的FrameLayout) 总结: 1,首先会触发Activity的dispatchTouchEvent 2,dispatchTouchEvent如果是action_down会调用onUserInteraction 3,接着dispatchTouchEvent方法会通过Activity的root view(ID为content的framelayout)实质上是viewgroup,通过 super.dispatchTouchEvent(event)把touchevent派发给我们通过setcontentview设置的view 4,若activity下面的view拦截了touchevent(返回true),则Activity.onTouchEvent 不会执行 下面我们来看下onUserInteraction,代码如下

    /** * Called whenever a key, touch, or trackball event is dispatched to the * activity. Implement this method if you wish to know that the user has * interacted with the device in some way while your activity is running. * This callback and {@link #onUserLeaveHint} are intended to help * activities manage status bar notifications intelligently; specifically, * for helping activities determine the proper time to cancel a notfication. * * <p>All calls to your activity's {@link #onUserLeaveHint} callback will * be accompanied by calls to {@link #onUserInteraction}. This * ensures that your activity will be told of relevant user activity such * as pulling down the notification pane and touching an item there. * * <p>Note that this callback will be invoked for the touch down action * that begins a touch gesture, but may not be invoked for the touch-moved * and touch-up actions that follow. * * @see #onUserLeaveHint() */ public void onUserInteraction() { }

    这个方法是activity的一个方法,这个方法是空方法,那么在什么时候会调用呢?触屏点击home,menu,back键都会触发此方法,下拉statubar,旋转屏幕,锁屏不会触发,所以他会用在屏保应用上,因为当你触摸机器,马上就会触发一个事件,而这个事件又不确定是什么,正好屏保可以满足此需求,或者对于一个activity,控制多长时间没有用户相应的时候,自己消失 下面再来看下onTouchEvent

    /** * Called when a touch screen event was not handled by any of the views * under it. This is most useful to process touch events that happen * outside of your window bounds, where there is no view to receive it. * * @param event The touch screen event being processed. * * @return Return true if you have consumed the event, false if you haven't. * The default implementation always returns false. */ public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }

    如果一个锁屏事件没有被这个activity下任何view处理,activity的ontouchevent将会调用,这对于处理window边界之外的touch事件非常有用,因为通常是因为没有view会接受到他们,返回true表示已经消费事件,反之,则没有消费。从中看出重点就这句代码 if (mWindow.shouldCloseOnTouch(this, event))整理的mwindow就是上面的dispatchtouchevent中的getwindow对象,所以直接到window抽象类和phonewindow子类查看,发现PhoneWindow没有重写Window的shouldCloseOnTouch方法,看下Window中的shouldCloseOnTouch方法代码如下

    /** @hide */ public boolean shouldCloseOnTouch(Context context, MotionEvent event) { if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) && peekDecorView() != null) { return true; } return false; }

    其实就是一个判断,判断mCloseOnTouchOutside标记是否为action_down事件,同时判断x,y坐标是不是超过Bounds,然后检查framelayout的id为content的DecorView是否为空

    源码下载

    转载请注明原文地址: https://ju.6miu.com/read-404.html

    最新回复(0)