Material Design 之 Behavior的使用和自定义Behavior

Bvwzf172 4年前
   <p>前面两篇文章讲了Toolbar 和 AppbarLayout 相关的东西,还没看过的同学可以去看看。前面我们说过,CoordinatorLayout很强大,它可以协调子View的交互动作,那么CoordinatorLayout它是怎么协调子View的呢?其实核心就是Behavior。那么今天讲的就是这个很重要的东西-Behavior,在上面篇文章中,我们其实已经看到过Behavior这个东西了,在AppbarLayout 与NestedScrollView 联动的时候,我们为NestedScrollView设置了一个Behavior, 通过app:layout_behavior="@string/appbar_scrolling_view_behavior",它的值是一个类的全路径,这个Behavior 是Google已经为我们提供的,AppbarLayout的内部类,专门用于处理可滚动View(如:ScrollView、RecyclerView) 与AppbarLayout 联动的。那么这篇文章我们通过介绍Google提供的一些Behavior 的使用场景、使用方式和自定义Behavior 来熟悉和掌握 Behavior。</p>    <h3>正文</h3>    <p>1,Behavior 介绍</p>    <p>看一下官方的介绍:Interaction behavior plugin for child views of CoordinatorLayout . 作用于CoordinatorLayout的子View的交互行为插件。一个Behavior 实现了用户的一个或者多个交互行为,它们可能包括拖拽、滑动、快滑或者其他一些手势。</p>    <p>Behavior 是一个顶层抽象类,其他的一些具体行为的Behavior 都是继承自这个类。它提供了几个重要的方法:</p>    <ul>     <li>layoutDependsOn</li>     <li>onDependentViewChanged</li>     <li>onStartNestedScroll</li>     <li>onNestedPreScroll</li>     <li>onNestedScroll</li>     <li>onStopNestedScroll</li>     <li>onNestedScrollAccepted</li>     <li>onNestedPreFling</li>     <li>onStartNestedScroll</li>     <li>onLayoutChild</li>    </ul>    <p>解释一下上面几个方法和它们的调用时机:</p>    <pre>  <code class="language-java">/**       * 表示是否给应用了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);      }</code></pre>    <p>以上就是Behavior的一些重要方法,当我们要自定义一个Behavior的时候,就会去重写上面的一些方法。自定义Behavior 会放在文章最后讲。对Behavior 有了一些了解后,接下来我们看一下Google给我提供了一些特殊场景的Behavior。</p>    <p>2,BottomSheetBehavior/BottomSheetDialog 的使用</p>    <p>BottomSheetBehavior 实现的效果在我们的项目中用的比较多,它就是从底部弹出一个布局,在很多的应用中,分享功能都有这样一个交互。在以前我们通常都是用PopupWindow来搞定,前面也写了一篇文章了,关于PupupWindow的使用和封装, <a href="/misc/goto?guid=4959732420373512665" rel="nofollow,noindex">通用PopupWindow,几行代码搞定PopupWindow弹窗</a> ,有了BottomSheetBehavior 实现起来就简单一点了。请看效果图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8770f60c3b96e60af7e7ba10544dc2e7.gif"></p>    <p style="text-align:center">bottomSheetBehavior.gif</p>    <p>看看怎么用BottomSheetBehavior:</p>    <p>1,在xml布局文件中为需要从底部弹出的布局绑定BottomSheetBehavior,代码如下:</p>    <pre>  <code class="language-java"><?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="match_parent">     <TextView         android:id="@+id/btn_show_bottom_sheet"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="显示/隐藏 BottomSheet"         android:background="@android:color/darker_gray"         android:textColor="@color/black"         android:padding="10dp"         />   <FrameLayout       android:id="@+id/share_view"       app:layout_behavior="@string/bottom_sheet_behavior"       android:layout_width="match_parent"       android:layout_height="wrap_content"       android:background="@android:color/white"       android:orientation="vertical"       app:behavior_peekHeight="0dp"       >       <include layout="@layout/bottom_sheet_share_dialog"/>   </FrameLayout>    </android.support.design.widget.CoordinatorLayout></code></pre>    <p>注意上面这行代码: app:behavior_peekHeight="0dp",peekHeight 属性是设置bottomSheet 折叠时的高度,我们设置为0表示折叠的时候完全隐藏,默认情况时显示布局的高度,布局会显示在界面,所以,如果要一开始布局不显示在界面上的话,需要将peekHeight 设置为0。也可以在代码中设置, 通过sheetBehavior.setPeekHeight(0)。</p>    <p>2,在代码中获取到与布局相关联的BottomSheetBehavior,设置展开与折叠的状态就可以了,BottomSheetBehavior有5种状态:</p>    <p>1, STATE_EXPANDED展开状态,显示完整布局。</p>    <p>2,STATE_COLLAPSED折叠状态,显示peekHeigth 的高度,如果peekHeight为0,则全部隐藏,与STATE_HIDDEN效果一样。</p>    <p>3,STATE_DRAGGING拖拽时的状态</p>    <p>4,STATE_HIDDEN隐藏时的状态</p>    <p>5,STATE_SETTLING释放时的状态</p>    <p>看代码:</p>    <pre>  <code class="language-java">View shareView = findViewById(R.id.share_view);          //获取BottomSheetBehavior          final BottomSheetBehavior sheetBehavior = BottomSheetBehavior.from(shareView);            //设置折叠时的高度          //sheetBehavior.setPeekHeight(BottomSheetBehavior.PEEK_HEIGHT_AUTO);            //监听BottomSheetBehavior 状态的变化          sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {              @Override              public void onStateChanged(@NonNull View bottomSheet, int newState) {                }                @Override              public void onSlide(@NonNull View bottomSheet, float slideOffset) {                }          });          //下滑的时候是否可以隐藏          sheetBehavior.setHideable(true);          findViewById(R.id.btn_show_bottom_sheet).setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  if(sheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED){                      sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);                  }else {                      sheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);                  }                }          });</code></pre>    <p>代码很简单,重要的就是通过方法 sheetBehavior.setState()来改变状态,是显示还是隐藏。其他的几个方法都添加了注释,不用多讲。</p>    <p>2.1, BottomSheetDialog</p>    <p>上面说了BottomSheetBehavior, 接下来看一下BottomSheetDialog, 一看名字就知道,它就是一个Dialog,使用方法和Dialog 一样,它是对BootomSheetBehavior 进行了包装,从底部弹出一个Dialog。BottomSheetDialog 使用起来比BottomSheetBahvior更方便,效果更佳。看一下它的源码也非常简单,就是Dialog 显示的布局绑定了BottomSheeBehavior,源码如下:</p>    <pre>  <code class="language-java">private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {          final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),                  R.layout.design_bottom_sheet_dialog, null);          if (layoutResId != 0 && view == null) {              view = getLayoutInflater().inflate(layoutResId, coordinator, false);          }          FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);          mBehavior = BottomSheetBehavior.from(bottomSheet);          mBehavior.setBottomSheetCallback(mBottomSheetCallback);          mBehavior.setHideable(mCancelable);          if (params == null) {              bottomSheet.addView(view);          } else {              bottomSheet.addView(view, params);          }          // We treat the CoordinatorLayout as outside the dialog though it is technically inside          coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View view) {                  if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) {                      cancel();                  }              }          });          return coordinator;      }</code></pre>    <p>就这样一个方法,获取到Behavior,设置了一个监听状态的回调,设置了下滑可以隐藏。然后将Dialog 显示的布局添加到了绑定了BottomSheetBehavior 的ViewGroup 里。这个方法在setContent()方法被调用:</p>    <pre>  <code class="language-java">@Override      public void setContentView(View view) {          super.setContentView(wrapInBottomSheet(0, view, null));      }        @Override      public void setContentView(View view, ViewGroup.LayoutParams params) {          super.setContentView(wrapInBottomSheet(0, view, params));      }</code></pre>    <p>接下来看一下使用方法,非常简单,以网易云音乐的歌单和分享UI为例:</p>    <p>网易云音乐歌单UI效果 如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/931f7d7b39b43f4ec663074d0d1e5221.png"></p>    <p style="text-align:center">网易云音乐歌单.png</p>    <p>来张gif图效果更清楚:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/98c051fedfb2206bbfe35b18f3eeeba4.gif"></p>    <p style="text-align:center">网易云音乐效果图.gif</p>    <p>本文通过BottomSheetDialog 实现的效果图如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7a501da0c388175fecc8470c9621c252.gif"></p>    <p style="text-align:center">bottomSheetDialog.gif</p>    <p>歌单代码如下:</p>    <pre>  <code class="language-java">private void showBottomSheetDialog(){          BottomSheetDialog dialog = new BottomSheetDialog(this);          View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_dialog,null);            handleList(view);            dialog.setContentView(view);          dialog.setCancelable(true);          dialog.setCanceledOnTouchOutside(true);          dialog.show();      }        private void handleList(View contentView){          RecyclerView recyclerView = (RecyclerView) contentView.findViewById(R.id.recyclerView);          LinearLayoutManager manager = new LinearLayoutManager(this);          manager.setOrientation(LinearLayoutManager.VERTICAL);          recyclerView.setLayoutManager(manager);          MusicAdapter adapter = new MusicAdapter();          recyclerView.setAdapter(adapter);          adapter.setData(mockData());          adapter.notifyDataSetChanged();      }</code></pre>    <p>分享代码如下:</p>    <pre>  <code class="language-java">/**       * share Dialog       */      private void showShareDialog(){          if(mBottomSheetDialog == null){              mBottomSheetDialog = new BottomSheetDialog(this);              View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_share_dialog,null);              mBottomSheetDialog.setContentView(view);              mBottomSheetDialog.setCancelable(true);              mBottomSheetDialog.setCanceledOnTouchOutside(true);              // 解决下滑隐藏dialog 后,再次调用show 方法显示时,不能弹出Dialog              View view1 = mBottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);              final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view1);              bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {                  @Override                  public void onStateChanged(@NonNull View bottomSheet, int newState) {                      if (newState == BottomSheetBehavior.STATE_HIDDEN) {                          Log.i("BottomSheet","onStateChanged");                          mBottomSheetDialog.dismiss();                          bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);                      }                  }                    @Override                  public void onSlide(@NonNull View bottomSheet, float slideOffset) {                    }              });          }else{              mBottomSheetDialog.show();          }        }</code></pre>    <p>代码很简单,和其他普通Dialog的用法一样。 值的主意的一点是这里有个bug ,那就是当你下滑隐藏了Dialog 之后,下次直接调用show方法来显示Dialog时(没有重新new 的情况下),Dialog不能显示,原因是因为BottomSheetDialog 源码中,关闭的Dialog 是依赖BottomSheetBehavior 的,当下滑隐藏的时候,BottomSheet的状态也为STATE_HIDDEN,并且同时dismiss Dialog,下次show 的时候,是没有办法显示一个状态为STATE_HIDDEN 的布局的。 解决思路:获取到BottomSheetDialog 的布局,然后拿到绑定的BottomSheetBehavior,重新设置监听,在调用dismiss 方法时,我们重新设置一些Behavior 的状态。代码如下:</p>    <pre>  <code class="language-java">// 解决下滑隐藏dialog 后,再次调用show 方法显示时,不能弹出Dialog              View view1 = mBottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);              final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view1);              bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {                  @Override                  public void onStateChanged(@NonNull View bottomSheet, int newState) {                      if (newState == BottomSheetBehavior.STATE_HIDDEN) {                          Log.i("BottomSheet","onStateChanged");                          mBottomSheetDialog.dismiss();                          bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);                      }                  }                    @Override                  public void onSlide(@NonNull View bottomSheet, float slideOffset) {                    }              });</code></pre>    <p>以上就是BottomSheetBehavior 和BottomSheetDialog 的用法。</p>    <p>3,SwipeDissmissBehavior 的使用</p>    <p>上面讲了BottomSheetBehavior 和BottomSheetDialog 的用法,接下来看另一种场景的Behavior-SwipeDissmissBehavior,叫滑动消失或者滑动关闭,这个Behavior 在我们项目中用得可能就不是很多了。有个场景就是Snackbar的使用了,Android 5.0 以上 ,增加了Snackbar提示消息,Snackbar 的Behavior 的就是 SwipeDissmissBehavior 的应用,当滑动Snackbar 的时候,Snackbar 消失,效果如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/dcf2e9eb7e3cae0228d178f20faa97dc.gif"></p>    <p style="text-align:center">snackbar的behavir.gif</p>    <p>使用也非常简单,在代码中只接new 一个SwipeDismissBehavior,设置一些属性后,添加到CoordinatorLayout.LayoutParams,代码如下:</p>    <pre>  <code class="language-java">mSwipeLayout = findViewById(R.id.swipe_layout);          SwipeDismissBehavior swipe = new SwipeDismissBehavior();            /**           * //设置滑动的方向,有3个值           *           * 1,SWIPE_DIRECTION_ANY 表示向左像右滑动都可以,           * 2,SWIPE_DIRECTION_START_TO_END,只能从左向右滑           * 3,SWIPE_DIRECTION_END_TO_START,只能从右向左滑           */          swipe.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);            swipe.setStartAlphaSwipeDistance(0f);            swipe.setSensitivity(0.2f);            swipe.setListener(new SwipeDismissBehavior.OnDismissListener() {              @Override              public void onDismiss(View view) {                  Log.e(TAG,"------>onDissmiss");              }                @Override              public void onDragStateChanged(int state) {                  Log.e(TAG,"------>onDragStateChanged");              }          });            CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mSwipeLayout.getLayoutParams();          if(layoutParams!=null){              layoutParams.setBehavior(swipe);          }</code></pre>    <p>有两个重要的方法, wipe.setSwipeDirection 设置滑动方向,有三个取值,上面已经注释,不过多解释,还有就是 swipe.setListener 可以监听dissmiss 和状态改变,在这些回调里面可以做一些自己的逻辑。最后效果图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/bfccb6328a1e67ea8c18f45ebac82140.gif"></p>    <p style="text-align:center">swipeDissmissBehavir.gif</p>    <p>4,自定义Behavior</p>    <p>上面讲了Google 为我们提供的一些场景使用的Behavior,当然还有一些Google 提供的一些组件使用的Behavior,AppbarLayout内部的Behavior,如专门协调 AppbarLayout 与可滚动View(NestedScrollView,RecyclerView )的, FloatActionButton内部的Behavior ,协调和Snackbar 的关系,保证Snackbar 弹出的时候不被FAB 遮挡。还有就是上面说的Snackbar内部的Behavior 等等。但是有时候,要实现多个View之间的的交互时,我们可以自定义Behavior ,下面就说说怎么自定义一个Behavior。</p>    <p>自定义Behavior 最关键的就是文章第一部分介绍的Behavior 提供的那一些方法,忘了的请到回去看一下第一部分的方法注释。自定义Behavior 分为两种:</p>    <ul>     <li>第一种是通过监听一个View的状态,如位置、大小的变化,来改变其他View的行为,这种只需要重写2个方法就可以了,分别是 layoutDependsOn 和 onDependentViewChanged , layoutDependsOn方法判断是指定依赖的View时,返回true,然后在onDependentViewChanged 里,被依赖的View做需要的行为动作。</li>    </ul>    <ul>     <li>第二种就是重写 onStartNestedScroll 、 onNestedPreScroll 、 onNestedScroll 等一系列方法,前面第一步分已经讲过。</li>    </ul>    <p>上面两种方法相比,第一种很简单,第二种复杂一些,但是第二种实现的效果也要复杂。下面就以开眼首页的滑动Header效果为例,来实现一个自定义的Behavior。开眼首页滑动header效果如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/70cd06362818a81e64128422ff213fbd.gif"></p>    <p style="text-align:center">开眼首页效果.gif</p>    <p>效果如上:就是列表滑动的时候是覆盖Header(不是Header缩小,Header没动),然后就是Header有一个alpha 的变化。</p>    <p>1,首先是整个布局,Header 固定在顶部,列表在Header 的下方,CoordinatorLayout 是一个FrameLayout,不能提供这样的布局,我们需要重写onLayoutChild 来让列表位于Header下面:</p>    <pre>  <code class="language-java">@Override      public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {          Log.i(TAG,"onLayoutChild.....");          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);      }</code></pre>    <p>我们需要知道Header的高度,将Header的高度写在dimens文件中,getHeaderHeight()方法如下:</p>    <pre>  <code class="language-java">/**       * 获取Header 高度       * @return       */      public int getHeaderHeight(){          return MaterialDesignSimpleApplication.getAppContext().getResources().getDimensionPixelOffset(R.dimen.header_height);      }</code></pre>    <p>2,当开始滑动的时候,利用setTranslationY 来移动列表,知道完全盖住header ,这是时候,列表就不移动了,只是列表的滑动了。当下滑到顶端的时候,又将列表向下滑动,直到header 完全显示,思路就是这样。开眼的首页向上滑洞的时候,Header 有一个alpha的变化,本例子没有实现,其实也很简单,只要重写onDependentViewChanged方法,在里面根据滑动距离算出alpha 变化的值就可以了。自定义Behavior 完整代码如下:</p>    <pre>  <code class="language-java">/**   *   *   自定义Behavior :实现RecyclerView(或者其他可滑动View,如:NestedScrollView) 滑动覆盖header 的效果   * Created by zhouwei on 16/12/19.   */    public class CoverHeaderScrollBehavior extends CoordinatorLayout.Behavior<View> {      public static final String TAG = "CoverHeaderScroll";        public CoverHeaderScrollBehavior(Context context, AttributeSet attributeSet){          super(context,attributeSet);      }          @Override      public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {          Log.i(TAG,"onLayoutChild.....");          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);      }        @Override      public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {          return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;      }        @Override      public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {          super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);          // 在这个方法里面只处理向上滑动          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 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);          // 在这个方法里只处理向下滑动          if(dyUnconsumed >0){              return;          }            float transY = child.getTranslationY() - dyUnconsumed;          Log.i(TAG,"------>transY:"+transY+"****** child.getTranslationY():"+child.getTranslationY()+"--->dyUnconsumed"+dxUnconsumed);          if(transY > 0 && transY < getHeaderHeight()){              child.setTranslationY(transY);          }      }        /**       * 获取Header 高度       * @return       */      public int getHeaderHeight(){          return MaterialDesignSimpleApplication.getAppContext().getResources().getDimensionPixelOffset(R.dimen.header_height);      }    }</code></pre>    <p>xml 的代码如下:</p>    <pre>  <code class="language-java"><?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:orientation="vertical"                android:layout_width="match_parent"                android:layout_height="match_parent">     <ImageView         android:layout_width="match_parent"         android:layout_height="@dimen/header_height"         android:scaleType="centerCrop"         android:src="@drawable/meizhi"         />      <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="@string/cover_header_behavior"          >          <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></code></pre>    <p>最后实现的效果如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a5cf4ce25d680f1326d4ab5e822a7712.gif"></p>    <p style="text-align:center">仿开眼首页效果.gif</p>    <h3>最后</h3>    <p>以上就是关于Behavior 的全部内容,自定义Behavior 这一块,特别是处理滑动嵌套对于刚接触的同学来说还是挺难的,不过当掌握了之后,我们能做出很多炫酷的效果。所以,再困难也值得花时间去学习。本文到此结束,如有问题,欢迎交流。</p>    <p>参考资料:</p>    <p>1, <a href="/misc/goto?guid=4959732420460973913" rel="nofollow,noindex">自定义Behavior的艺术探索-仿UC浏览器主页</a></p>    <p>2, <a href="/misc/goto?guid=4959732420552872298" rel="nofollow,noindex">使用 CoordinatorLayout 实现复杂联动效果</a></p>    <p>3, <a href="/misc/goto?guid=4959732420640013784" rel="nofollow,noindex">Material之Behavior实现支付宝密码弹窗 仿淘宝/天猫商品属性选择</a></p>    <p> </p>    <p>来自:http://www.jianshu.com/p/82d18b0d18f4</p>    <p> </p>