1、ViewGroup
1)ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,平时项目里经常用到的各种布局,均属于ViewGroup的子类。
2)ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。
2、Demo演示
自定义一个MyConstraintLayout,继承自ConstraintLayout
public class MyConstraintLayout extends ConstraintLayout {
public MyConstraintLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
布局MyConstraintLayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.viewviewgroup_zlz.MyConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/id_my_constraint"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/id_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_dark"
android:padding="30dp"
android:text="click me"
android:textColor="@android:color/white"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.example.viewviewgroup_zlz.MyConstraintLayout>
在MainActivity中使用MyConstraintLayout
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//触摸空白
findViewById(R.id.id_my_constraint).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(Constants.TAG, "------ layout touch");
return false;
}
});
//点击空白
findViewById(R.id.id_my_constraint).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(Constants.TAG, "------ layout click");
}
});
//按钮触摸
findViewById(R.id.id_btn).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(Constants.TAG, "------ button touch");
return false;
}
});
//点击按钮
findViewById(R.id.id_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(Constants.TAG, "------ button click");
}
});
}
}
效果图
点击按钮,再点击空白处
3、事件分发流程
当点击了某个控件,首先会去调用该控件所在布局的dispatchTouchEvent()方法,然后在布局的dispatchTouchEvent()方法中找到被点击的相应控件,再去调用该控件的dispatchTouchEvent()方法。
布局的dispatchTouchEvent()方法在ViewGroup中,控件的dispatchTouchEvent()方法在View中。
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;
}
//disallowIntercept : 是否禁用掉事件拦截的功能,默认是false,可通过调用requestDisallowInterceptTouchEvent()方法对这个值进行修改
//!onInterceptTouchEvent(ev) : onInterceptTouchEvent()返回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;
//遍历了当前ViewGroup下的所有子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);
//判断当前遍历的View是不是正在点击的View
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;
//调用了该View的dispatchTouchEvent
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
//点击空白区域时
//一般情况下target都会是null
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;
}
//执行View中的dispatchTouchEvent()方法了,因为ViewGroup的父类就是View
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);
}
View中的dispatchTouchEvent() :
public boolean dispatchTouchEvent(MotionEvent event) {
//mOnTouchListener : 在setOnTouchListener()方法里赋值的,即说只要给控件注册了touch事件,mOnTouchListener就一定被赋值
//(mViewFlags & ENABLED_MASK) == ENABLED : 判断当前点击的控件是否是enable
//mOnTouchListener.onTouch(this, event) : 去回调控件注册touch事件时的onTouch()方法
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
1)在dispatchTouchEvent()中最先执行的就是onTouch()方法,因此onTouch()肯定是要优先于onClick()执行的。
2)如果在onTouch()方法里返回了true,就会让dispatchTouchEvent()方法直接返回true,不会再继续往下执行,onClick()就不会再执行了。
3)onClick()的调用是在onTouchEvent(event)方法中的。
View中的onTouchEvent() :
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果该控件是可以点击的, 就会进入到switch判断中
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//如果当前的事件是抬起手指,则在经过种种判断之后,会执行performClick()方法
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) {
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
removeLongPressCallback();
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
//只有前一个action返回true,才会触发后一个action
return true;
}
return false;
}
View中的performClick() :
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
//只要mOnClickListener不是null,就会去调用它的onClick方法
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
事件分发机制流程图