美文网首页
2.Android 3分钟快速搞定事件分发 (无惧面试 )

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

作者: 鹏城十八少 | 来源:发表于2021-04-08 12:01 被阅读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

    相关文章

      网友评论

          本文标题:2.Android 3分钟快速搞定事件分发 (无惧面试 )

          本文链接:https://www.haomeiwen.com/subject/vdnmkltx.html