对于dispatchTouchEvent一直都处于迷迷糊糊的理解状态,就知道他有分发事件的作用,但是真正每次用起来却又很茫然。今天小小的研究了一下,希望能给大家带来帮助。
下面我一点一点的贴出源码来分析。
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
/**是否拦截 || onInterceptTouchEvent是否返回fasle,注意这里是取反. *这里引用一下老郭blog中说的一句话"1.disallowIntercept是指是否禁用掉事件拦截的功能,默认是 *false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改.2.竟然就是 *对onInterceptTouchEvent方法的返回值取反!也就是说如果我们在onInterceptTouchEvent方法中 *返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在nInterceptTouchEvent *方法中返回true,就*会让第二个值为false,从而跳出了这个条件判断" */
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
//然后这里开始循环子view(注意这里是倒序)
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
//如果x,y坐标在子view中这进入if
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
//赋值给目标对象
mMotionTarget = child;
return true;
}
}
}
}
}
}
.......
return target.dispatchTouchEvent(ev);
}
好的,简单的注释了一下代码。接下来我们写一个小demo来实践一下。RelativeLayout中写2个button,btn1在下层,btn2在上层,分别点击看其点击是否有效。
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" >
<Button android:id="@+id/btn1" android:layout_width="300dp" android:layout_height="300dp" android:background="#a59a6a" android:text="btn1" />
<Button android:id="@+id/btn2" android:layout_width="100dp" android:layout_height="100dp" android:text="btn2" android:background="#aaaaaa" />
</RelativeLayout>
然后注册点击事件:
findViewById(R.id.btn1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "btn1~~~~~", 1).show();
Log.d("MainActivity", "btn1 on click~~~~~");
}
});
findViewById(R.id.btn2).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "btn2~~~~~", 1).show();
Log.d("MainActivity", "btn2 on click~~~~~");
}
});
当然大伙很容就说了,当然了点击btn1当然打出log,btn2当然打出log。这还用说吗。好的。用源码的角度来解释一下,点击btn1的时候,点击x、y的坐标点在btn1上,不在btn2上,所以btn2不响应。点击btn2的时候x,y的坐标在2个上面,那问题来了,为什么就btn2相应了而btn2没有相应了。
来看看源码,首先进行for循环,然后取mChildrenCount-1的view,也就是btn2后return结束。
相信大家用过bringToFront,说我用代码强制绘制到顶部。那这种分发机制还管用吗。ok,改一下demo。
在xml中btn3放在btn2的上面,然后在代码中将btn2强制置顶。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<Button
android:id="@+id/btn1"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#a59a6a"
android:text="btn1" />
<com.example.ontouchtext.MyFrameLayout
android:id="@+id/framelayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.example.ontouchtext.Btn2
android:id="@+id/btn2"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#aaaaaa"
android:text="btn2" />
<com.example.ontouchtext.Btn3
android:id="@+id/btn3"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="50dp"
android:layout_marginTop="50dp"
android:background="#1babaa"
android:text="btn3" />
</com.example.ontouchtext.MyFrameLayout>
</RelativeLayout>
然后在activity中编写置顶代码。
btn2.bringToFront();
自定义Btn2,Bnt3。
public class Btn2 extends Button{
public Btn2(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("Btn2", "btn2 on click~~~~~");
}
return super.onTouchEvent(event);
}
}
public class Btn3 extends Button{
public Btn3(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("Btn3", "btn3 on click~~~~~");
}
return super.onTouchEvent(event);
}
}
自定一个MyFrameLayout
public class MyFrameLayout extends FrameLayout {
public MyFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final float xf = ev.getX();
final float yf = ev.getY();
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Rect frame = new Rect();
for (int i = getChildCount() - 1; i >= 0; i--) {
Button v = (Button) getChildAt(i);
Log.d("MainActivity", v.getText().toString());
v.getHitRect(frame);
Log.d("MainActivity", frame.contains((int)xf,(int)yf)+"");
}
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(" onTouchEvent", "MyFrameLayout onTouchEvent go on~~");
return super.onTouchEvent(event);
}
}
这时候如果我点击btn2和btn3的交集处,到底会触发bt2还是btn3了,我们看看log。
btn2触发了,再看看上面的先打印的是btn2,btn3说明我们调用了btn2.bringToFront()后系统将本来在最后面的btn3放在了btn2的后面,倒序循环后,btn2最先进入到for循环中,然后复制给了target后return。
希望对大家有帮助,如有错误希望大家指出。
附上代码:这里写链接内容