一个神奇的控件——Android CoordinatorLayout与Behavior使用指南

squtxtfzt 8年前
   <h2>介绍</h2>    <p>CoordinatorLayout是用来协调其子view们之间动作的一个父view,而Behavior就是用来给CoordinatorLayout的子view们实现交互的。</p>    <h2>SUM</h2>    <h2>1. CollapsingToolbarLayout_伸缩折叠工具</h2>    <p>a. CollapsingToolbarLayout折叠或展开时,FloatingActionButton跟随运动并且大小相应变化. CollapsingToolbarLayout是专门用来实现子布局内不同元素响应滚动细节的布局。</p>    <p>b. AppBarLayout是一种支持响应滚动手势的app bar布局(比如工具栏滚出或滚入屏幕);与AppBarLayout组合的滚动布局(Recyclerview、NestedScrollView等)需要设置app:layout_behavior="@string/appbar_scrolling_view_behavior"(上面代码中NestedScrollView控件所设置的)。没有设置的话,AppBarLayout将不会响应滚动布局的滚动事件。。</p>    <p>c. CollapsingToolbarLayout和ScrollView一起使用会有滑动bug,注意要使用NestedScrollView来替代ScrollView。</p>    <p>Android studio中有一个Activity模板叫ScrollingActivity,它实现的就是简单的可折叠工具栏。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/89a3126a1e2b49136fb7042a67c18fcb.gif"></p>    <p>ScrollingActivity的布局代码如下:</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"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:fitsSystemWindows="true"      tools:context="com.jack.jack_junit_demo.ScrollingActivity">        <android.support.design.widget.AppBarLayout          android:id="@+id/app_bar"          android:fitsSystemWindows="true"          android:layout_height="180dp"          android:layout_width="match_parent"          android:theme="@style/AppTheme.AppBarOverlay">            <android.support.design.widget.CollapsingToolbarLayout              android:id="@+id/toolbar_layout"              android:fitsSystemWindows="true"              android:layout_width="match_parent"              android:layout_height="match_parent"              app:layout_scrollFlags="scroll|exitUntilCollapsed"              app:contentScrim="?attr/colorPrimary">                <android.support.v7.widget.Toolbar                  android:id="@+id/toolbar"                  android:layout_height="?attr/actionBarSize"                  android:layout_width="match_parent"                  app:layout_collapseMode="pin"                  app:popupTheme="@style/AppTheme.PopupOverlay" />            </android.support.design.widget.CollapsingToolbarLayout>      </android.support.design.widget.AppBarLayout>      <!--可以include 抽取出来-->      <android.support.v4.widget.NestedScrollView                                  android:layout_width="match_parent"                      android:layout_height="match_parent"                            app:layout_behavior="@string/appbar_scrolling_view_behavior"              >              <TextView                          android:layout_width="wrap_content"                          android:layout_height="wrap_content"                          android:layout_margin="@dimen/text_margin"                           android:text="@string/large_text" />           </android.support.v4.widget.NestedScrollView>        <android.support.design.widget.FloatingActionButton          android:id="@+id/fab"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_margin="@dimen/fab_margin"          app:layout_anchor="@id/app_bar"          app:layout_anchorGravity="bottom|end"          android:src="@android:drawable/ic_dialog_email" />    </android.support.design.widget.CoordinatorLayout></code></pre>    <p>2.AppBarLayout的子布局有5种滚动标识</p>    <p>就是上面代码CollapsingToolbarLayout中配置的app:layout_scrollFlags属性:</p>    <ol>     <li>scroll:将此布局和滚动时间关联。这个标识要设置在其他标识之前,没有这个标识则布局不会滚动且其他标识设置无效。</li>     <li>enterAlways:任何向下滚动操作都会使此布局可见。这个标识通常被称为“快速返回”模式。</li>     <li>enterAlwaysCollapsed:假设你定义了一个最小高度(minHeight)同时enterAlways也定义了,那么view将在到达这个最小高度的时候开始显示,并且从这个时候开始慢慢展开,当滚动到顶部的时候展开完。</li>     <li>exitUntilCollapsed:当你定义了一个minHeight,此布局将在滚动到达这个最小高度的时候折叠。</li>     <li>snap:当一个滚动事件结束,如果视图是部分可见的,那么它将被滚动到收缩或展开。例如,如果视图只有底部25%显示,它将折叠。相反,如果它的底部75%可见,那么它将完全展开。</li>    </ol>    <p>3.CollapsingToolbarLayout的contentScrim、statusBarScrim 属性。</p>    <ul>     <li>app:contentScrim设置折叠时工具栏布局的颜色</li>     <li>app:statusBarScrim设置折叠时状态栏的颜色。<br> 默认contentScrim是colorPrimary的色值,statusBarScrim是colorPrimaryDark的色值。这个后面会用到。</li>    </ul>    <p>4.CollapsingToolbarLayout子布局设置折叠模式,app:layout_collapseMode**</p>    <ul>     <li>off:这个是默认属性,布局将正常显示,没有折叠的行为。</li>     <li>pin:CollapsingToolbarLayout折叠后,此布局将固定在顶部。</li>     <li>parallax:CollapsingToolbarLayout折叠时,此布局也会有视差折叠效果</li>    </ul>    <p>当CollapsingToolbarLayout的子布局设置了parallax模式时,我们还可以通过</p>    <p>app:layout_collapseParallaxMultiplier</p>    <p>设置视差滚动因子,值为:0~1。</p>    <p>5.FloatingActionButton</p>    <p>FloatingActionButton这个控件通过app:layout_anchor这个设置锚定在了AppBarLayout下方。FloatingActionButton源码中有一个Behavior方法,当AppBarLayout收缩时,FloatingActionButton就会跟着做出相应变化。关于CoordinatorLayout和Behavior,我下一篇文章会和大家一起学习。</p>    <p>B站很早就开源了一个弹幕引擎,还起了个狂拽酷炫吊炸天的名字叫“烈焰弹幕使 ”(一看就是二次元程序猿们的作品→_→),源码在github上,项目名叫 DanmakuFlameMaster 。</p>    <h2>2. 自定义CoordinatorLayout的Behavior</h2>    <p>自定义Behavior模仿知乎</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/04733c349a7be0929b1c4fc8e75baf57.gif"></p>    <p>1.先看下布局</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:id="@+id/behavior_demo_coordinatorLayout"      android:layout_width="match_parent"      android:layout_height="match_parent">        <android.support.design.widget.AppBarLayout          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:theme="@style/AppTheme.AppBarOverlay">            <android.support.v7.widget.Toolbar              android:id="@+id/toolbar"              android:layout_width="match_parent"              android:layout_height="?attr/actionBarSize"              app:layout_scrollFlags="scroll|enterAlways|snap"              android:background="?attr/colorPrimary" />      </android.support.design.widget.AppBarLayout>        <android.support.v4.widget.SwipeRefreshLayout          android:id="@+id/behavior_demo_swipe_refresh"          android:layout_width="match_parent"          android:layout_height="match_parent"          app:layout_behavior="@string/appbar_scrolling_view_behavior">          <android.support.v7.widget.RecyclerView              android:id="@+id/behavior_demo_recycler"              android:layout_width="match_parent"              android:layout_height="match_parent"              />      </android.support.v4.widget.SwipeRefreshLayout>        <!--行为,依赖于自定义Beavior-->      <android.support.design.widget.FloatingActionButton          android:id="@+id/fab"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_marginRight="16dp"          android:layout_marginBottom="72dp"          android:src="@android:drawable/ic_dialog_email"          app:layout_behavior="com.example.zcp.coordinatorlayoutdemo.behavior.MyFabBehavior"          android:layout_gravity="bottom|right" />        <!--行为,依赖于自定义Beavior-->      <LinearLayout          android:layout_width="match_parent"          android:layout_height="?attr/actionBarSize"          android:layout_gravity="bottom"          android:background="@color/colorPrimary"          android:gravity="center"          app:layout_behavior="com.example.zcp.coordinatorlayoutdemo.behavior.MyBottomBarBehavior">          <TextView              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:layout_gravity="center"              android:textColor="#ffffff"              android:text="这是一个底栏"/>      </LinearLayout>    </android.support.design.widget.CoordinatorLayout></code></pre>    <p>SwipeRefreshLayout、FloatingActionButton和当做底栏的LinearLayout上有一个app:layout_behavior配置。</p>    <p>SwipeRefreshLayout配置的"@string/appbar_scrolling_view_behavior"是系统提供的,用来使滑动控件与AppBarLayout互动。</p>    <p>FloatingActionButton和底栏上配置的是我们接下来要自定义的Behavior。</p>    <p>先看FloatingActionButton的Behavior。</p>    <pre>  <code class="language-java">public class MyFabBehavior extends CoordinatorLayout.Behavior<View> {        private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();        private float viewY;//控件距离coordinatorLayout底部距离      private boolean isAnimate;//动画是否在进行        public MyFabBehavior(Context context, AttributeSet attrs) {          super(context, attrs);      }        @Override      public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {            if (child.getVisibility() == View.VISIBLE && viewY == 0) {              //获取控件距离父布局(coordinatorLayout)底部距离              viewY = coordinatorLayout.getHeight() - child.getY();          }            return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;//判断是否竖直滚动      }        @Override      public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {          //大于0是向上滚动 小于0是向下滚动            if (dy >= 0 && !isAnimate && child.getVisibility() == View.VISIBLE) {              hide(child);          } else if (dy < 0 && !isAnimate && child.getVisibility() == View.GONE) {              show(child);          }      }        //隐藏时的动画      private void hide(final View view) {          ViewPropertyAnimator animator = view.animate().translationY(viewY).setInterpolator(INTERPOLATOR).setDuration(200);            animator.setListener(new Animator.AnimatorListener() {              @Override              public void onAnimationStart(Animator animator) {                  isAnimate = true;              }                @Override              public void onAnimationEnd(Animator animator) {                  view.setVisibility(View.GONE);                  isAnimate = false;              }                @Override              public void onAnimationCancel(Animator animator) {                  show(view);              }                @Override              public void onAnimationRepeat(Animator animator) {                }          });          animator.start();      }        //显示时的动画      private void show(final View view) {          ViewPropertyAnimator animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(200);          animator.setListener(new Animator.AnimatorListener() {              @Override              public void onAnimationStart(Animator animator) {                  view.setVisibility(View.VISIBLE);                  isAnimate = true;              }                @Override              public void onAnimationEnd(Animator animator) {                  isAnimate = false;              }                @Override              public void onAnimationCancel(Animator animator) {                  hide(view);              }                @Override              public void onAnimationRepeat(Animator animator) {                }          });          animator.start();      }  }</code></pre>    <p>逻辑并不复杂,我们通过重写Behavior中关于嵌套滑动的两个回调完成了FloatingActionButton的隐藏和显示判断及操作。</p>    <p>单独出场的底栏也可以利用上面一样的方法来设置隐藏或显示,我的底栏是和AppBarLayout一起出场,所以我就让底栏从属于AppBarLayout活动。代码如下:</p>    <pre>  <code class="language-java">public class MyBottomBarBehavior extends CoordinatorLayout.Behavior<View> {          public MyBottomBarBehavior(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) {            float translationY = Math.abs(dependency.getTop());//获取更随布局的顶部位置          child.setTranslationY(translationY);          return true;      }    }</code></pre>    <p>代码量比上个还少。我们还可以通过重写onMeasureChild()来使控件响应从属控件的大小变化。</p>    <p>Behavior提供的很多,我这里用到的只是一部分,大家可以看看源码,根据具体需求去使用。</p>    <h2>3 .CollapsingToolbarLayout与TabLayout结合</h2>    <p>CollapsingToolbarLayout与TabLayout组合使用的效果也不错。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/48a9dd7862cee0951951c2bf5bee8ba0.gif"></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"  xmlns:tools="http://schemas.android.com/tools"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:fitsSystemWindows="true">        <android.support.design.widget.AppBarLayout          android:id="@+id/app_bar"          android:layout_width="match_parent"          android:layout_height="250dp"          android:fitsSystemWindows="true"          android:theme="@style/AppTheme.AppBarOverlay">            <android.support.design.widget.CollapsingToolbarLayout              android:id="@+id/toolbar_layout"              android:layout_width="match_parent"              android:layout_height="match_parent"              app:titleEnabled="false"              android:fitsSystemWindows="true"              app:contentScrim="@color/colorPrimary"              app:statusBarScrim="@android:color/transparent"              app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">              <ImageView                  android:id="@+id/imageview"                  android:layout_width="match_parent"                  android:layout_height="match_parent"                  android:scaleType="centerCrop"                  android:adjustViewBounds="true"                  app:layout_collapseMode="parallax"                  app:layout_collapseParallaxMultiplier="0.7"                  android:fitsSystemWindows="true"                  android:src="@drawable/girl2"/>              <View                  android:layout_width="match_parent"                  android:layout_height="40dp"                  android:background="@drawable/gradient"                  android:fitsSystemWindows="true" />              <android.support.v7.widget.Toolbar                  android:id="@+id/toolbar"                  android:layout_width="match_parent"                  android:layout_height="96dp"                  android:minHeight="?attr/actionBarSize"                  android:gravity="top"                  app:layout_collapseMode="pin"                  app:title="hello"                  app:popupTheme="@style/AppTheme.PopupOverlay"                  app:titleMarginTop="15dp"                  />              <android.support.design.widget.TabLayout                  android:id="@+id/tablayout"                  android:layout_width="match_parent"                  android:layout_height="45dp"                  android:layout_gravity="bottom" />          </android.support.design.widget.CollapsingToolbarLayout>      </android.support.design.widget.AppBarLayout>          <android.support.v4.view.ViewPager          android:id="@+id/viewpage"          android:layout_width="match_parent"          android:layout_height="wrap_content"          app:layout_behavior="@string/appbar_scrolling_view_behavior">      </android.support.v4.view.ViewPager>    </android.support.design.widget.CoordinatorLayout></code></pre>    <p>2. AppBarLayout</p>    <ol>     <li>SwipeRefreshLayout配置的"@string/appbar_scrolling_view_behavior"是系统提供的,用来使滑动控件与AppBarLayout互动。</li>    </ol>    <p> </p>    <p>来自:http://www.jianshu.com/p/4bb9ee0e0177</p>    <p> </p>