转载请注明出处:http://blog.youkuaiyun.com/xiaohao0724/article/details/54798908
通过对上篇 Android中事件处理机制之---View的事件分发详解(一) 的学习相信大家对Android事件处理机制都有了一定的了解。接下来今天我们继续来学习ViewGroup中的事件传递机制。
Android中的事件是从布局一层层向里面的布局或控件传递的,在传递过程中如果在哪层被拦(onInterceptTouchEvent返回true)则在这层被消费不会再向内层传递,如果传递到最内层事件没有完全响应将一层层向外传递,如果外层响应则消费掉事件,如果不响应将继续向外层传递直到最外层布局。
ViewGroup是根据onInterceptTouchEvent的返回值进行事件传递的,当返回false时向内层传递,返回true是拦截事件不再向内层传递
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false/true;
}
我们先来新建一个工程EventViewGroup
自定义MyLinearlayout
public class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
// return super.onInterceptTouchEvent(ev);
}
}
activity_main.xml
<com.havorld.eventviewgroup.view.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/myLinearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="@android:color/holo_green_light"
android:gravity="center"
android:padding="10dp"
android:text="TextView" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:padding="10dp"
android:text="Button" />
</com.havorld.eventviewgroup.view.MyLinearLayout>
public class MainActivity extends Activity {
protected static final String TAG = "TAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyLinearLayout myLinearLayout = (MyLinearLayout) findViewById(R.id.myLinearLayout);
TextView textView = (TextView) findViewById(R.id.textView);
Button button = (Button) findViewById(R.id.button);
myLinearLayout.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG, "myLinearLayout---onTouch");
return false;
}
});
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "textView---onClick");
}
});
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "button---onClick");
}
});
}
}
当点击某个控件时,首先会去调用该控件所在布局父类ViewGroup中的dispatchTouchEvent方法,然后在该方法中遍历子控件找到被点击的对应控件,再去调用该控件的父类View中的dispatchTouchEvent方法。
上一篇Android中事件处理机制之---View的事件分发详解(一) 中我们已经对View中的dispatchTouchEvent进行过分析,现在我们来看一下ViewGroup中的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;
}
//标记①
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;
//标记②
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);
//标记③
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;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
//标记⑥
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
//标记⑦
return super.dispatchTouchEvent(ev);
}
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
return target.dispatchTouchEvent(ev);
}
一、对MyLinearLayout和TextView进行事件分析
分别点击TextView和空白处
1、onInterceptTouchEvent返回false
分析:
当点击了TextView时会先调用MyLinearLayout父控件ViewGroup中的dispatchTouchEvent,然后看源码的标记①处disallowIntercept || !onInterceptTouchEvent(ev),当两个条件有一个成立时进入判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改(不常用),第二个条件的值就是自定义MyLinearLayout复写onInterceptTouchEvent方法的返回值取反,此处我们返回的是false所以!onInterceptTouchEvent(ev) = true进入判断方法中,看标记②对布局中的子View进行遍历,在标记③处进行判断哪个View是所点击的View,在标记④处调用子View的事件分发机制child.dispatchTouchEvent(ev)并返回true执行TextView的onClick方法(注意:此处子View是TextView由于设置了onCick事件clickable 为真才返回true, 此处不明白的同学可参考上篇Android中事件处理机制之---View的事件分发详解(一) ),此时进入方法内在标记⑤处也就整个dispatchTouchEvent方法直接返回true后面的不再执行。
当点击空白处时先调用MyLinearLayout父控件ViewGroup中的onInterceptTouchEvent,在标记③处进行判断没有子View被点击,因为点击的是父布局,所以跳出判断继续向下执行到标记⑥处,由于没有运行标记④处所以没有跟mMotionTarget赋值,此时target = mMotionTarget = null进入判断方法中,在MyLinearLayout父控件ViewGroup内标记⑦处调用super.dispatchTouchEvent(ev)也就是ViewGroup的父控件View的事件分发机制(具体可参考Android中事件处理机制之---View的事件分发详解(一) ) 所以执行MyLinearLayout的onTouch方法。
2、onInterceptTouchEvent返回true
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
// return super.onInterceptTouchEvent(ev);
}
分析:
因为onInterceptTouchEvent返回true所以不管点击TextView还是MyLinearLayout在标记①处的判断都为false,所以不进入判断方法内,在标记⑥处进行判断进入方法内在标记⑦处把事件分发给MyLinearLayout父控件ViewGroup内标记⑦处调用super.dispatchTouchEvent(ev)也就是ViewGroup的父控件View,所以两次点击都执行MyLinearLayout的onTouch方法。
下面我们来做个拓展:
将TextView的onClick事件代码替换成onTouch事件(onInterceptTouchEvent返回false)
textView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG, "textView---onTouch---" + event.getAction());
return false;
}
});
分析:
点击TextView调用MyLinearLayout父控件ViewGroup中的dispatchTouchEvent,标记① 处 !onInterceptTouchEvent(ev) = true进入方法中,在标记④处调用的child.dispatchTouchEvent(ev)正是TextView的事件分发处理,根据上篇我们对事件分发的学习不难得出此处会只调用一次onTouch事件TextView执行按下事件并且child.dispatchTouchEvent(ev)的返回值为false,所以不会执行到标记⑤处返回而是直接执行到标记⑦处调用MyLinearLayout的父控件ViewGroup的super.dispatchTouchEvent(ev)事件分发,所以会执行MyLinearLayout的触摸事件。
这是父类布局和子控件结合在一块时的情况,各种组合多种多样但万变不离其宗,比如
当TextView的onTouch事件返回true时(onInterceptTouchEvent返回false)点击TextView:
当MyLinearLayout的触摸事件返回true时(T(onInterceptTouchEvent返回false,textView的onTouch返回false)点击一次TextView:
添加Activity的触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "activity---onTouch");
return super.onTouchEvent(event);
}
当
MyLinearLayout和textView的触摸事件都
返回false时(
T(onInterceptTouchEvent返回false
)点击一次TextView
总结一下: