上篇分析
贴上代码
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是否为空