MD风格之丰富多变Toolbar

一. 下载

    compile 'com.android.support:design:23.4.0'

二. 详解

CoordinatorLayout继承自ViewGroup,实现了NestedScrollingParent接口,可以说是超级版FrameLayout
CoordinatorLayout的用途主要有两个:

  • 作为最顶层的application decor或者chrome layout.
  • 作为有特定交互的多个子View的容器.

可以通过给CoordinatorLayout的子View指定Behaviors让子View之间产生不同的交互,也可是使用DefaultBehavior注解指定默认的交互行为。Behaviors可以实现各种各样的交互和布局变化,如侧滑抽屉和面板的滑动隐藏、一个按钮“粘”在其它元素上并跟随其一起滑动。CoordinatorLayout的子View可以有一个anchor锚,这个anchor的id必须是CoordinatorLayout的子View,但不要求一定是固定View或固定View的子View。使用app:layout_anchorapp:layout_anchorGravity属性可以控制浮动View的相对位置。
简单点说,通过CoordinatorLayout我们可以很好地控制容器中子View的交互行为,当然这离不开CoordinatorLayout的静态抽象内部类Behavior的作用,Behavior作为CoordinatorLayout子View的Behavior交互插件,实现了一到多个子View之间的交互,如drags,、swipes、 flings等手势。
CoordinatorLayout常用于app bar(之前叫ActionBar,现在改用Toolbar,不过统称AppBar)与其它View的交互。在Toolbar外面包裹一层AppBarLayout以便Toolbar和其它子View能更好地响应滚动事件。
AppBarLayout继承自LinearLayout,是一个vertical的LinearLayout,并添加了@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)注解以响应滚动事件。其子View应该通过setScrollFlags(int)app:layout_scrollFlags提供它们希望的滚动行为。AppBarLayout最好作为CoordinatorLayout的直接子View,否则它的很多方法将会失效。AppBarLayout需要一个可滚动的兄弟View以便知道何时滚动,这就需要为滚动View设置一个AppBarLayout.ScrollingViewBehavior实例(这里将RecyclerView的layout_behavior属性设置为AppBarLayout.ScrollingViewBehavior):

<?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.frank.mdtest.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/abl"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways" />

        <android.support.design.widget.TabLayout
            android:id="@+id/tl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|enterAlways" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>

这样,当用户滚动RecyclerView时,AppBarLayout就能响应滚动事件并根据其子View的layout_scrollFlags属性控制其如何进入和退出屏幕,ScrollFlags包括:

  • scroll: 所有想滚出屏幕的View都要设置这个flag,如果不使用该flag则会固定在屏幕顶端。
  • exitUntilCollapsed: 该flag会使View在滚出屏幕前滚动直至“collapsed”状态(View的最小高度)。
  • enterAlways: 该flag会确保任何向下的滚动都会导致该View可见,通常用于“快速返回”模式。
  • enterAlwaysCollapsed: 当View设置了minHeight并使用该flag时,View只会在最小高度(“collapsed”)时进入,只会在滚动View滚到顶的时候重新展开直至完整高度。
  • snap: 当滚动结束时,如果View只是部分可见,将会被拉住并滚动至它的最近的边缘。如:如果View仅仅底部25%可见,它将会被完全滚出屏幕。相反,如果它底部75%可见,它将滚动直至完全显示。

注意:使用layout_scrollFlags的View必须在不适用该属性的View之前定义,以确保这些View都能从上滚出屏幕,留下其后固定的View。

为了更详细的控制Toolbar在“collapsing”过程中的元素交互,可以在Toolbar外面包裹一层CollapsingToolbarLayout
CollapsingToolbarLayout继承自FrameLayout,被设计为AppBarLayout的直接子View并作为Toolbar的容器,CollapsingToolbarLayout包含以下特征:

  • Collapsing title: 其Title会随布局完全展开可见而变大,随折叠而变小。可通过setTitle(CharSequence)方法设置Title,通过collapsedTextAppearanceexpandedTextAppearance属性调整Title样式。
  • Content scrim: 当滚动到”collapsed”阈值时显示或隐藏罩层。可通过setContentScrim(Drawable)方法设置罩层。
  • Status bar scrim: 当滚动到”collapsed”阈值时在状态栏显示或隐藏罩层。可使通过setStatusBarScrim(Drawable)方法设置罩层并在LOLLIPOP设备上起作用,同时需要android:fitsSystemWindows="true"
  • Parallax scrolling children: 其子View可以在该布局内做视差滚动,可以通过给子View(通常是Toolbar的兄弟ImageView)设置app:layout_collapseMode="parallax"app:layout_collapseParallaxMultiplier="0.7"实现。
  • Pinned position children: 其子View可以固定在指定位置,可以通过对Toolbar的app:layout_collapseMode="pin"属性值设置确保滚动至被折叠后Toolbar能固定在屏幕顶端。

collapseMode包括:

  • off: 没有任何scroll behavior的常规元素(默认属性值)。
  • pin: View将会固定到CollapsingToolbarLayout的底端。
  • parallax: View将会随着CollapsingToolbarLayout做视差滚动。
<?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.frank.mdtest.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/abl"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" />

        </android.support.design.widget.CollapsingToolbarLayout>

        <android.support.design.widget.TabLayout
            android:id="@+id/tl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|snap" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>

通过CollapsingToolbarLayoutapp:layout_collapseMode="pin"属性值地设置确保滚动至被折叠后Toolbar能固定在屏幕顶端,更好的是,Toolbar配合CollapsingToolbarLayout使用时,Title的大小将随个展开和折叠调整自己的大小,不过在这种情况下需要调用CollapsingToolbarLayout而不是Toolbar的setTitle()方法设置Title。此外还可以通过给CollapsingToolbarLayout子View(通常是Toolbar的兄弟ImageView)设置app:layout_collapseMode="parallax"app:layout_collapseParallaxMultiplier="0.7"属性以实现视差滚动效果,然后给CollapsingToolbarLayout设置app:contentScrim属性以便在折叠时设置罩层颜色:

<?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.frank.mdtest.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/abl"
        android:layout_width="match_parent"
        android:layout_height="192dp"
        android:fitsSystemWindows="true" >

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/ctl"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:contentScrim="@color/colorPrimary"
            android:fitsSystemWindows="true" >

            <ImageView
                android:id="@+id/iv_toolbar_bg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/skill_default_ow"
                android:fitsSystemWindows="true"
                app:layout_collapseMode="parallax"
                app:layout_scrollFlags="scroll|enterAlways" />

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:contentInsetStart="0dp" >

                <TextView
                    android:id="@+id/tv_title"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center"
                    android:text="Title" />

            </android.support.v7.widget.Toolbar>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <ImageView
        android:id="@+id/fab"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/group_in"
        android:elevation="10dp"
        android:layout_marginLeft="20dp"
        app:layout_anchor="@id/abl"

</android.support.design.widget.CoordinatorLayout>

CoordinatorLayout.Behavior类是所有Behavior的基类,用来处理CoordinatorLayout的直接子View之间的动作交互。该抽象类已知的子类包括AppBarLayout.Behavior, AppBarLayout.ScrollingViewBehavior, BottomSheetBehavior, FloatingActionButton.Behavior, SwipeDismissBehavior
CoordinatorLayout.Behavior的主要方法包括:

  • public Behavior().
    无参构造器,以便用new关键字实例化
  • public Behavior(Context context, AttributeSet attrs).
    通过layout属性或@CoordinatorLayout.DefaultBehavior注解间接实例化时,需要提供该构造器,以便CoordinatorLayout.LayoutParams可以通过反射实例化该Behavior
  • public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev).
    CoordinatorLayout向内分发触摸事件之前进行处理。如果Behavior想要拦截并接管事件流,需要返回true,默认返回false。但如果Behavior拦截了触摸事件,那剩下的事件流将发送到onTouchEvent方法
  • public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev).
    Behavior拦截触摸事件后接收和处理触摸事件,如果Behavior处理了这次触摸事件并还想继续接收事件流的其它事件,需要返回true,默认返回false
  • public int getScrimColor(CoordinatorLayout parent, V child).
    child view的罩层颜色,默认为Color.BLACK
  • public float getScrimOpacity(CoordinatorLayout parent, V child).
    child view的罩层透明度,默认为0.f
  • public boolean blocksInteractionBelow(CoordinatorLayout parent, V child).
    决定child view后面的view是否应该被阻塞,默认返回getScrimOpacity(parent, child) > 0.f
  • public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency).
    判断给定的child view是否和其它某个兄弟view进行布局依赖。在请求layout时该方法至少要被调用一次,如果对于给定参数的(child,dependency)值对,该方法返回true,那么其父容器CoordinatorLayout将会:①总是在dependent view布局完成后才布局child view, 无论声明顺序如何②当dependency view的布局或位置变化时,会调用该方法。如果child view的布局依赖dependency view的布局则返回true,默认返回false
  • public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency).
    child view是否要对它所依赖的dependent view的改变做出响应。一旦dependent view在标准布局流中的大小和位置发生改变,该方法就会被调用。Behavior可以用该方法对child view进行更新操作,但如果Behavior通过该方法更改了child view的布局, 那它也应该在onLayoutChild(CoordinatorLayout, View, int)方法中做出相应的更改。由于所有子view都是按依赖顺序进行布局的,所以在正常布局期间该方法将不会被调用。如果Behavior更改了child view的大小或位置, 应该返回true,默认返回false
  • public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency).
    在dependent view被从父布局中移除后会调用该方法,此时Behavior可以对child view进行适当更新操作
  • public boolean isDirty(CoordinatorLayout parent, V child).
    告诉CoordinatorLayout该child是否为dirty状态,默认返回false
  • public boolean onMeasureChild(CoordinatorLayout parent, V child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed).
    CoordinatorLayout要测量给定child view时调用。如果想要让Behavior自己决定如何测量该child view,需要返回true,默认返回false(由CoordinatorLayout测量)
  • public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection).
    CoordinatorLayout要布局给定child view时调用。如果Behavior实现了onDependentViewChanged(CoordinatorLayout, View, View)方法并更改了child view,那它也应实现该方法。如果Behavior对child view进行了布局则返回true,默认返回false
  • public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes).
    CoordinatorLayout的一个子孙view尝试初始化嵌套滚动时调用。CoordinatorLayout所有直接子view关联的Behavior都可以响应该事件并返回true以表明CoordinatorLayout作为该嵌套滚动的父容器,只有该方法返回了true的Behavior才能接收嵌套滚动的事件流,默认返回false
  • public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes).
    CoordinatorLayout接受嵌套滚动时会回调该方法
  • public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target).
    当一次嵌套滚动结束时回调该方法
  • public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed).
    当嵌套滚动已经更新并且target已经或试图滚动时调用,其中dxConsumed表示target作嵌套滚动过程中水平方向上消费的距离,dxUnconsumed表示用户期望的水平方向上的滚动距离
  • public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed).
    在嵌套滚动将要更新并且target消耗滚动距离之前调用,其中dx表示用户想要滚动的水平像素值,consumed[0]表示消耗的dx距离,consumed[1]表示消耗的dy距离
  • public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed).
    当嵌套滚动子view开始或想要fling时调用,如果Behavior消费了该fling则需要返回true,默认返回false
  • public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY).
    当嵌套滚动子view将要fling时调用,如果Behavior消费了该fling则需要返回true,默认返回false

关于Nested Scrolling(嵌套滚动/嵌套滑动),想了解更多可参考NESTED SCROLLING WITH COORDINATORLAYOUT ON ANDROIDNestedScrolling事件机制源码解析等blogs的简单介绍,或阅读相关源码:


References

Android Developers Blog
Android Open Source Project
CodePath Android Cliffnotes

发布了48 篇原创文章 · 获赞 168 · 访问量 56万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览