View事件分发机制(源码分析篇)
01.Android中事件分发顺序
1.1 事件分发的对象是谁
- 事件分发的对象是事件。注意,事件分发是向下传递的,也就是父到子的顺序。
当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。
- Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
主要发生的Touch事件有如下四种:
- MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
- MotionEvent.ACTION_MOVE:滑动View
- MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
- MotionEvent.ACTION_UP:抬起View(与DOWN对应)
事件列:
- 从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件。即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理
- 任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图:
1.2 事件分发的本质
将点击事件(MotionEvent)向某个View进行传递并最终得到处理
- 即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
- Android事件分发机制的本质是要解决,点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。
1.3 事件在哪些对象间进行传递
Activity、ViewGroup、View
- 一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
- Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的
View是所有UI组件的基类
- 一般Button、ImageView、TextView等控件都是继承父类View
ViewGroup是容纳UI组件的容器,即一组View的集合(包含很多子View和子VewGroup),
- 其本身也是从View派生的,即ViewGroup是View的子类
- 是Android所有布局的父类或间接父类:项目用到的布局(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类。
- 与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。
1.4 事件分发过程涉及方法
事件分发过程由这几个方法协作完成
dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()
1.5 Android中事件分发顺序
Android中事件分发顺序:
- Activity(Window) -> ViewGroup -> View
其中:
- super:调用父类方法
- true:消费事件,即事件不继续往下传递
- false:不消费事件,事件继续往下传递 / 交由给父控件onTouchEvent()处理
充分理解Android分发机制,本质上是要理解:
- Activity对点击事件的分发机制
- ViewGroup对点击事件的分发机制
- View对点击事件的分发机制
02.Activity的事件分发机制
2.1 源码分析
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发
- 具体是由Activity的Window来完成
我们来看下Activity的dispatchTouchEvent()的源码
public boolean dispatchTouchEvent(MotionEvent ev) {//第一步//一般事件列开始都是DOWN,所以这里基本是trueif (ev.getAction() == MotionEvent.ACTION_DOWN) {//第二步onUserInteraction();}//第三步if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);
}
第一步
- 一般事件列开始都是DOWN(按下按钮),所以这里返回true,执行onUserInteraction()
第二步
- 先来看下onUserInteraction()源码
public void onUserInteraction() { }
从源码可以看出:
- 该方法为空方法
- 从注释得知:当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
- 所以onUserInteraction()主要用于屏保
第三步
- Window类是抽象类,且PhoneWindow是Window类的唯一实现类
- superDispatchTouchEvent(ev)是抽象方法
- 通过PhoneWindow类中看一下superDispatchTouchEvent()的作用
@Override public boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);//mDecor是DecorView的实例//DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类 }
接下来我们看mDecor.superDispatchTouchEvent(event):
public boolean superDispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);//DecorView继承自FrameLayout//那么它的父类就是ViewGroup而super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent() }
得出结果
- 执行getWindow().superDispatchTouchEvent(ev)实际上是执行了ViewGroup.dispatchTouchEvent(event)
- 这样事件就从 Activity 传递到了 ViewGroup
2.2 点击事件调用顺序
三个方法执行顺序
@Override public boolean onInterceptTouchEvent(MotionEvent e) {LogUtils.e("yc----------事件拦截----------");return super.onInterceptTouchEvent(e); }@Override public boolean dispatchTouchEvent(MotionEvent ev) {LogUtils.e("yc----------事件分发----------");return super.dispatchTouchEvent(ev); }@SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent e) {LogUtils.e("yc----------事件触摸----------");return super.onTouchEvent(e); }
- 执行结果如下
yc----------事件分发---------- yc----------事件拦截---------- yc----------事件触摸----------
下面将用一段伪代码来阐述上述三个方法的关系和点击事件传递规则
// 点击事件产生后,会直接调用dispatchTouchEvent分发方法 public boolean dispatchTouchEvent(MotionEvent ev) {//代表是否消耗事件boolean consume = false;if (onInterceptTouchEvent(ev)) {//如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件//则该点击事件则会交给当前View进行处理//即调用onTouchEvent ()方法去处理点击事件consume = onTouchEvent (ev) ;} else {//如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件//则该点击事件则会继续传递给它的子元素//子元素的dispatchTouchEvent()就会被调用,重复上述过程//直到点击事件被最终处理为止consume = child.dispatchTouchEvent (ev) ;}return consume; }
当一个点击事件发生时,调用顺序如下
- 1.事件最先传到Activity的dispatchTouchEvent()进行事件分发
- 2.调用Window类实现类PhoneWindow的superDispatchTouchEvent()
- 3.调用DecorView的superDispatchTouchEvent()
- 4.最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent()
2.3 得出结论
- 当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发,最终是调用了ViewGroup的dispatchTouchEvent()方法
- 这样事件就从 Activity 传递到了 ViewGroup
03.ViewGroup事件的分发机制
3.1 看一下这个案例
布局如下:
结果测试
- 只点击Button,发现执行顺序:btn1,btn2
- 再点击空白处,发现执行顺序:btn1,btn2,viewGroup
从上面的测试结果发现:
- 当点击Button时,执行Button的onClick(),但ViewGroupLayout注册的onTouch()不会执行
- 只有点击空白区域时才会执行ViewGroupLayout的onTouch();
- 结论:Button的onClick()将事件消费掉了,因此事件不会再继续向下传递。
3.2 源码分析
ViewGroup的dispatchTouchEvent()源码分析,该方法比较复杂,截取几个重要的逻辑片段进行介绍,来解析整个分发流程。
// 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {//disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用//可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//默认情况下会进入该方法if (!disallowIntercept) {//调用拦截方法intercepted = onInterceptTouchEvent(ev); ev.setAction(action);} else {intercepted = false;} } else {// 当没有触摸targets,且不是down事件时,开始持续拦截触摸。intercepted = true; }
- 这一段的内容主要是为判断是否拦截。如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果mFirstTouchTarget != null,即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果不是以上两种情况,即已经是MOVE或UP事件了,并且之前的事件没有对象进行处理,则设置成true,开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()方法返回false,那么接下来的一些列事件都不会交给他处理。如果VieGroup的onInterceptTouchEvent()第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。
当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View或ViewGroup进行处理。
/* 从最底层的父视图开始遍历,** 找寻newTouchTarget,即上面的mFirstTouchTarget** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。*/ for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = customOrder? getChildDrawingOrder(childrenCount, i) : i;final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);// 如果当前视图无法获取用户焦点,则跳过本次循环if (childWithAccessibilityFocus != null) {if (childWithAccessibilityFocus != child) {continue;}childWithAccessibilityFocus = null;i = childrenCount - 1;}//如果view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}newTouchTarget = getTouchTarget(child);// 已经开始接收触摸事件,并退出整个循环。if (newTouchTarget != null) {newTouchTarget.pointerIdBits |= idBitsToAssign;break;}//重置取消或抬起标志位//如果触摸位置在child的区域内,则把事件分发给子View或ViewGroupif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// 获取TouchDown的时间点mLastTouchDownTime = ev.getDownTime();// 获取TouchDown的Indexif (preorderedList != null) {for (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}//获取TouchDown的x,y坐标mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();//添加TouchTarget,则mFirstTouchTarget != null。newTouchTarget = addTouchTarget(child, idBitsToAssign);//表示以及分发给NewTouchTargetalreadyDispatchedToNewTouchTarget = true;break; }
dispatchTransformedTouchEvent()
方法实际就是调用子元素的dispatchTouchEvent()
方法。- 其中
dispatchTransformedTouchEvent()
方法的重要逻辑如下:
if (child == null) {handled = super.dispatchTouchEvent(event); } else {handled = child.dispatchTouchEvent(event); }
- 由于其中传递的child不为空,所以就会调用子元素的dispatchTouchEvent()。如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环。
//添加TouchTarget,则mFirstTouchTarget != null。 newTouchTarget = addTouchTarget(child, idBitsToAssign);//表示以及分发给NewTouchTargetalreadyDispatchedToNewTouchTarget = true;
- 其中在
addTouchTarget(child, idBitsToAssign);
内部完成mFirstTouchTarget被赋值。如果mFirstTouchTarget为空,将会让ViewGroup默认拦截所有操作。如果遍历所有子View或ViewGroup,都没有消费事件。ViewGroup会自己处理事件。
3.3 得出结论
- Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View
在ViewGroup中通过onInterceptTouchEvent()对事件传递进行拦截
- onInterceptTouchEvent方法返回true代表拦截事件,即不允许事件继续向子View传递;
- 返回false代表不拦截事件,即允许事件继续向子View传递;(默认返回false)
- 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。
04.View事件的分发机制
4.1 源码分析
View中dispatchTouchEvent()的源码分析
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
从上面可以看出:
- 只有以下三个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent(event)方法
第一个条件:mOnTouchListener != null;
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED;
第三个条件:mOnTouchListener.onTouch(this, event);
下面,我们来看看下这三个判断条件:
- 第一个条件:mOnTouchListener!= null
//mOnTouchListener是在View类下setOnTouchListener方法里赋值的
public void setOnTouchListener(OnTouchListener l) { //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)mOnTouchListener = l;
}
- 第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED
该条件是判断当前点击的控件是否enable
由于很多View默认是enable的,因此该条件恒定为true
- 第三个条件:mOnTouchListener.onTouch(this, event)
回调控件注册Touch事件时的onTouch方法
//手动调用设置
button.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; }
});
- 如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。
- 如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。
接下来,我们继续看:onTouchEvent(event)的源码分析
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. 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)) { //如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; // 在经过种种判断之后,会执行到关注点1的performClick()方法。//请往下看关注点1if ((mPrivateFlags & PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { //关注点1//请往下看performClick()的源码分析performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now 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(); // Be lenient about moving outside of buttons int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; }
//如果该控件是可以点击的,就一定会返回truereturn true; }
//如果该控件是不可以点击的,就一定会返回falsereturn false;
}
关注点1:
- performClick()的源码分析
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
- 只要mOnClickListener不为null,就会去调用onClick方法;
那么,mOnClickListener又是在哪里赋值的呢?请继续看:
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
- 当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值(不为空),即会回调onClick()。
4.2 得出结论
- 1.onTouch()的执行高于onClick()
2.每当控件被点击时:
如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();如果回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()。
- onTouch()返回false(该事件没被onTouch()消费掉) = 执行onTouchEvent() = 执行OnClick()
如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;
- onTouch()返回true(该事件被onTouch()消费掉) = dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行OnClick()
4.3 验证结论
在回调onTouch()里返回true
TextView textView = findViewById(R.id.tv_13); //设置OnTouchListener() textView.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {Log.d("小王逗比","执行了onTouch(), 动作是:" + event.getAction());return true;} }); //设置OnClickListener textView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.d("小王逗比","执行了onClick()");} });
- 打印日志如下所示
- 注意action为0是ACTION_DOWN,为2是ACTION_MOVE,为1是ACTION_UP。
2019-04-04 13:37:58.301 13616-13616/org.yczbj.ycrefreshview D/小王逗比: 执行了onTouch(), 动作是:0 2019-04-04 13:37:58.315 13616-13616/org.yczbj.ycrefreshview D/小王逗比: 执行了onTouch(), 动作是:2 2019-04-04 13:37:58.405 13616-13616/org.yczbj.ycrefreshview D/小王逗比: 执行了onTouch(), 动作是:2 2019-04-04 13:37:58.408 13616-13616/org.yczbj.ycrefreshview D/小王逗比: 执行了onTouch(), 动作是:1
在回调onTouch()里返回false
- 打印结果如下所示
2019-04-04 13:41:26.961 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动作是:0 2019-04-04 13:41:26.978 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动作是:2 2019-04-04 13:41:27.072 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动作是:2 2019-04-04 13:41:27.074 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动作是:1 2019-04-04 13:41:27.076 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onClick()
- 总结:onTouch()返回true就认为该事件被onTouch()消费掉,因而不会再继续向下传递,即不会执行OnClick()。
05.思考一下
5.1 onTouch()和onTouchEvent()的区别
- 这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
- 如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。
特别注意:请看下面代码
//&&为短路与,即如果前面条件为false,将不再往下执行 //所以,onTouch能够得到执行需要两个前提条件: //1. mOnTouchListener的值不能为空 //2. 当前点击的控件必须是enable的。 mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)
- 因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。
5.2 Touch事件的后续事件(MOVE、UP)层级传递
- 如果给控件注册了Touch事件,每次点击都会触发一系列action事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP等)
当dispatchTouchEvent在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到后一个事件(ACTION_MOVE和ACTION_UP)
- 即如果在执行ACTION_DOWN时返回false,后面一系列的ACTION_MOVE和ACTION_UP事件都不会执行
从上面对事件分发机制分析知:
- dispatchTouchEvent()和 onTouchEvent()消费事件、终结事件传递(返回true)
- 而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,对后续的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用
- 请记住:接收了ACTION_DOWN事件的函数不一定能收到后续事件(ACTION_MOVE、ACTION_UP)
这里给出ACTION_MOVE和ACTION_UP事件的传递结论:
- 如果在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()消费事件(返回true),那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP
- 如果在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费事件(返回true),那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent()并结束本次事件传递过程。
View事件分发机制(源码分析篇)相关推荐
- android SDK-25事件分发机制--源码正确解析
android SDK-25事件分发机制–源码正确解析 Android 事件分发分为View和ViewGroup的事件分发,ViewGroup比View过一个拦截判断,viewgroup可以拦截事件, ...
- View的Touch事件分发(二.源码分析)
Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...
- Android View系列(二):事件分发机制源码解析
概述 在介绍点击事件规则之前,我们需要知道我们分析的是MotionEvent,即点击事件,所谓的事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent生成以后,系统需要把这 ...
- android 事件分发 代码解析,Android事件分发之源码分析
原文首发于微信公众号:躬行之,欢迎关注交流! 上篇文章中叙述了 Android 事件分发的大致流程,下面从 Activity.ViewGroup.View 三个方面介绍事件的相关方法,小节如下: Ac ...
- 深入解析Android事件分发机制源码(1)
有关事件分发的文章,网上已经有了太多太多,但是看了很多,大部分都只是讲解了最外层表现给开发者看的结果,并没有深入讲解,为何会得到这个现象.基于透过现象看本质的思想,趁着手头没有太多活,写下这篇博客,一 ...
- Android事件分发之源码分析,kotlin库
//这里又调用了FrameLayout中的dispatchTouchEvent方法 return super.dispatchTouchEvent(event); } - } 由于在 FrameLay ...
- Android面试老生常谈的 View 事件分发机制,看这一篇就够了
本文首发我的微信公众号:徐公,想成为一名优秀的 Android 开发者,需要一份完备的 知识体系,在这里,让我们一起成长,变得更好~. 在 Android 开发当中,View 的事件分发机制是一块很重 ...
- android字符显示流程图,Android应用层View绘制流程与源码分析
1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...
- Android应用层View绘制流程与源码分析
前言 Activity中界面加载显示的基本流程原理,最终分析结果就是下面的关系: 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归 ...
最新文章
- python--练习--将字符串转成列表
- 一种新的8B/10B编解码硬件设计方法
- ContextLoader,ContextLoaderListener解读
- 单片机的内存分配(变量的存储位置)详解
- nagios无法安装check_mysql插件的问题
- TThread类详解转
- EDUCoder编程练习题解(循环)
- 终于,我们这代程序员在上海各奔东西
- centos locale报错问题
- (字符串)ZigZag Conversion
- Aqua Data Studio 19中文版
- Linux开发之libaio源码分析及应用
- 《组合数学全家桶》(ACM / OI 全网最全,清晰易懂)
- matlab 光平面标定代码,光平面标定法
- Vue进阶(六十八):JS 判断当前浏览器是否为 IE
- 突破KEIL软件编译时 C51中断号最大只能为31限制的补丁,使中断号可以达到256
- vue 图表三维立体3D散点图
- css空心图形,css画空心箭头
- 【HCIE-RS 天梯路】STP RSTP MSTP
- POI实现一个通用的Excel读取模板
热门文章
- python获取用户输入中文_python中的用户输入
- linux查看python环境变量_Linux中的Python环境变量
- int main( int argc , char *argv[] , char *envp[] )中参数解说
- 电机调速制动matlab,基于Matlab的三相异步电动机起动、调速和制动特性仿真
- stm32单片机屏幕一直闪_stm32实现LED灯的闪烁
- 关于startActivityForResult
- 上海将打造“泛在化、融合化、智敏化”智慧城市
- Mozilla停止对Firefox Hello的支持(采访)
- c++ 11 多线程处理(1)
- html5中的新标签