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

本来今天早上起来就想写这篇文章的,但是看着心爱的骑士队正在打比赛,想着詹皇的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上的地址感兴趣的同学可以去看看!!!

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

推荐阅读更多精彩内容