MaterialDesign系列文章(十七)Behavior的相关问题

不怕跌倒,所以飞翔

参考文献:(感谢作者的开源精神)
严振杰的博客
希小风的博客

1.Behavior介绍

Behavior是Android新出的Design库里新增的布局概念。Behavior只有是CoordinatorLayout的直接子View才有意义。可以为任何View添加一个Behavior。
Behavior是一系列回调。让你有机会以非侵入的为View添加动态的依赖布局,和处理父布局(CoordinatorLayout)滑动手势的机会。如果我们想实现控件之间任意的交互效果,完全可以通过自定义 Behavior 的方式达到。

Behavior官方提供的app:layout_behavior属性

关于这个我仔细找了一下就找到了两个

  • appbar_scrolling_view_behavior 这个是appBarLayout的一个子类android.support.design.widget.AppBarLayout$ScrollingViewBehavior中提供的
  • bottom_sheet_behavior 这个是单独的一个类中实现的android.support.design.widget.BottomSheetBehavior

这里说明了两个问题:

  • 第一这个字符串设置的值应该是类的全路径名称
  • 第二这个类可以自定义(但是自定义的时候应该也指定全路径名称)

2.Behavior的自定义

这里我准备按照希小风的博客风格去逐步实

其实Behavior就是一个应用于View的观察者模式,一个View跟随着另一个View的变化而变化,或者说一个View监听另一个View,在Behavior中,被观察View也就是事件源被称为denpendcy,而观察View被成为child

这里先贴出继承继承CoordinatorLayout.Behavior<V extends View>常用的方法

 /**
     * 表示是否给应用了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 进行重新布局
    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        return super.onLayoutChild(parent, child, layoutDirection);
    }

2.1Button与TextView联动

这里主要是自定义了一个Behavior

/**
 * 作者 : 贺金龙
 * 创建时间 :  2017/11/8 11:33
 * 类描述 : 这个是第一个简单的自定义EasyBehavior
 * 类说明 : 这里的泛型应给是被观察者,也就是child
 */
public class EasyBehavior extends CoordinatorLayout.Behavior<TextView> {
   
    public EasyBehavior(Context context, AttributeSet attrs) {
        /*这里说明一下这个构造方法一定要些上,否则会报错的*/
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
        /*这里主要是说明观察者是什么类型的,如果返回true说明是观察者观察的View否则返回false也就不会产生联动了*/
        return dependency instanceof Button;
    }


    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
        /*这里主要是观察者位置改变的时候,被观察者的位置在观察者位置的基础上响应的增加了200*/
        child.setX(dependency.getX() + 200);
        child.setY(dependency.getY() + 200);
        child.setText(dependency.getX() + "," + dependency.getY());
        return true;
    }
}

注释已经写的很详细了,所以这里就不在赘述了...

2.2仿UC首页折叠的Behavior效果

这个效果可以下载一个UC去看一下,其实这个效果基本上就是顶上放一个TextView和AppBarLayout中底部ToolBar进行
联动(其实我觉得就是通过这两个都是通过计算位置进行处理的),这里我就粘一下代码了,这里一看就能懂
清单文件:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:ignore="RtlHardcoded">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:elevation="0dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:scaleType="centerCrop"
                android:src="@mipmap/ic_launcher"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.9"/>

            <FrameLayout
                android:id="@+id/frameLayout"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:layout_gravity="bottom|center_horizontal"
                android:background="@color/colorPrimaryDark"
                android:orientation="vertical"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.3">

            </FrameLayout>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        app:behavior_overlapTop="30dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <include layout="@layout/uc_behavior"/>
    </android.support.v4.widget.NestedScrollView>

    <android.support.v7.widget.Toolbar
        android:id="@+id/main.toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimaryDark"
        app:layout_anchor="@id/frameLayout"
        app:theme="@style/ThemeOverlay.AppCompat.Dark"
        app:title="这个是toolBar的标题">
    </android.support.v7.widget.Toolbar>

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/colorPrimaryDark"
        android:gravity="center"
        android:textColor="#fff"
        android:textSize="18sp"
        app:layout_behavior="com.hejin.materialdesign.behavior.ToolBarBehavior"/>

</android.support.design.widget.CoordinatorLayout>

这里注意一点啊,里面有一个属性app:layout_anchor="@id/frameLayout"这个属性代表有依附的意思,简单的说就是通过依附达到共同享用滑动事件的意思,也就是说上面的FramLayout滑动的时候ToolBar就会一起跟着滑动的,这里依附的话,会在被依附的控件的最上边
behavior文件

/**
 * 作者 : 贺金龙
 * 创建时间 :  2017/11/8 14:41
 * 类描述 : 实现ToolBar和TextView联动的Behavior
 * 类说明 :
 */
public class ToolBarBehavior extends CoordinatorLayout.Behavior<TextView> {

    private int mStartY;

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

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
        return dependency instanceof Toolbar;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
        if (mStartY == 0) {/*这里获取的是点击处对控件顶部的距离*/
            mStartY = (int) dependency.getY();
        }

        /*计算ToolBar从开始引动到最后的百分比,也就是ToolBar的当前高度比上总高度*/
        float percent = dependency.getY() / mStartY;

        /*改变child的坐标(从消失到可见)*/
        child.setY(child.getHeight() * (1 - percent) - child.getHeight());
        return true;
    }
}

2017年11月09日添加:

自定义简书的Behavior

public class BottomBehavior extends CoordinatorLayout.Behavior<View> {

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

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof AppBarLayout;
    }

    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        float translationY = Math.abs(dependency.getTop());//获取更随布局的顶部位置
        child.setTranslationY(translationY);
        return true;
    }
}

这里写了好久都没有成功,后来我发现了一个问题,toolBar在移动的时候是嵌套在AppBarLayout中的,所以你是监听不到,因此这里不能使用ToolBar而是使用的AppBarLayout

感觉要是真的像自定义一些很难的还是不行,加强学习吧


2018年03月19日补充

其实关于自定义Behavior的内容,之前自己了解的不够,今天补充一些内容

补充说明1:

首先你要理解那个是依赖的View,那个是被观察的View(我这里是这么理解的)

上面所有的child代表的是被观察的View(也就是绑定的View,说白了就是在xml布局中设置layout_behavior的那个View)

补充说明2:

这里面有一个API->ViewCompat.offsetTopAndBottom(view, offset);
这个方法的意思是,使view移动相应的位置,位置的大小取决于offset,向下为正,向上为负,这里面还有左右移动的,api和这个类似,自己找一下就可以了

补充说明3:

  1. 一般处理两个View之间的移动的时候,都会用到
    boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency)
    这个方法是处理相应的依赖关系的,也就是上面说的依赖和被观察的关系
    onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency)
    这个方法是处理相应位置的改变的一些内容的,说简单点就是当你被观察的View位置什么的发生改变就会回调这个方法.

2.当处理滑动的时候会用到几个相应的方法

  • boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type)
    这个是在你手指触碰到控件的时候调用的方法,这里面参数有必要说明一下:
    参数1:coordinatorLayout对象,这个没有什么好说的
    参数2:child这个是相应的被观察者,也就是要被移动的那个view
    参数3:directTargetChild我理解这个参数是滑动的直接子View(这个我不太确定)
    参数4:target这个是被观察的View
    参数5:代表是水平滑动还是竖直滑动的一个类型值取值包含ViewCompat#SCROLL_AXIS_HORIZONTAL\ViewCompat#SCROLL_AXIS_VERTICAL分别对应着竖直和水平滑动,
    参数6:代表一个type的值,没用到可能是下面的那个方法做区分用的吧;
    这里引用一个判断竖直滚动的例子
    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }
  • void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type)
    这个方法是滚动的时候调用的,当滚动发生的时候,这里计算出了相应的数值,还是简单的说明一下不一样的参数吧!
    参数4参数5:dx代表x/y轴的速度值
    参数6我也没弄明白是什么,我打印的时候一直是0
    这里引用一个联动的例子
    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
        int leftScrolled = target.getScrollY();//获取滚动的Y轴的距离
        child.setScrollY(leftScrolled);
    }

很好理解,就是目标滚的距离是多少就让依赖的View滚多远

boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY)
这个方法就是滚动的惯性,就是当你使用了很大力气的时候推一下,当你松手的时候他还会滚一段时间的.也是简单说一下参数
参数4和参数5代表的是松手时刻的瞬时速度
上个例子

    @Override
    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY) {
        ((NestedScrollView) child).fling((int) velocityY);
        return true;
    }

这里就直接把相应的速度进行传递就可以了!


就先些这些吧!今天有点累了,眼睛有点疼,其实我觉得这个后期多些两个就会了~~~
下面这些文章准备不累的时候好好实现一下...
http://blog.csdn.net/king1425/article/details/61923877
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0224/3991.html
http://www.jianshu.com/p/488283f74e69
http://www.jianshu.com/p/82d18b0d18f4


这一系列文章的地址,希望对大家有帮助

项目地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容