美文网首页Android_MaterialDesign
MaterialDesign系列文章(十七)Behavior的相

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

作者: 笔墨Android | 来源:发表于2017-11-08 15:36 被阅读60次

    不怕跌倒,所以飞翔

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

    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


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

    项目地址

    相关文章

      网友评论

        本文标题:MaterialDesign系列文章(十七)Behavior的相

        本文链接:https://www.haomeiwen.com/subject/plrsmxtx.html