2.Android 3分钟快速搞定事件分发 (无惧面试 )

2021-04-08  本文已影响0人  鹏城十八少

笔者是面霸,面试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

http://blog.csdn.net/lmj623565791/article/details/38960443

http://bbs.51cto.com/thread-870659-1.html

上一篇下一篇

猜你喜欢

热点阅读