Android_MaterialDesign

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

2017-11-08  本文已影响60人  笔墨Android

不怕跌倒,所以飞翔

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

1.Behavior介绍

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

Behavior官方提供的app:layout_behavior属性

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

这里说明了两个问题:

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.当处理滑动的时候会用到几个相应的方法

    @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;
    }
    @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


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

项目地址

上一篇下一篇

猜你喜欢

热点阅读