您当前的位置: 首页 >  ui

命运之手

暂无认证

  • 3浏览

    0关注

    747博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【高级UI】【024】TouchEvent事件分发流程

命运之手 发布时间:2021-11-19 14:57:23 ,浏览量:3

分发流程

安卓的事件分发流程,并没有网上说的那么复杂

之所以网上很多讲得非常复杂,是因为动不动就拿系统源码说事

其实系统源码大多都是繁杂的细节处理,真正核心的东西剥离出来,并没有多少

还有就是,有的博主把流程图做得太细太复杂了

其实只要把最核心的流程讲清楚,剩下的让读者自己去思考发散下,就已经够了

图画得太细,反而让很多人根本看不下去

这里一张图,已经足够总结了整个事件分发的完整流程了

在这里插入图片描述 核心知识点

上面的图,基本上已经包含了全部的知识,这里再以文字的形式,列举其中最核心的内容

这是因为,整张图看下去,很容易忘,文字印象会更深刻点,图适合用于理解整个流程

所以这里既给了文字版,也给了图形版

  • dispatch方法,用于将父View的事件分发给子View
  • intercept方法,用于拦截当前事件,不分发给子View
  • onTouchEvent和OnTouchListener,用于对事件进行处理
  • 不管是dispatch方法,还是onTouch方法,true表示事件已处理,false表示未处理
  • 父View是通过子View的onDispatchTouchEvent方法的返回值,来判断子View有没有处理事件的
  • OnTouchListener和onTouchEvent,都只是onDispatchTouchEvent的内部逻辑而已,父View是不管这些的
  • 默认情况下,子控件onTouchEvent的返回值,就是子控件onDispatchTouchEvent方法的返回值,但是我们可以通过重写来打破默认逻辑
  • 如果onTouchEvent返回了false,onDispatchTouchEvent返回了true,对父控件而言,子控件处理了这个事件,父控件只看dispatch方法返回值
  • 当一个子控件处理了TouchEvent,余下的同级子控件,就收不到TouchEvent了
  • 当所有子控件都未对事件进行处理时,父控件将调用View基类中的onDispatchTouchEvent方法来处理事件
  • View.onDispatchTouchEvent调用OnTouchListener监听器和onTouchEvent方法来处理事件,优先使用监听器来处理事件,当监听器不存在,或监听器未处理时,才调用onTouchEvent来处理,监听器和onTouchEvent的处理结果,就是View.onDispatchTouchEvent的返回值
  • 重写分发流程,一般主要工作是修改方法返回值,但要记得调用super方法,因为事件分发是在super方法中实现的,如果不调用,事件就不会向下分发了

下沉传递和冒泡响应

读懂了上面的流程后,我们可以发现

整个事件流程,是一个先从上而下传递,再从下而上处理的U型过程

这个过程在业内,有个专业的术语,叫做下沉传递和冒泡响应

dispatch是由Activity传给ViewGroup,再传给View,如果其中有一个节点对分发流程进行了intercept,则停止继续分发

onTouchEvent是由View到ViewGroup,再到Activity,如果其中有一个节点对事件进行了处理,则停止向上冒泡,上级不需要再处理

在这里插入图片描述 测试方案

想要把事件分发流程吃透,光靠看是不行的,一定要自己多动手验证下,才能印象深刻

验证方法很简单,我们自定义两个View,两个ViewGroup

重写View,ViewGroup,Activity中和事件分发相关的方法

通过修改返回值,我们就能验证实际效果和理论是不是一致的了


	
	
	    
	
	        
	
	        
	    
	


	package com.android.architecture.view;
	
	import android.content.Context;
	import android.util.AttributeSet;
	import android.view.MotionEvent;
	import android.view.View;
	
	import com.easing.commons.android.code.Console;
	
	@SuppressWarnings("all")
	public class V1 extends View {
	
	    public V1(Context context) {
	        this(context, null);
	    }
	
	    public V1(Context context, AttributeSet attributeSet) {
	        this(context, attributeSet, 0);
	    }
	
	    public V1(Context context, AttributeSet attributeSet, int style) {
	        super(context, attributeSet, style);
	        init(context, attributeSet);
	    }
	
	    protected void init(Context context, AttributeSet attributeSet) {
	    }
	
	    @Override
	    public boolean dispatchTouchEvent(MotionEvent event) {
	        if (event.getAction() == MotionEvent.ACTION_DOWN) {
	            Console.info("View1  dispatchTouchEvent  ACTION_DOWN");
	            super.dispatchTouchEvent(event);
	            return true;
	        }
	        if (event.getAction() == MotionEvent.ACTION_UP)
	            Console.info("View1  dispatchTouchEvent  ACTION_UP");
	        return super.dispatchTouchEvent(event);
	    }
	
	    @Override
	    public boolean onTouchEvent(MotionEvent event) {
	        if (event.getAction() == MotionEvent.ACTION_DOWN)
	            Console.info("View1  onTouchEvent  ACTION_DOWN");
	        if (event.getAction() == MotionEvent.ACTION_UP)
	            Console.info("View1  onTouchEvent  ACTION_UP");
	        return false;
	    }
	}


	package com.android.architecture.view;
	
	import android.content.Context;
	import android.util.AttributeSet;
	import android.view.MotionEvent;
	import android.widget.FrameLayout;
	
	import com.easing.commons.android.code.Console;
	
	@SuppressWarnings("all")
	public class G1 extends FrameLayout {
	
	    public G1(Context context) {
	        this(context, null);
	    }
	
	    public G1(Context context, AttributeSet attributeSet) {
	        this(context, attributeSet, 0);
	    }
	
	    public G1(Context context, AttributeSet attributeSet, int style) {
	        super(context, attributeSet, style);
	        init(context, attributeSet);
	    }
	
	    protected void init(Context context, AttributeSet attributeSet) {
	        setClickable(true);
	    }
	
	    @Override
	    public boolean onInterceptTouchEvent(MotionEvent event) {
	        if (event.getAction() == MotionEvent.ACTION_DOWN)
	            Console.info("G1  onInterceptTouchEvent");
	        return false;
	    }
	
	    @Override
	    public boolean dispatchTouchEvent(MotionEvent event) {
	        if (event.getAction() == MotionEvent.ACTION_DOWN) {
	            Console.info("G1  dispatchTouchEvent  ACTION_DOWN");
	            super.dispatchTouchEvent(event);
	            return true;
	        }
	        if (event.getAction() == MotionEvent.ACTION_UP)
	            Console.info("G1  dispatchTouchEvent  ACTION_UP");
	        return super.dispatchTouchEvent(event);
	    }
	
	    @Override
	    public boolean onTouchEvent(MotionEvent event) {
	        if (event.getAction() == MotionEvent.ACTION_DOWN)
	            Console.info("G1  onTouchEvent  ACTION_DOWN");
	        if (event.getAction() == MotionEvent.ACTION_UP)
	            Console.info("G1  onTouchEvent  ACTION_UP");
	        return false;
	    }
	}





	package com.android.architecture;
	
	import android.view.MotionEvent;
	import android.widget.FrameLayout;
	
	import com.easing.commons.android.app.CommonActivity;
	import com.easing.commons.android.code.Console;
	import com.easing.commons.android.ui.control.title_bar.TitleBar;
	import com.easing.commons.android.ui.dialog.BottomSlideDialog;
	
	import butterknife.BindView;
	import lombok.SneakyThrows;
	
	@SuppressWarnings("all")
	public class StartActivity extends CommonActivity {
	
	    @BindView(R.id.titleBar)
	    TitleBar titleBar;
	
	    @BindView(R.id.container)
	    FrameLayout container;
	
	    BottomSlideDialog dialog;
	
	    @Override
	    protected boolean beforeCreate() {
	        statusBarColor = R.color.color_transparent;
	        navigationBarColor = R.color.color_light_blue;
	        return super.beforeCreate();
	    }
	
	    @Override
	    @SneakyThrows
	    protected void create() {
	        setContentView(R.layout.activity_start);
	        titleBar.showReturnButton(false);
	        titleBar.setBackgroundResource(R.drawable.color_light_blue);
	        titleBar.setTitleText("Android");
	        dialog = BottomSlideDialog.create(ctx, R.layout.dialog_menu);
	        init();
	    }
	
	    @Override
	    public void onBackPressed() {
	        dialog.show();
	    }
	
	    protected void init() {
	
	    }
	
	    @Override
	    public boolean dispatchTouchEvent(MotionEvent event) {
	        if (event.getAction() == MotionEvent.ACTION_DOWN)
	            Console.info("Activity  dispatchTouchEvent  ACTION_DOWN");
	        if (event.getAction() == MotionEvent.ACTION_UP)
	            Console.info("Activity  dispatchTouchEvent  ACTION_UP");
	        super.dispatchTouchEvent(event);
	        return true;
	    }
	
	    @Override
	    public boolean onTouchEvent(MotionEvent event) {
	        if (event.getAction() == MotionEvent.ACTION_DOWN)
	            Console.info("Activity  onTouchEvent  ACTION_DOWN");
	        if (event.getAction() == MotionEvent.ACTION_UP)
	            Console.info("Activity  onTouchEvent  ACTION_UP");
	        return false;
	    }
	}

ACTION_UP和ACTION_DOWN之间的关联

按下和弹起事件,一定是成对出现的,操作系统会对事件做一致性校验

当dispatchTouchEvent接收到了Down事件时,只有返回了true,下次才能接收到与之对应的Up事件

如果dispatchTouchEvent在处理Down事件时返回了false,则下次的Up事件,不会再分发给该控件

同理,ACTION_MOVE和ACTION_DOWN的关系也是如此

onClick和onTouchEvent之间的关联

onClick是onTouchEvent方法中的一个子处理

当onTouchEvent触发了Down事件,又触发了Up事件,中间触摸区域没有离开过控件范围

便会被视为一次点击事件,然后onTouchEvent会执行performClickInternal代码,来完成点击事件的处理

当然,这是View的默认行为,如果我们重写了onTouchEvent等方法,或没有调用super方法

那么onClick就不会执行,这就是为什么很多人重写触摸事件之后,点击事件失效的原因

关注
打赏
1654938663
查看更多评论
立即登录/注册

微信扫码登录

0.0412s