一个神奇的控件——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>