事件分发机制ViewGroup

参考文章1
参考文章2

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;
}

事件分发机制流程图
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KillerNoBlood

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值