事件传递机制面试问什么?

自定义Behavior的艺术探索-仿UC浏览器主页

关于Android的事件传递,应该从两个方面来说,5.0以前,5.0以后
在5.0以前:
我们一般关注三个方法

  • dispatchTouchEvent
  • onInterceptTouchEvent
  • onTouchEvent
    ViewGroup是继承自View的,而onInterceptTouchEvent方法是ViewGroup独有的,可以对事件进行拦截,拦截之后事件是无法传递到子view上的(包括本次事件序列的move、up等)
    一个点击事件发生会依次经过activity的dispatchTouchEvent ,ViewGroup的dispatchTouchEvent,ViewGroup的onInterceptTouchEvent。
    下面分两种情况:
  • onInterceptTouchEvent返回true
    表示ViewGroup对本事件进行拦截,会直接调用ViewGroup的onTouchEvent,如果onTouchEvent不处理该事件,会调用Activity的onTouchEvent,后续的事件不会再传递到ViewGroup了。
  • onInterceptTouchEvent返回false
    事件会传递到子view上,会调用View的dispatchTouchEvent,View的onTouchEvent,如果子View的onTouchEvent没有处理事件,事件会经过ViewGroup的onTouchEvent,如果ViewGroup的onTouchEvent没有处理,会调用Activity的onTouchEvent,后续的事件不会再传递到ViewGroup了。
  • onInterceptTouchEvent先返回false后返回true,则子view无法接收后续的事件(但会接收到一个cancel事件,cancel事件会重置mFirstTarget,这也就是后续事件不会再执行onInterceptTouchEvent的原因)

一些细节:
onInterceptTouchEvent在两种情况下会调用:

  • down事件的时候坑定会调用
  • 子view处理了down事件,后续的move也会执行到onInterceptTouchEvent

5.0以后:引入嵌套滑动
引入了嵌套滑动CoordinatorLayout之后,一般是要配合Behavior使用,在使用过程中我们需要关注两种情况:

  • 某个view监听另一个view的状态变化,例如大小、位置、显示状态等
  • 某个view监听CoordinatorLayout里的滑动状态
    对于第一种情况,我们关心的是:
    layoutDependsOn和onDependentViewChanged方法,
    对于第二种情况,我们关心的是:
    onStartNestedScroll和onNestedPreScroll方法。

先来说第一组方法:
layoutDependsOn和onDependentViewChanged的调用时机
这个主要是在CoordinatorLayout类的onAttachedToWindow()方法,内部定义了一个ViewTreeObserver,然后给这个ViewTreeObserver添加了一个监听器OnPreDrawListener,用来监听view的draw操作。然后在这个监听器中循环遍历所有的子View,一旦某个子View发生了draw操作,就循环遍历所有子view,如果发现某个view上有Behavior,就调用Behavior中的这些方法。

看下第二组方法:
写个demo来测试一下CoordinatorLayout、Behavior、子View之间事件的流程

package com.example.huozhenpeng.myapplication;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 邮箱 [email protected]
 */

public class MCoordinateLayout extends CoordinatorLayout {


    public MCoordinateLayout(Context context) {
        super(context);
    }

    public MCoordinateLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MCoordinateLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("abc","coordinatorlayout------onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.e("abc","coordinatorlayout------onTouchEvent");
        return super.onTouchEvent(ev);
    }
}

package com.example.huozhenpeng.myapplication;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 邮箱 [email protected]
 */

public class MBehavior extends CoordinatorLayout.Behavior {

    public MBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.e("abc","behavior----onInterceptTouchEvent");
        return super.onInterceptTouchEvent(parent, child, ev);
//        return true;
    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.e("abc","behavior----onTouchEvent");
        return super.onTouchEvent(parent, child, ev);
    }
}

package com.example.huozhenpeng.myapplication;

import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 邮箱 [email protected]
 */

public class MTextView extends android.support.v7.widget.AppCompatTextView {
    public MTextView(Context context) {
        super(context);
    }

    public MTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("abc","textview---------onTouchEvent");
        return super.onTouchEvent(event);
//        return true;
    }
}






   

       
   






image.png
package com.example.huozhenpeng.myapplication;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 邮箱 [email protected]
 */

public class MBehavior extends CoordinatorLayout.Behavior {

    public MBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.e("abc","behavior----onInterceptTouchEvent");
//        return super.onInterceptTouchEvent(parent, child, ev);
        return true;
    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.e("abc","behavior----onTouchEvent");
        return super.onTouchEvent(parent, child, ev);
    }
}

image.png
package com.example.huozhenpeng.myapplication;

import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

/**
 * 作者 huozhenpeng
 * 日期 2018/8/10
 * 邮箱 [email protected]
 */

public class MTextView extends android.support.v7.widget.AppCompatTextView {
    public MTextView(Context context) {
        super(context);
    }

    public MTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("abc","textview---------onTouchEvent");
//        return super.onTouchEvent(event);
        return true;
    }
}

image.png

大概总结一下:事件传递到CoordinatorLayout的时候,CoordinatorLayout会把事件交给Behavior(不管是在onInterceptTouchEvent()方法中还是在onTouchEvent()方法中),Behavior具有中断事件的能力,如果Behavior中断了事件,事件也是传递不到子view的。

我们以NestedScrollView为例来研究下第二组方法
在onTouchEvent()方法中调用startNestedScroll()

@Override
    public boolean startNestedScroll(int axes, int type) {
        return mChildHelper.startNestedScroll(axes, type);
    }

紧接着

public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    setNestedScrollingParentForType(type, p);
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
            int nestedScrollAxes, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
                    nestedScrollAxes, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
        }
        return false;
    }

其实就是去调用CoordinatorLayout 类的onStartNestedScroll方法了

 @Override
    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == View.GONE) {
                // If it's GONE, don't dispatch
                continue;
            }
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                        target, axes, type);
                handled |= accepted;
                lp.setNestedScrollAccepted(type, accepted);
            } else {
                lp.setNestedScrollAccepted(type, false);
            }
        }
        return handled;
    }

接着就去调用Behavior的onStartNestedScroll()方法了,onNestedScrollAccepted()也是这个调用流程。
然后去调用dispatchNestedScroll()方法

image.png
image.png

关于自定义Behavior时,重写Behavior中的一些方法:


image.png

在CoordinatorLayout的onStartNestedScroll方法中会根据其子view(直接的字view)的Behavior的onStartNestedScroll方法的返回值来给view的LayoutParams设置一个boolean值,后续的方法都会对该值进行判断


image.png

比如说在onNestedScroll方法中

CoordinatorLayout

看下CoordinatorLayout的onMeasure和onLayout的部分代码

@Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

  ......
final Behavior b = lp.getBehavior();
           if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                   childHeightMeasureSpec, 0)) {
               onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                       childHeightMeasureSpec, 0);
           }

..........
     }
@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int layoutDirection = ViewCompat.getLayoutDirection(this);
        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            if (child.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior behavior = lp.getBehavior();

            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
                onLayoutChild(child, layoutDirection);
            }
        }
    }

这说明如果View的Behavior重写了onMeasureChild、onLayoutChild方法,那么会优先调用Behavior中的方法,而不会去调用CoordinatorLayout中的方法

有个疑问是假如CoordinatorLayout的onNestedPreScroll在将事件分发给有Behavior的子View时,是可以同时将一个事件分发给所有实现了Behavior的子View吗?看下代码就理解了

@Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int  type) {
        int xConsumed = 0;
        int yConsumed = 0;
        boolean accepted = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted(type)) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                mTempIntPair[0] = mTempIntPair[1] = 0;
                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair, type);

                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
                        : Math.min(xConsumed, mTempIntPair[0]);
                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
                        : Math.min(yConsumed, mTempIntPair[1]);

                accepted = true;
            }
        }

        consumed[0] = xConsumed;
        consumed[1] = yConsumed;

        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

你可能感兴趣的:(事件传递机制面试问什么?)