安卓behavior详解3--自定义behavior详解

来源:互联网 发布:指定dns解析域名 编辑:程序博客网 时间:2024/05/21 06:19

一.前言

官方定义:
A Behavior implements one or more interactions that a user can take on a child view. These interactions may include drags, swipes, flings, or any other gestures.
中文:
一个Behavior实现了一个或多个用户可以采取的交互视图。这些相互作用可能包括拖动,滑动,快速滑动,或任何其他的手势。

二.类似观察者概念

其实Behavior就是一个应用于View的观察者模式,一个View跟随者另一个View的变化而变化,或者说一个View监听另一个View。

在Behavior中,被观察的View 也就是事件源被称为denpendcy,而观察View,则被称为child

Behavior内部的方法当在CoordinatorLayout上发生触摸事件的时候,CoordinatorLayout会处理触摸事件,并回调Behavior中的部分方法,我们可以通过处理这些回调方法来实现需求

三.Behavior中的几个重要方法

Behavior 是一个顶层抽象类,其他的一些具体行为的Behavior 都是继承自这个类。它提供了几个重要的方法:
layoutDependsOn
onDependentViewChanged
onStartNestedScroll
onNestedPreScroll
onNestedScroll
onStopNestedScroll
onNestedScrollAccepted
onNestedPreFling
onLayoutChild

    /**     * 表示是否给应用了Behavior 的View 指定一个依赖的布局,通常,当依赖的View 布局发生变化时,不管被被依赖View 的顺序怎样,被依赖的View也会重新布局     * @param parent     * @param child 绑定behavior 的View     * @param dependency 依赖的view     * @return 如果child 是依赖的指定的View 返回true,否则返回false     */    @Override    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {        return super.layoutDependsOn(parent, child, dependency);    }    /**     * 当被依赖的View 状态(位置、大小等)发生变化时,这个方法被调用     * @param parent     * @param child     * @param dependency     * @return     */    @Override    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {        return super.onDependentViewChanged(parent, child, dependency);    }    /**     *  当coordinatorLayout 的子View试图开始嵌套滑动的时候被调用。     * 当返回值为true的时候表明coordinatorLayout 充当nested scroll parent 处理这次滑动,需要注意的是只有当返回值为true的时候,Behavior 才能收到后面的一些nested scroll 事件回调(如:onNestedPreScroll、onNestedScroll等)这个方法有个重要的参数  nestedScrollAxes,表明处理的滑动的方向。     * @param coordinatorLayout 和Behavior 绑定的View的父CoordinatorLayout     * @param child  和Behavior 绑定的View     * @param directTargetChild     * @param target     * @param nestedScrollAxes 嵌套滑动 应用的滑动方向,看 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},        {@link ViewCompat#SCROLL_AXIS_VERTICAL}     * @return     */    @Override    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {        return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);    }    /**     * 嵌套滚动发生之前被调用       在nested scroll child 消费掉自己的滚动距离之前,嵌套滚动每次被nested scroll child       更新都会调用onNestedPreScroll。注意有个重要的参数consumed,可以修改这个数       组表示你消费了多少距离。假设用户滑动了100px,child 做了90px 的位移,你需要       把 consumed[1]的值改成90,     * 这样coordinatorLayout就能知道只处理剩下的10px的滚动。     * @param coordinatorLayout     * @param child     * @param target     * @param dx  用户水平方向的滚动距离     * @param dy  用户竖直方向的滚动距离     * @param consumed     */    @Override    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);    }    /**     * 进行嵌套滚动时被调用     * @param coordinatorLayout     * @param child     * @param target     * @param dxConsumed target 已经消费的x方向的距离     * @param dyConsumed target 已经消费的y方向的距离     * @param dxUnconsumed x 方向剩下的滚动距离     * @param dyUnconsumed y 方向剩下的滚动距离     */    @Override    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);    }    /**     * 嵌套滚动结束时被调用,这是一个清除滚动状态等的好时机。     * @param coordinatorLayout     * @param child     * @param target     */    @Override    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {        super.onStopNestedScroll(coordinatorLayout, child, target);    }    /**     * onStartNestedScroll返回true才会触发这个方法,接受滚动处理后回调,可以在这个      方法里做一些准备工作,如一些状态的重置等。     * @param coordinatorLayout     * @param child     * @param directTargetChild     * @param target     * @param nestedScrollAxes     */    @Override    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {        super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);    }    /**     * 用户松开手指并且会发生惯性动作之前调用,参数提供了速度信息,可以根据这些    速度信息决定最终状态,比如滚动Header,是让Header处于展开状态还是折叠状       态。返回true 表示消费了fling.     * @param coordinatorLayout     * @param child     * @param target     * @param velocityX  x方向的速度     * @param velocityY  y方向的速度     * @return     */    @Override    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);    }/* * 可以重写这个方法对子View 进行重新布局 * @param coordinatorLayout     * @param child * @param layoutDirection 布局的方向     */@Override    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {        return super.onLayoutChild(parent, child, layoutDirection);    }

四.嵌套滚动原理和CoordinatorLayout

这里写图片描述

NestedScrolling提供了一套父View和子View滑动交互机制。要完成这样的交互,父View需要实现NestedScrollingParent接口,而子View需要实现NestedScrollingChild接口,系统提供的NestedScrollView控件就实现了这两个接口,这两个接口虽然有很多抽象方法,但均有NestedScrolling[Parent,Children]Helper辅助类来帮助处理大部分逻辑

这里写图片描述

NestedScroll的机制的简版是这样的,当子View在处理滑动事件之前,先告诉自己的父View是否需要先处理这次滑动事件,父View处理完之后,告诉子View它处理的多少滑动距离,剩下的还是交给子View自己来处理

CoordinatorLayout的事件传递

CoordinatorLayout并不会直接处理触摸事件,而是尽可能地先交由子View的Behavior来处理,它的onInterceptTouchEvent和onTouchEvent两个方法最终都是调用performIntercept方法,用来分发不同的事件类型分发给对应的子View的Behavior处理

//处理拦截或者自己的触摸事件private boolean performIntercept(MotionEvent ev, final int type) {    boolean intercepted = false;    boolean newBlock = false;    MotionEvent cancelEvent = null;    final int action = MotionEventCompat.getActionMasked(ev);    final List<View> topmostChildList = mTempList1;    getTopSortedChildren(topmostChildList); //在5.0以上,按照z属性来排序,以下,则是按照添加顺序或者自定义的绘制顺序来排列    // Let topmost child views inspect first    final int childCount = topmostChildList.size();    for (int i = 0; i < childCount; i++) {        final View child = topmostChildList.get(i);        final LayoutParams lp = (LayoutParams) child.getLayoutParams();        final Behavior b = lp.getBehavior();        // 如果有一个behavior对事件进行了拦截,就发送Cancel事件给后续的所有Behavior。假设之前还没有Intercept发生,那么所有的事件都平等地对所有含有behavior的view进行分发,现在intercept忽然出现,那么相应的我们就要对除了Intercept的view发出Cancel        if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {            // Cancel all behaviors beneath the one that intercepted.            // If the event is "down" then we don't have anything to cancel yet.            if (b != null) {                if (cancelEvent == null) {                    final long now = SystemClock.uptimeMillis();                    cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);                }                switch (type) {                    case TYPE_ON_INTERCEPT:                        b.onInterceptTouchEvent(this, child, cancelEvent);                        break;                    case TYPE_ON_TOUCH:                        b.onTouchEvent(this, child, cancelEvent);                        break;                }            }            continue;        }        if (!intercepted && b != null) {            switch (type) {                case TYPE_ON_INTERCEPT:                    intercepted = b.onInterceptTouchEvent(this, child, ev);                    break;                case TYPE_ON_TOUCH:                    intercepted = b.onTouchEvent(this, child, ev);                      break;            }            if (intercepted) {                mBehaviorTouchView = child; //记录当前需要处理事件的View            }        }        // Don't keep going if we're not allowing interaction below this.        // Setting newBlock will make sure we cancel the rest of the behaviors.        final boolean wasBlocking = lp.didBlockInteraction();        final boolean isBlocking = lp.isBlockingInteractionBelow(this, child); //behaviors是否拦截事件        newBlock = isBlocking && !wasBlocking;        if (isBlocking && !newBlock) {            // Stop here since we don't have anything more to cancel - we already did            // when the behavior first started blocking things below this point.            break;        }    }    topmostChildList.clear();    return intercepted;}
原创粉丝点击