2.Android 3分钟快速搞定事件分发 (无惧面试 )
笔者是面霸,面试200+场 当过考官:面过别人300+场 去过500强,也呆过初创公司。
关注我就能达到大师级水平,这话我终于敢说了, 年薪60万不是梦!
斩获腾讯、华为、字节跳动,蚂蚁金服,oppo,VIVO,安卓岗offer!我有一套速通大厂技巧分享给你!
面试十二连问,你招的住吗?
1.事件分发机制是怎么样的?
2.onTouch和onTouchevent和onClick的执行顺序?
3.onTouch返回值,onTouchevent返回值,导致结果如何,添加linsternr又如何?
4.Button和ImageView有什么不一样?
5.如何理解消费?
6.可以达到父控件和子空间同时点击吗?
7.ListView上的button,点击button2个控件同时要有相应,应该怎么处理?
8.点击事件被拦截,但是相传到下面的view,如何操作?
9.实战分析
10.请简述Android事件传递机制, ACTION_CANCEL事件何时触发?
进入正题:
1.事件分发机制是怎么样的?
事件分发机制是一种责任链模式
事件分发机制分为2种:View事件的分发和ViewGroup事件分发机制
先看简单的View事件分发机制,demo如下
//子控件的ontouch方法影响子控件的函数
//onTouch====onTouchEvent====onClick;
/**
* 检验view的事件分发顺序,点击---dispatch- Ontouch返回值为ture 不执行---ontouchEvent---onclick
*/
button1.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "button1 on touch"+event.getAction());
return true;
}
});
/**
* 检验view的事件分发顺序, 点击---dispatch- Ontouch返回值为false执行---ontouchEvent---onclick
*/
button2.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "button1 on touch" + event.getAction());
return false;
}
});
demo地址:
源码分析:然后我们来看一下View中dispatchTouchEvent方法的源码:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
整个View的事件转发流程是:(原理是dispatchTouchEvent)
View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。
onClick方法是在onTouchEvent方法里调用的
总结
1.看判断条件。如果没有mOnTouchListener ,ontouch不执行,onTouchEvent执行
2.如果有mOnTouchListener,并且onTouchEvent=true,onTouchEvent不执行
3.如果有mOnTouchListener,并且onTouchEvent=false,onTouchEvent执行
ViewGruop的事件分发:
多了一个拦截事件的方法:onInterceptTouchEvent
ViewGroup的dispathcTouchEvent方法:里面有onInterceptTouchEvent方法
onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。
总结ViewGroup发现:dispathcTouchEvent开始-----disallownotIntercepter---onInterceptTouchEvent-----1.子类dispath() 2.父类TouchEvent方法
源码:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier !=null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled =false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
||mFirstTouchTarget !=null) {
final boolean disallowIntercept = (mGroupFlags &FLAG_DISALLOW_INTERCEPT) !=0;
if (!disallowIntercept) {
intercepted =onInterceptTouchEvent(ev);
/**默认为false,不拦截子控件的监听*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
是否传递给子view,通过这个方法:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child ==null) {
handled =super.dispatchTouchEvent(event);
}else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
实例分析:
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。
简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。
上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。
dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。
在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件
理论总结:
1. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。
Activity到----phoneWindow-----decorVIew----TitleBar------ViewGroup:是通过看源代码发现的
2. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
3.拦截的好好处在于调用谁的dispatchTouchEvent的方法,谁出来点击事件
4. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。
5.在ViewGroup中onInterceptTouchEvent方法若反回false,那么触屏事件会继续向下传递,
但如果没有子View去处理这个事件,即子view的onTouchEvent没有返回True
则最后还是由ViewGroup去处理这个事件,也就又执行了自己的onTouchEvent。
ViewGroup 类中,实际是没有onTouchEvent 方法的,但是由于ViewGroup 继承自View,
2.onTouch和onTouchevent和onClick的执行顺序?
View.dispatchEvent----ontouch-----ontouchEvent(方法down,up,判断onclick时间)---onclick
ontouch先执行,如果返回true,ontouchEvent,onClick都不执行
ontouch先执行,如果返回false,ontouchEvent,然后再是onclick方法
如果不重写onTouchListerner方法。
onTouchEvent如果返回true,不会执行onclick方法
onTouchEvent如果返回false,会执行onclick方法
思考的问题:
1.View的dispatchTouchEvent重写了会怎么样,View的onTouchEvent重写了会怎样?
2.ViewGroup的dispatchTouchEvent重写了会怎么样,ViewGroup的onTouchEvent重写了会怎样?
3.Button和ImageView有什么不一样?
Button和ImageView效果不一样:一个是自带点击,一个是要自己控制点击
onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,
那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。
/**ImageView默认是不能点击事件的,要想点击的话必须手动设置*/
imageView.setClickable(true);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("TAG","imageView setOnTouchListener");
}
});
5.如何理解消费?
如果onTouch为true,代表消费了。不会执行onTouchevent了
如果子类的onTouchevent为true,代表消费了,父类不会执行onTouchevent
6.可以达到父控件和子空间同时点击吗?
如下:7问题
7.ListView上的button,点击button2个控件同时要有相应,应该怎么处理?
分析:从listview分发到button
在listView的空白区域:执行listview的onTouchEvent方法。拦截button
在button的点击区域: 不拦截,消费button的onTouchEvent事件。
具体方案:在listView的OnInterceptTouchEvent()方法里面。判断区域。是否拦截
顺便提一下父控件和子控件状态跟随
当父控件是布局而子控件是控件时,如果要设置点击效果,可以在父布局里面加上android:clickable="true" ,在子控件里面设置android:clickable="false",并设置状态跟随父布局android:duplicateParentState="true",至于效果,则随自己写吧
8.点击事件被拦截,但是相传到下面的view,如何操作?
重写子类的requestDisallowInterceptTouchEvent()方法返回true,就不会执行父类的onInterceptTouchEvent(),即可将点击事件传到下面的View。
9.实战分析
1.如何让子类只有触摸事件,没有点击事件
2.如何让子类既有触摸事件又有点击事件
3.如何让父类只有触摸事件,没有点击事件
4.如何让父类既有触摸事件又有点击事件
1.onTouch为true,事件被消费了,ontouchevent不执行,点击也就不会执行
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("peng"," button.setOnTouchListener"+event.getAction());
return true;
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("peng"," button.setOnClickListener");
}
});
2.onTouch为false,ontouchevent会执行,这样,点击事件会执行
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("peng"," button.setOnTouchListener"+event.getAction());
return false;
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("peng"," button.setOnClickListener");
}
});
默认情况下,子view的
onTouchEvent返回true,消费
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean pass=super.onTouchEvent(event);
Log.d("peng","onTouchEvent onTouchEvent"+pass);
return pass;
}
3.父类想要响应,子类不能消费,onTouch false ,onTouchEvent 也为false,onTouch 为false
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("peng"," button.setOnClickListener");
}
});
viewHead.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("peng"," viewHead.setOnTouchListener"+event.getAction());
return true;
}
});
viewHead.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("peng"," viewHead.setOnClickListener");
}
});
public class Myviewextends android.support.v7.widget.AppCompatButton {
public Myview(Context context) {
super(context);
}
public Myview(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public Myview(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("peng","Myview onTouchEvent onTouchEvent"+false);
return false;
}
4.
父类想要响应,子类不能消费,onTouch false ,onTouchEvent 也为false,onTouch 为false .父类的onTouchEvent也要重写true,代表消费了。
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("peng","MyParentView onTouchEvent");
return true;
}
1.如何让子类只有触摸事件,没有点击事件
2.如何让子类既有触摸事件又有点击事件
3.1如何让父类只有触摸事件,没有点击事件,.如何让父类既有触摸事件又有点击事件
子控件拿到事件之后,先判断是否设置了OnTouchListener, 如果设置了,则调用OnTouchListener的onTouch方法,如果返回true,事件已经处理到此结束,则跳过onTouchEvent方法,否则调用onTouchEvent方法,当onTouchEvent方法返回true,则事件处理到此结束,上面的父控件就不会再调用onTouchEvent方法
实战分析二:我想让webview在应用里面后台运行?看不到界面
2个viewGoup
1个webview和一个ViewGroup(包含4个btn)
点击btn的时候,会消费掉事件
问题:viewgroup点击空白也是会有事件的,如果给他添加监听
加入点击空白页面,Viewgroup的子View消费掉。(webview的子空间)
问题:不想让子控件消费怎么做
1.可以自己消费掉,ontouch==true。不再传递了,onclick事件就不会执行
2.可以让另外一个viewgourp自己消费掉。
ontouch==true,自己的onclick不会在执行,但是这个子view还是响应了
原因:因为没有拦截,走了子类的dispathevent方法。子类的dispathevent----ontouch---子类的onclick方法消费了。
所以消费是onclick和ontouch方法,但是onclick方法也是要先调用ontouch---ontouchevnet----onclick。
总结:最后的消费🈯️的时onTouch事件
实战分析三:
1.有一个布局,然后有一个textview,然后想点击整个布局有点击事件
结果发现:点击textview的区域没有响应,因为textview把焦点占用了。
为了让整个区域都有效果的话,把textview设置成
android:clickable="false"
android:id="@+id/tv_sport_data_second_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/dp_20"
android:textColor="@color/color_333333"
android:textStyle="bold"
android:clickable="false"
android:text="0"
android:layout_below="@id/ll_second_title"
android:layout_marginTop="@dimen/dp_6"
android:id="@+id/rl_sport_data_second"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
>
android:id="@+id/ll_second_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
>
android:id="@+id/tv_sport_data_second_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/color_333333"
android:textSize="@dimen/dp_14"
android:text="@string/string_sport_rank_total_sport"
></TextView>
10.请简述Android事件传递机制, ACTION_CANCEL事件何时触发?
https://blog.csdn.net/kingofhacker/article/details/75111372
ACTION_CANCELL:手指保持按下操作,并从当前控件转移到外层控件时触发
关于第一个问题,不做任何解释。 (悦动圈里面的滑动开关)
关于ACTION_CANCEL何时被触发,系统文档有这么一种使用场景:在设计设置页面的滑动开关时,如果不监听ACTION_CANCEL,在滑动到中间时,如果你手指上下移动,就是移动到开关控件之外,则此时会触发ACTION_CANCEL,而不是ACTION_UP,造成开关的按钮停顿在中间位置。
意思是当滑动的时候就会触发,不知道大家搞没搞过微信的长按录音,有一种状态是“松开手指,取消发送”,这时候就会触发ACTION_CANCEL。
https://blog.csdn.net/cufelsd/article/details/89471402
https://blog.csdn.net/epubit17/article/details/80342004
参考博客:
http://blog.csdn.net/guolin_blog/article/details/9097463