当触摸android设备的屏幕时,android系统将创建一个MotionEvent对象。
MotionEvent对象是描述了一组和用户触摸相关的事件序列,它包含了触摸的位置,时间,动作等信息。
发生触摸时,这个MotionEvent对象会被传递到合适的方法中作为方法参数(通常是回调方法)。
getAction()方法描述了当前的动作:
像OnTouchListener的onTouch()回调方法就接受一个系统产生的MotionEvent,这类回调方法返回值为boolean,如果返回false,那么实现OnTouchListener的View会告诉android:我对此触摸的事件序列以及以后会发生的触摸事件序列都不感兴趣,请你寻找下一个感兴趣的View。android系统下次收到触摸事件不会将MotionEvent的实例发送给这个View,而是寻找下一个接受MotionEvent的View。前后2个MotionEvent是同一个对象,而不是新产生的(下面的例子会验证这个说法)
下面是Demo:
布局文件: activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<RelativeLayout
android:id="@+id/truelayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:layout_weight="1"
android:tag="truelayout"
>
<com.example.mytouchdemo.TrueButton
android:id="@+id/truebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:tag="truebutton"
android:text="true"
/>
<com.example.mytouchdemo.FalseButton
android:id="@+id/falsebutton"
android:layout_below="@+id/truebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:tag="falsebutton"
android:text="false"
/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/falselayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:tag="falselayout"
android:background="@android:color/darker_gray"
>
<com.example.mytouchdemo.TrueButton
android:id="@+id/falsebutton2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:tag="falsebutton2"
android:text="false"
/>
</RelativeLayout>
</LinearLayout>
TrueButton和FalseButton都是自定义控件,继承了BooleanButton,BooleanButton继承了Button
BooleanButton的onTouchEvent回调在接收了MotionEvent对象,返回值取决于是trueButton.还是falseButton..
public abstract class BooleanButton extends Button {
protected boolean myValue() {
return false;
}
public BooleanButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(tag, "------"+ getTag() +"--------------");
Log.v(tag, "action:" + event.getAction());
Log.v(tag, "and I'm returning " + myValue());
Log.v(tag, "MotionEvent's hashcode="+event.hashCode()+"");
return(myValue());
}
private String tag = "test";
}
public class TrueButton extends BooleanButton {
protected boolean myValue() {
return true;
}
public TrueButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
public class FalseButton extends BooleanButton {
public FalseButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
MainActivity.java
public class MainActivity extends Activity implements OnTouchListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//上半部分布局,ontouch方法返回true
RelativeLayout trueLayout = (RelativeLayout)findViewById(R.id.truelayout);
//true按钮,ontouch方法和自身的onTouchEvent方法返回true
Button trueButton = (Button)findViewById(R.id.truebutton);
//false按钮,ontouch方法和自身的onTouchEvent方法返回false
Button falseButton = (Button)findViewById(R.id.falsebutton);
Button falseButton2 = (Button)findViewById(R.id.falsebutton2);
//下半部分布局,ontouch方法返回false
RelativeLayout falseLayout = (RelativeLayout)findViewById(R.id.falselayout);
//注册ontouch事件
trueLayout.setOnTouchListener(this);
falseLayout.setOnTouchListener(this);
trueButton.setOnTouchListener(this);
falseButton.setOnTouchListener(this);
falseButton2.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
String tagStr = v.getTag().toString().substring(0, 4);
//tag前4位数为true的控件,onTouch返回true,否则返回false
if ("true".equals(tagStr)) {
Log.v(tag, "------"+ v.getTag() +"--------------");
Log.v(tag, "action:" + event.getAction());
Log.v(tag, "and I'm returning " + tagStr);
Log.v(tag, "MotionEvent's hashcode="+event.hashCode()+"");
return true;
} else {
return false;
}
}
String tag = "test";
}
现在启动程序,点击上半布局的TrueButton,检查logcat输出:
06-17 22:26:31.662: V/test(5766): ------truebutton--------------
06-17 22:26:31.662: V/test(5766): action:0
06-17 22:26:31.662: V/test(5766): and I'm returning true
06-17 22:26:31.662: V/test(5766): MotionEvent's hashcode=1097894832
06-17 22:26:31.682: V/test(5766): ------truebutton--------------
06-17 22:26:31.682: V/test(5766): action:2
06-17 22:26:31.682: V/test(5766): and I'm returning true
06-17 22:26:31.682: V/test(5766): MotionEvent's hashcode=1097894832
06-17 22:26:31.702: V/test(5766): ------truebutton--------------
06-17 22:26:31.702: V/test(5766): action:2
06-17 22:26:31.702: V/test(5766): and I'm returning true
06-17 22:26:31.702: V/test(5766): MotionEvent's hashcode=1097894832
06-17 22:26:31.712: V/test(5766): ------truebutton--------------
06-17 22:26:31.712: V/test(5766): action:1
06-17 22:26:31.712: V/test(5766): and I'm returning true
06-17 22:26:31.712: V/test(5766): MotionEvent's hashcode=1097894832
可以看到所有的事件都在truebutton中被接收了,包括按下action:0,移动action:2,弹起action:1。这是因为onTouch方法中返回了true。
现在点击上半布局的FalseButton,检查logcat输出:
06-17 22:29:06.892: V/test(5766): ------falsebutton--------------
06-17 22:29:06.892: V/test(5766): action:0
06-17 22:29:06.892: V/test(5766): and I'm returning false
06-17 22:29:06.892: V/test(5766): MotionEvent's hashcode=1097894832
06-17 22:29:06.892: V/test(5766): ------truelayout--------------
06-17 22:29:06.892: V/test(5766): action:0
06-17 22:29:06.902: V/test(5766): and I'm returning true
06-17 22:29:06.902: V/test(5766): MotionEvent's hashcode=1097894832
06-17 22:29:06.912: V/test(5766): ------truelayout--------------
06-17 22:29:06.912: V/test(5766): action:2
06-17 22:29:06.912: V/test(5766): and I'm returning true
06-17 22:29:06.912: V/test(5766): MotionEvent's hashcode=1097894832
06-17 22:29:06.932: V/test(5766): ------truelayout--------------
06-17 22:29:06.932: V/test(5766): action:2
06-17 22:29:06.932: V/test(5766): and I'm returning true
06-17 22:29:06.932: V/test(5766): MotionEvent's hashcode=1097894832
06-17 22:29:06.952: V/test(5766): ------truelayout--------------
06-17 22:29:06.952: V/test(5766): action:2
06-17 22:29:06.952: V/test(5766): and I'm returning true
06-17 22:29:06.952: V/test(5766): MotionEvent's hashcode=1097894832
06-17 22:29:06.972: V/test(5766): ------truelayout--------------
06-17 22:29:06.972: V/test(5766): action:1
06-17 22:29:06.972: V/test(5766): and I'm returning true
06-17 22:29:06.972: V/test(5766): MotionEvent's hashcode=1097894832
看到falseButton和包含它的布局truelayout都接收了按下事件,但是之后的移动,弹起都只有truelayout接收。因为falseButton在onTouch中返回false,这表明它不再接收之后的触摸时间,交由其他View处理,所以android系统找到了truelayout,因为truelayout的onTouch返回true,所以它处理所有的触摸事件序列。并且注意到,2次接受的motionEvent的hashcode都是一样的,说明他们是一个motionevent
下面点击下半个布局的falsebutton,logcat输出:
06-17 22:36:57.942: V/test(7164): ------falsebutton2--------------
06-17 22:36:57.942: V/test(7164): action:0
06-17 22:36:57.942: V/test(7164): and I'm returning false
06-17 22:36:57.942: V/test(7164): MotionEvent's hashcode=1098095696
可以看到非常悲剧,因为包含它的布局falselayout返回的也是false,现在没有view可以接收motionevent对象,所以不能监听到移动和弹起的事件
下面看一个特殊的情况:按住上半部分的trueButton并且移动手指一直到移出button外,移到relativelayout上。看log:
06-17 22:39:41.442: V/test(7164): ------truebutton--------------
06-17 22:39:41.442: V/test(7164): action:0
06-17 22:39:41.442: V/test(7164): and I'm returning true
06-17 22:39:41.442: V/test(7164): MotionEvent's hashcode=1098095696
06-17 22:39:41.462: V/test(7164): ------truebutton--------------
06-17 22:39:41.462: V/test(7164): action:2
06-17 22:39:41.462: V/test(7164): and I'm returning true
06-17 22:39:41.462: V/test(7164): MotionEvent's hashcode=1098095696
………………………………………………
你会发现就算出了button的范围它依然监听触摸事件,这是因为它返回true,代表这一系列触摸动作都由我来处理,这也是“触摸序列”的含义
补充:注意如果MainActivity中onTouch方法返回true,则layout或者Button本身的onTouchEvent都不会被调用,因为触摸事件已经被onTouch处理。