美文网首页Android开发Material Design
MaterialDesign系列文章(五)Behavior的使用

MaterialDesign系列文章(五)Behavior的使用

作者: 笔墨Android | 来源:发表于2018-05-08 00:46 被阅读51次

    本来今天早上起来就想写这篇文章的,但是看着心爱的骑士队正在打比赛,想着詹皇的2:0。。。想着猛龙的东部第一,今天真的是给猛龙打成“朦胧”了。。。猛龙的球迷不会打我吧!!!哈哈

    詹皇镇楼

    昨天有小伙伴说要我说一下Behavior的使用,对于这个东西我也不是很了解,但是不了解可以学吗!!!其实作为程序员,我们在日常开发中会接触到很多新东西,不是我们每一个都要去了解的,我们也不会有那么多的精力。但是为什么有的人可以会的那么多呢?其实有的时候我总在想这个问题,后来我发现一件很有意思的事情,他们也不见得什么都懂,但是以往的经历让他们知道怎么去接触一个新的东西,怎么去快速上手这个东西。其实我们应该培养的是解决问题的能力,而不是什么都会。。。好了,闲话就扯到这里吧!下面开始今天的内容。其实我挺喜欢詹皇的!哈哈。。。

    本文知识点:

    • Behavior是什么东东:
    • 已有的Behavior有哪些:
      • BottomSheetBehavior的使用
      • SwipeDismissBehavior的使用
    • Behavior里面回调的说明
    • 几个常见的Behavior的案例:

    1. Behavior是什么东东

    关于Behavior的描述是这样的

    Interaction behavior plugin for child views of CoordinatorLayout.
    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.

    简单的翻译一下:CoordinatorLayout中子View的交互行为,可以在CoordinatorLayout的子类中实现一个或多个交互,这些交互可能是拖动,滑动,闪动或任何其他手势。其实就是实现CoordinatorLayout内部控件的交互行为,可以在非侵入的方式实现相应的交互!他能做什么呢?看了后面就知道了!!!哈哈。。。

    image

    2. 已有的Behavior有哪些:

    关于这个问题,我去google找了找,下面这张图说明一切:

    image

    但其实,开发中常用的就BottomSheetBehavior、SwipeDismissBehavior剩下的就是自定义了。

    2.1 BottomSheetBehavior的使用

    BottomSheetBehavior主要是实现从底部弹出内容的Behavior。其实这个里面包含很多内容,像BottomSheet、BottomSheetDialog、BottomSheetDialogFragment我们这里一个一个说明一下:

    2.1.1 BottomSheet的使用

    这个一般是使用在相应的布局中的!为什么这么说呢,因为它可以直接在布局中使用,就酱紫啊!!!先介绍一下里面比较重要的概念,否则我怕你吓啥么不知道啥么啥(原谅我的一口东北话)!

    效果就是酱紫了
    • app:layout_behavior="@string/bottom_sheet_behavior" 最重要的一句,没有它都是耍流氓!
    • app:behavior_peekHeight="0dp" 可见的部分高度(我发现如果不设置这个东西,底部的内容会一直在上面不会相应事件,所以如果你不想看见它就设置成0否则随意)
    • app:behavior_hideable="true" 是否能通过下滑手势收起
    • static <V extends View> BottomSheetBehavior<V> from(V view) 获取相应的BottomSheetBehavior对象
    • setBottomSheetCallback(BottomSheetCallback callback) 相应的监听
      • onStateChanged(@NonNull View bottomSheet, int newState) 状态改变的回调
      • onSlide(@NonNull View bottomSheet, float slideOffset) 滑动的时候调用
    • getState() 获取相应的状态
    • setState(final @State int state) 设置相应的状态
      • STATE_COLLAPSED: 默认的折叠状态
      • STATE_DRAGGING : 过渡状态
      • STATE_SETTLING: 视图从脱离手指自由滑动到最终停下的这一小段时间
      • STATE_EXPANDED: bottom sheet 处于完全展开的状态
      • STATE_HIDDEN : 默认无此状态(可通过app:behavior_hideable 启用此状态),启用后用户将能通过向下滑动完全隐藏

    根布局是CoordinatorLayout,这个是重点啊!!!

    这里注意几点问题:

    • 获取BottomSheetBehavior对象的时候,使用的是设置有app:layout_behavior="@string/bottom_sheet_behavior"的布局
    • app:behavior_peekHeight="0dp"一定要设置,否则你会在界面上一直看到它,并无法响应你的手势

    xml中的代码

    <?xml version="1.0" encoding="utf-8"?>
    <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:context="com.jinlong.newmaterialdesign.behavior.BehaviorActivity">
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="bottomSheet"
            android:text="展示bottomSheet" />
    
        <LinearLayout
            android:id="@+id/ll_bottom"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:behavior_peekHeight="50dp"
            app:layout_behavior="@string/bottom_sheet_behavior">
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="#f25b41"
                android:gravity="center"
                android:text="底部还有内容啊!!!" />
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="#009988"
                android:gravity="center"
                android:text="标签1" />
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="#002288"
                android:gravity="center"
                android:text="标签2" />
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="#009922"
                android:gravity="center"
                android:text="标签3" />
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="#00aa88"
                android:gravity="center"
                android:text="标签4" />
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="#999988"
                android:gravity="center"
                android:text="标签5" />
        </LinearLayout>
    </android.support.design.widget.CoordinatorLayout>
    

    主页面的逻辑

            BottomSheetBehavior<LinearLayout> bottomSheetBehavior = BottomSheetBehavior.from(mLlBottomSheet);
            if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
                //展开状态,隐藏
                bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
            } else {
                //其他的状态展开
                bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            }
    

    2.1.2 BottomSheetDialog的使用

    其实这个东西的使用和对话框的使用基本上是一样的。你setContentView()进去一个布局,然后调用show()方法展示一下就可以了,但是这里有一个特别需要注意的地方,如果你在对话框中设置的布局超过整个屏幕的话(这里不是说你设置了match就是全屏了,是有效内容。这里建议你试试就知道了),整个内容不会铺满全屏,顶部会留出一段空间,和peek的效果类似,这里注意一下就可以了!其他的使用和对话框的使用一样,这里直接贴一下主要代码!!!

        BottomSheetDialog sheetDialog = new BottomSheetDialog(this);        sheetDialog.setContentView(R.layout.sheet_dialog);
        sheetDialog.show();
    

    2.1.3 BottomSheetDialogFragment的使用

    其实这个和写一个Fragment是一样的,但是也存在和上面弹出对话框的那种问题,就是当你布局过大的情况下会留出一段空间。

    这里主要说明两点问题:

    • 使用这个获取相应的BottomSheetBehaviorBottomSheetBehavior.from((View) view.getParent());
    • 使用show(getSupportFragmentManager(), "dialog");显示。

    2.2 SwipeDismissBehavior的使用

    这个是滑动消失和滑动关闭,很多情况下都是和5.0新出的Snackbar。一个和Toast类似的东西,因为不是本文重点,所以关于Snackbar就不展开说了!其实除了Snackbar使用到这个,基本上没有那个APP想把自己的页面划没了吧!!!其实用法还是很简单的,主要创建一个对象,设置一些像一个的参数就可以了!直接上代码:

            TextView tvTitle = findViewById(R.id.tv_title);
            SwipeDismissBehavior<View> mSwipe = new SwipeDismissBehavior();
            /*
             * SWIPE_DIRECTION_START_TO_END 只能从左向右滑动
             * SWIPE_DIRECTION_END_TO_START 只能从右向左滑动
             * SWIPE_DIRECTION_ANY 左右滑动都可以
             */
            mSwipe.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_ANY);
    
            mSwipe.setListener(new SwipeDismissBehavior.OnDismissListener() {
                @Override
                public void onDismiss(View view) {
                    //View消失的回调
                }
    
                @Override
                public void onDragStateChanged(int state) {
                    /*
                     * STATE_IDLE 空闲状态 
                     * STATE_DRAGGING 滑动中
                     * STATE_SETTLING 消失
                     */
                }
            });
    

    注释已经很详细了,这里注意一点啊,如果设置了滑动删除功能,这个页面就存在滑动删除的功能了,是页面存在这个功能,里门的大多数控件都能存在滑动删除功能,但是我尝试了,AppBarLayout等一些相应的控件不能,估计是设置了behavior的控件不能滑动删除,其他的都可以,但是这个只是我的猜测,没有验证!!!

    3. Behavior里面回调的说明:

    这里先明确一个概念,behavior的嵌套滚动都是依照一个相应的参考物,所以在自定义的时候一定要区分哪个是依照的View哪个是被观察的View,只有区分了这些才能更好的理解下面的内容,下面出现的所有child都是被观察的View,也就是xml中定义behavior的View。

    • layoutDependsOn(CoordinatorLayout parent, View child, View dependency) 表示是否给应用了Behavior 的View 指定一个依赖的布局

      • 参数1:coordinatorlayout对象
      • 参数2:child 被观察的View
      • 参数3:依赖变化的View(被观察的View)
    • onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) 当依赖的View发生变化的时候hi掉的方法

    • onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) 当用户手指按下的时候,你是否要处理这次操作。当你确定要处理这次操作的时候,返回true;如果返回false的时候,就不会去响应后面的回调事件了。你想怎么滑就怎么话,我都不做处理。这里的(axes)滚动方向很重要,可以通过此参数判断滚动方向!

      • 参数3:直接目标,相当于能滑动的控件
      • 参数4:观察的View
      • 参数5:这个可以简单理解为滚动方向
        • ViewCompat#SCROLL_AXIS_HORIZONTAL 水平方向
        • ViewCompat#SCROLL_AXIS_VERTICAL 竖直方向
      • 参数6:这个参数是之后有的,如果你输入的类型不是TYPE_TOUCH那么就不会相应这个滚动
    • onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type) 当onStartNestedScroll准备处理这次滑动的时候(返回true的时候),回调这个方法。可以在这个方法中做一些响应的准备工作!

    • onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type) 当滚动开始执行的时候回调这个方法。

      • 参数4/参数5:用户x/y轴滚动的距离(注意这里是每一次都回调的啊!!!)
      • 参数6:处理滚动的距离的参数,内部维护着输出距离,假设用户滑动了100px,child 做了90px的位移,你需要把consumed[1]的值改成90,这样coordinatorLayout就能知道只处理剩下的10px的滚动。其中consumed[0]代表x轴、consumed[1]代表y轴。可能你不理解这个问题,换个形象点的比喻,比如你开发某一个功能,但是你只会其中的90%那么怎么办呢?不能就不管了。好你找到了你的同事或者老大,让他去完成剩下的10%。这样问题就完美的解决了,是一个概念的!
    • onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) 上面这个方法结束的时候,coordinatorLayout处理剩下的距离,比如还剩10px。但是coordinatorLayout发现滚动2px的时候就已经到头了。那么结束其滚动,调用该方法,并将coordinatorLayout处理剩下的像素数作为参(dxUnconsumed、dyUnconsumed)传过来,这里传过来的就是 8px。参数中还会有coordinatorLayout处理过的像素数(dxConsumed、dyConsumed)。老大开始处理剩下的距离了!这个方法主要处理一些越界后的滚动。还是不懂对吧!还拿你们老大做比喻:比如上面还剩 10%的工作,这时老大处理了2%后发现已经可以上线了,于是老大结束了工作,并将处理剩下的内容(dxUnconsumed、dyUnconsumed)纪录下来,告诉你。老大处理了的内容(dxConsumed、dyConsumed)也告诉了你。

      • 参数4/参数5:当没有滚动到顶部或者底部的时候,x/y轴的滚动距离
      • 参数6/参数7:当滚动到顶部或者底部的时候,x/y轴的滚动距离
    if (dyConsumed > 0 && dyUnconsumed == 0) {
        System.out.println("上滑中。。。");
    }
    if (dyConsumed == 0 && dyUnconsumed > 0) {
        System.out.println("到边界了还在上滑。。。");
    }
    if (dyConsumed < 0 && dyUnconsumed == 0) {
        System.out.println("下滑中。。。");
    }
    if (dyConsumed == 0 && dyUnconsumed < 0) {
        System.out.println("到边界了,还在下滑。。。");
    }
    
    • onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY) 当手指松开发生惯性动作之前调用,这里提供了响应的速度,你可以根据速度判断是否需要进行折叠等一系列的操作,你要确定响应这个方法的话,返回true。

      • 参数4/参数5:代表相应的速度
    • onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) 停止滚动的时候回调的方法。当你不去响应Fling的时候会直接回调这个方法。在这里可以做一些清理工作。或者其他的内容。。。

    • onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) 确定子View位置的方法,这个方法可以重新定义子View的位置(这里明确是设置behavior的那个View哦),例如下面这样

      • ViewCompat#LAYOUT_DIRECTION_LTR 视图方向从左到右
      • ViewCompat#LAYOUT_DIRECTION_RTL 视图方向从优到左

    基本上能用到的API就这么多,但是这里面的内容很多,先好好理解一下,我其实都不怎么理解,没事不理解没事,后面几个例子就ok了!

    我只是一个吃瓜群众

    4. 几个常见的Behavior的案例:

    这里说明一下自定义Behavior分为两种类型,一种是依赖相应的View变化而变化、一种是依赖滚动变化。

    4.1 依赖于某个View的变化而变化的Behavior

    这个最经典的案例就是底栏跟随AppBarLayout移动给移动,其实代码很简单,只要算出AppBarLayout的移动距离,动态的设置给相应的依赖控件就可以了。一波代码走起!!!先上一张效果图。

    效果图
    public class TwoBehavior extends CoordinatorLayout.Behavior<View> {
    
        private String TAG = TwoBehavior.class.getSimpleName();
    
        //这个千万要写,否则会出异常
        public TwoBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
            //依赖于AppBarLayout的
            return dependency instanceof AppBarLayout;
        }
    
        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
            //计算出AppBarLayout移动的距离
            float top = Math.abs(dependency.getTop());
            Log.e(TAG, "AppBarLayout移动的距离" + top);
            child.setTranslationY(top);
            return true;
        }
    }
    

    然后代码里使用:app:layout_behavior="全路径"就可以实现了,因为CoordinatorLayout里面的内容怕你不知道,所以这里我还是贴一下相应的清单文件,要不你又该把实现不了的锅让我背了,这个锅我不背。。。

    <?xml version="1.0" encoding="utf-8"?>
    <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:context="com.jinlong.newmaterialdesign.behavior.TwoBehaviorActivity">
    
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <android.support.v7.widget.Toolbar
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="@color/colorPrimary"
                app:layout_scrollFlags="scroll|enterAlways"
                app:title="底部联动的Behavior"
                app:titleTextColor="@android:color/white" />
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:background="#009988"
                    android:gravity="center"
                    android:text="标签1" />
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:background="#002288"
                    android:gravity="center"
                    android:text="标签2" />
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:background="#009922"
                    android:gravity="center"
                    android:text="标签3" />
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:background="#00aa88"
                    android:gravity="center"
                    android:text="标签4" />
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:background="#999988"
                    android:gravity="center"
                    android:text="标签5" />
            </LinearLayout>
        </android.support.v4.widget.NestedScrollView>
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:layout_gravity="bottom"
            android:background="@color/colorPrimary"
            android:gravity="center"
            android:text="这个一个底栏"
            android:textColor="@android:color/white"
            app:layout_behavior="com.jinlong.newmaterialdesign.behavior.TwoBehavior" />
    </android.support.design.widget.CoordinatorLayout>
    

    这里面处理View的变化才是难点,反正我是这么认为的,不懂的同学可以补充一下相应的知识,什么View变化,获取相应位置的方法等些许内容。网上还是挺多的,依赖的View基本上都是这么实现的。都是实现这两个方法的,玩转了就好了,多写写自然就熟了。

    4.2 依赖于滚动变化而变化的Behavior

    这个就比较难了,因为涉及到相应的滚动计算什么的,只有多写多看才能熟,先来一个简单的例子吧。剩下的就靠大家多多练习了!!!

    4.2.1 首先是对位置的确定

            @Override
        public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
            //设置了behavior的布局
            CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            if(params!=null && params.height == CoordinatorLayout.LayoutParams.MATCH_PARENT){
                child.layout(0,0,parent.getWidth(),parent.getHeight());
                child.setTranslationY(getHeaderHeight());
                return true;
            }
    
            return super.onLayoutChild(parent, child, layoutDirection);
        }
        
        /**
         * 这里是Header的高度,可以设置成任何你想的高度
         */
        public int getHeaderHeight(){
        //      当你设置到相应的清单文件的时候,你就这么弄
        //      return Context.getResources().getDimensionPixelOffset(R.dimen.header_height);
            return 500;
        }
    

    这里的位置确定主要用到了一个View.setTranslationY()的方法,这个方法我查了查,和View.getTop()的方法有些类似,是相对于父控件左上角的偏移量。那么就很好理解了,设置Behavior的View便宜到指定的位置下面,因为这里设置了一个相应的图片高度,所以这里就是在图片的下面。

    4.2.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 || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
        }
    
    • 处理滚动距离等一系列的计算

    首先点你手指开始滑动的时候,会执行下面这个方法。

        @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);
            // 在这个方法里面只处理向上滑动
            if(dy < 0){
                return;
            }
            //计算每次移动的距离
            float transY =  child.getTranslationY() - dy;
            if(transY > 0){
                child.setTranslationY(transY);
                consumed[1]= dy;
            }
        }
    

    说明一下:dy<0 代表的是向下滑动。child.getTranslationY()获取的是设置behavior的View距离CoordinatorLayout顶部的偏移量。dy代表的是每一次移动的距离。所以transY计算的就是每一次移动后应该距离顶部的距离设置给相应的View。这里consumed[1]= dy表示的是你处理的距离(后面会用到的)。所以就不难理解了,当你每次向上滑动的时候,会计算相应的数值,设置给child,使它逐渐的向上移动,当到达顶部之后就不进行改变相应的竖直了。就酱紫了...

    每次上面的方法执行完成的时候会调用下面这个方法

        @Override
        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
            // 在这个方法里只处理向下滑动
            if(dyUnconsumed >0){
                return;
            }
    
            float transY = child.getTranslationY() - dyUnconsumed;
            Log.i(TAG,"------>transY:"+transY+"****** child.getTranslationY():"+child.getTranslationY()+"--->dyUnconsumed"+dyUnconsumed);
            if(transY > 0 && transY < getHeaderHeight()){
                child.setTranslationY(transY);
            }
        }
    

    还记的我上面说的那个老大的问题吧?当你consumed[1]= dy的时候,就会传递过来相应的参数dxUnconsumed/dyUnconsumed代表处理剩下的参数,但是这里要注意一点。针对于上面这个例子当你上一个方法return的时候,那么这个dxUnconsumed/dyUnconsumed就会有值的!那就不难理解了,当你向下滑动的时候会之间改变child的位置,直到child全部显示出来为止!

    4.2.3 全部的内容是酱紫的

    public class OneBehavior extends CoordinatorLayout.Behavior {
    
        private String TAG = OneBehavior.class.getSimpleName();
    
        public OneBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) {
            super.onStopNestedScroll(coordinatorLayout, child, target, 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 || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
        }
    
        @Override
        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
            // 在这个方法里只处理向下滑动
            if(dyUnconsumed >0){
                return;
            }
    
            float transY = child.getTranslationY() - dyUnconsumed;
            Log.i(TAG,"------>transY:"+transY+"****** child.getTranslationY():"+child.getTranslationY()+"--->dyUnconsumed"+dyUnconsumed);
            if(transY > 0 && transY < getHeaderHeight()){
                child.setTranslationY(transY);
            }
        }
    
        @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);
            // 在这个方法里面只处理向上滑动
            if(dy < 0){
                return;
            }
    
            float transY =  child.getTranslationY() - dy;
            Log.i(TAG,"transY:"+transY+"++++child.getTranslationY():"+child.getTranslationY()+"---->dy:"+dy);
            if(transY > 0){
                child.setTranslationY(transY);
                consumed[1]= dy;
            }
        }
    
        @Override
        public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
            //设置了behavior的布局
            CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            if(params!=null && params.height == CoordinatorLayout.LayoutParams.MATCH_PARENT){
                child.layout(0,0,parent.getWidth(),parent.getHeight());
                child.setTranslationY(getHeaderHeight());
                return true;
            }
    
            return super.onLayoutChild(parent, child, layoutDirection);
        }
    
        /**
         * 这里是Header的高度,可以设置成任何你想的高度
         */
        public int getHeaderHeight(){
        //        当你设置到相应的清单文件的时候,你就这么弄
        //        return Context.getResources().getDimensionPixelOffset(R.dimen.header_height);
            return 500;
        }
    }
    

    4.2.3 布局的内容

    布局的内容就简单多了。直接引入一个behavior就可以了。有一个问题注意下,因为上面面我是写死的高度,所以布局里面就是一个写死的高度了。如果你想做好适配的话,就直接从xml中获取就可以了。

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="500px"
            android:scaleType="centerCrop"
            android:src="@mipmap/heard_1" />
        <!--这是一张图片-->
    
        <android.support.v4.widget.NestedScrollView
            android:id="@+id/nested_scroll_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/white"
            app:layout_behavior="com.jinlong.newmaterialdesign.behavior.OneBehavior">
        <!--这是是全路径-->
            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="@string/large_text" />
        </android.support.v4.widget.NestedScrollView>
    </android.support.design.widget.CoordinatorLayout>
    

    基本上就这么多了,其实我在算法上面真的很渣、很渣一个很渣已经不能形容我了。我已经很努力的给你们讲明白了。不懂的可以给我留言,其实自定义Behavior真的是看见一个你练一个,慢慢的你就能开车了!其实我也是看了别人分享的东西之后总结的。有什么不好的地方还请多多指教!关于Behavior原理感兴趣的同学可以看看HelloCsld的这篇文章讲的也是挺透彻的!希望今天讲的这些能帮到你!今天就到这里吧!拜拜。。。

    代码在gitHub上的地址感兴趣的同学可以去看看!!!

    搞笑一下

    相关文章

      网友评论

        本文标题:MaterialDesign系列文章(五)Behavior的使用

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