CoordinatorLayout自定义Behavior的简单总结

前言

CoordinatorLayout 是 Google 在 Design Support 包中提供的一个十分强大的布局视图,它可以说是Design库这种最重要的控件,虽说本质上类似于 FrameLayout,但是它允许开发者通过指定 Behavior 从而实现各种复杂的 UI 效果

CoordinatorLayout与Behavior介绍

官方对CoordinatorLayout的描述:

CoordinatorLayout is a super-powered FrameLayout.

CoordinatorLayout is intended for two primary use cases:

As a top-level application decor or chrome layout
As a container for a specific interaction with one or more child views

官方对Behavior的描述:

Interaction behavior plugin for child views of CoordinatorLayout.

意思就是CoordinatorLayout是用来协调其子view们之间动作的一个父view,而Behavior就是用来给CoordinatorLayout的子view们实现交互的

注意:
使用Behavior需要注意的是 CoordinatorLayout 子视图的层级关系,如果想在子视图中使用 Behavior 进行控制,那么这个子视图一定是 CoordinatorLayout 的直接孩子,间接子视图是不具有 behavior 属性的,原因当然也很简单,behavior 是 LayoutParams 的一个属性,而间接子视图的 LayoutParams 根本不是 CoordinatorLayout 类型的

Behavior的一些重要回调方法介绍

一般自定义Behavior需要重写的几个重要方法:

  • layoutDependsOn
 /**
     * 表示是否给应用了Behavior 的View 指定一个依赖的布局,通常,当依赖的View 布局发生变化时
     * 不管被依赖View 的顺序怎样,被依赖的View也会重新布局
     * @param parent CoordinatorLayout对象
     * @param child 绑定behavior的View
     * @param dependency   依赖的view
     * @return 如果child 是依赖的指定的View 返回true,否则返回false
     */
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        if (dependency != null && dependency.getId() == R.id.scrolling_header) {        
        //do something
        return true;
    }
    return false;
    }

负责查询该 Behavior 是否依赖于某个视图,如果是则返回 true,那么之后其他操作就会围绕这个依赖视图而进行了

  • onDependentViewChanged
    /**
     * 当被依赖的View 状态(如:位置、大小)发生变化时,这个方法被调用
     * @param parent CoordinatorLayout对象
     * @param child 绑定behavior的View
     * @param dependency 依赖的view
     * @return 当dependency发生改变,同样child也需要发生改变,这个时候需要返回true
     */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        final float progress = Math.abs(dependency.getTranslationY() / (dependency.getHeight()));
        //do something
        return true;
    }

这段就是根据依赖视图进行调整的方法,当依赖视图发生变化时,这个方法就会被调用。在这里可以通过当前依赖视图的位移,计算出一个位移因数(取值 0 - 1),用该位移因数来做一些视图的位移,缩放等等的操作

  • onLayoutChild
//可以重写这个方法对子View 进行重新布局
    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
    if (lp.height == CoordinatorLayout.LayoutParams.MATCH_PARENT) {
        child.layout(0, 0, parent.getWidth(), (int) (parent.getHeight() - getDependencyViewHeight()));
        return true;
    }
    }

负责对被 Behavior 控制的视图进行布局,就是将 ViewGroup 的 onLayout 针对该视图的部分抽出来给 Behavior 处理。我们判断一下如果目标视图高度要填充父视图,我们就自己将其高度减去 Header View 折叠后的高度。为什么要这么做呢?因为 CoodinatorLayout 就是一个 FrameLayout,不像 LinearLayout 一样能自动分配各个 View 的高度,因此我们要自己实现大小控制

  • onStartNestedScroll
    /**
     *  当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 (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

用户按下手指时触发,询问 NSP 是否要处理这次滑动操作,如果返回 true 则表示“我要处理这次滑动”,如果返回 false 则表示“我不 care 你的滑动,你想咋滑就咋滑”,后面的一系列回调函数就不会被调用了。它有一个关键的参数,就是滑动方向,表明了用户是垂直滑动还是水平滑动,本例子只需考虑垂直滑动,因此判断滑动方向为垂直时就处理这次滑动,否则就不 care

  • onNestedPreScroll
    /**
     * 嵌套滚动发生之前被调用
     * 一般可以用来处理向上滑动时的逻辑
     * @param coordinatorLayout
     * @param child
     * @param target
     * @param dx  用户水平方向的滚动距离
     * @param dy  用户竖直方向的滚动距离
     * @param consumed 消费距离,[0]为水平距离,[1]为垂直距离
     */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
    }

在nested scroll child 消费掉自己的滚动距离之前,嵌套滚动每次被nested scroll child更新都会调用onNestedPreScroll。注意有个重要的参数consumed,可以修改这个数组表示你消费了多少距离。假设用户滑动了100px,child 做了90px 的位移,你需要把 consumed[1]的值改成90,这样coordinatorLayout就能知道只处理剩下的10px的滚动

  • onNestedScroll
    /**
     * 进行嵌套滚动时被调用
     * 一般可以用来处理向下滑动时的逻辑
     * @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);
    }

上一个方法结束后,NSC 处理剩下的距离。比如上面还剩 10px,这里 NSC 滚动 2px 后发现已经到头了,于是 NSC 结束其滚动,调用该方法,并将 NSC 处理剩下的像素数作为参数(dxUnconsumed、dyUnconsumed)传过来,这里传过来的就是 8px。参数中还会有 NSC 处理过的像素数(dxConsumed、dyConsumed)。这个方法主要处理一些越界后的滚动

  • onStopNestedScroll
    /**
     *  嵌套滚动结束时被调用,这是一个清除滚动状态等的好时机。
     * @param coordinatorLayout
     * @param child
     * @param target
     */
    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        super.onStopNestedScroll(coordinatorLayout, child, target);
    }

一切滚动停止后调用,如果不会发生惯性滚动,fling 相关方法不会调用,直接执行到这里。这里我们做一些清理工作,当然有时也要处理中间态问题

  • onNestedScrollAccepted
    /**
     * 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);
    }

当 NSP 接受要处理本次滑动后,这个回调被调用,我们可以做一些准备工作,比如让之前的滑动动画结束

  • onNestedPreFling
    /**
     * 用户松开手指并且会发生惯性动作之前调用,参数提供了速度信息,可以根据这些速度信息
     * 决定最终状态,比如滚动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);
    }

用户松开手指并且会发生惯性滚动之前调用。参数提供了速度信息,我们这里可以根据速度,决定最终的状态是展开还是折叠,并且启动滑动动画。通过返回值我们可以通知 NSC 是否自己还要进行滑动滚动,一般情况如果面板处于中间态,我们就不让 NSC 接着滚了,因为我们还要用动画把面板完全展开或者完全折叠

参考:
http://www.jianshu.com/p/82d18b0d18f4
http://www.jianshu.com/p/7f50faa65622

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值