AndroidWorldAndroid学习android

CoordinatorLayout 上的一些布局技巧

2017-03-09  本文已影响12127人  cpacm

CoordinatorLayout是一个“加强版”的 FrameLayout,它主要有两个用途:
(1) 用作应用的顶层布局管理器
(2) 通过为子View指定 behavior 实现自定义的交互行为。
在我们做 Material Design 风格的app时通常都使用 CoordinatorLayout 作为布局的根节点,以便实现特定的UI交互行为。
那么现在我们来看看如何用已有的一些控件实现一些常见的布局。

Toolbar + TabLayout 实现 TabLayout 置顶效果

很常见的一种模式是 TabLayout 放在 ToolBar 布局中与其一起置顶在界面上方,而现在的效果是将ToolBar 隐藏而 TabLayout一直置顶在界面上方。

tablayout

如何实现呢?首先所在的 Activity 要使用 AppTheme.NoActionBar 风格主题,之后再编写布局文件。

<android.support.design.widget.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"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways|snap"
            app:popupTheme="@style/AppTheme.PopupOverlay">

            <!-- toolbar内部布局文件 -->

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

        <android.support.design.widget.TabLayout
            android:id="@+id/tab_layout"
            android:layout_width="match_parent"
            android:layout_height="@dimen/tab_height"
            app:tabIndicatorColor="@color/white"
            app:tabTextColor="@color/white_normal">

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


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

    <!-- 主界面布局文件 -->

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

很重要的两点:

  1. 设置 Toolbar 的 layout_scrollFlags="scroll|enterAlways|snap" 保证 Toolbar 能随界面滑动向上隐藏。
  2. TabLayout 与 Toolbar 同级,父节点为 AppBarLayout ,保证 TabLayout 能够能够显示在 Toolbar 外面而不随其一起隐藏。

浸入式 + CollapsingToolbarLayout

CollapsingToolbarLayout 可以包裹 Toolbar , 当其显示完收缩动画时使 Toolbar 显示在顶端。而我们可以再加入浸入式的效果让 CollapsingToolbarLayout 的背景图突破系统的状态栏使界面更加美观同时也不影响 Toolbar 的显示效果。


浸入式布局

在实现布局文件前要赋予当前的Activity主题为AppTheme.Immersive。
其样式要在 v19 与 v21 分别处理,至于v19之前的 android 版本那就无能为力了。
v19

<style name="AppTheme.Immersive" parent="AppTheme.NoActionBar">
    <item name="android:windowTranslucentStatus">true</item>
</style>

v21

<style name="AppTheme.Immersive" parent="AppTheme.NoActionBar">
    <!--透明导航栏-->
    <item name="android:statusBarColor">@android:color/transparent</item>
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
</style>

之后再来看布局文件,包括了 AppBarLayoutCollapsingToolbarLayoutToolbar 这些控件。
同样也是要分为 v19 和 v21 两种布局
v19

<?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.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="220dp"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
            app:statusBarScrim="@android:color/transparent"
            app:titleEnabled="false">

            <RelativeLayout
                android:id="@+id/anime_root"
                android:layout_width="match_parent"
                android:layout_height="220dp"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7">

                <!-- 可伸缩背景图布局文件 -->

            </RelativeLayout>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:layout_marginTop="25dp"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"
                app:titleMarginTop="15dp" />

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

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

    <!-- 界面布局文件 -->

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

v21

<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="220dp"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
            app:statusBarScrim="@android:color/transparent"
            app:titleEnabled="false">
            
            <RelativeLayout
                android:id="@+id/anime_root"
                android:layout_width="match_parent"
                android:layout_height="220dp"
                android:fitsSystemWindows="true"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7">
                
                <!-- 可伸缩背景图布局文件 -->
                
            </RelativeLayout>
            
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"
                app:titleMarginTop="15dp" />
                
        </android.support.design.widget.CollapsingToolbarLayout>
        
    </android.support.design.widget.AppBarLayout>
    
    <!-- 界面布局文件 -->
    
</android.support.design.widget.CoordinatorLayout>

那么重点在哪里呢?

  1. 设置你当前的 Activity 的状态栏为透明,4.4之前的机型则无法适配。
  2. 注意每个控件下的 fitsSystemWindows 属性,使系统能够调整 view 的 padding 值使其适配。
  3. 设置 CollapsingToolbarLayout 为可滚动(scroll),滚动结束后可以设置 statusBarScrim 作为覆盖色。
  4. 背景布局与 Toolbar 同级,布局文件上可以设置滚动的模式,如视差滚动及相应的值。
  5. 注意到 v19 的布局文件上的 Toolbar ,给它赋予了一个 android:layout_marginTop="25dp"的属性,以便在Android 4.4上时防止被系统的状态栏所覆盖。当然这个值可以在代码中获取到系统状态栏高度再进行设置。

fitsSystemWindows详解:这个一个boolean值的内部属性,让view可以根据系统窗口(如status bar)来调整自己的布局,如果值为true,就会调整view的paingding属性来给system windows留出空间。

Fragment + 不同风格布局

有时候产品要求在一个 Activity 上显示不同的 Fragment 界面,且 Fragment 上的每一个头部样式都不一样,比如说一个是普通情况下的 Toolbar,另一个却是浸入式的可伸缩头部,像简书app的首页

jianshu.gif

额,我们先不提图中的那个bug,图中的界面主要是两种效果,一个为普通的标题栏+正文,另一个则是浸入式的图片背景+正文。我们也可以利用浸入式的主题来仿照出简书的效果。

各个页面不同样式

第一步要做的是给 Fragment 所在的 Activity 套上 AppTheme.Immersive 浸入式主题样式,之后为 Activity 加上布局

<?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">

    <com.ashokvarma.bottomnavigation.BottomNavigationBar
        android:id="@+id/bottom_navigation_bar"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:layout_gravity="bottom"
        android:fitsSystemWindows="true">

    </com.ashokvarma.bottomnavigation.BottomNavigationBar>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="56dp" />

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

无他,就是一个 ViewPager 容器用来加载 Fragment。

书城所在的 Fragment 布局如下:

<?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"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
            app:statusBarScrim="@android:color/transparent"
            app:titleEnabled="false">

            <!-- 背景布局 -->

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="36dp"
                android:minHeight="36dp"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay">
                <!-- 搜索框布局 -->

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

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

    </android.support.design.widget.AppBarLayout>
    
    <!-- 界面主布局 -->

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

布局跟之前的浸入式布局相同,但单单这样布局是有问题的,此时的 Toolbar 会显示在系统的状态栏下。所以我们要修正 Toolbar 的摆放位置,即人为的为其设置 MarginTop 距离。

/**
 * 修正 Toolbar 的位置
 * 在 Android 4.4 版本下无法显示内容在 StatusBar 下,所以无需修正 Toolbar 的位置
 *
 * @param toolbar
 */
protected void fixToolbar(Toolbar toolbar) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        int statusHeight = getStatusBarHeight(getActivity());
        ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams();
        layoutParams.setMargins(0, statusHeight, 0, 0);
    }
}

/**
 * 获取系统状态栏高度
 *
 * @param context
 * @return
 */
public int getStatusBarHeight(Context context) {
    Class<?> c = null;
    Object obj = null;
    Field field = null;
    int x = 0, statusBarHeight = 0;
    try {
        c = Class.forName("com.android.internal.R$dimen");
        obj = c.newInstance();
        field = c.getField("status_bar_height");
        x = Integer.parseInt(field.get(obj).toString());
        statusBarHeight = context.getResources().getDimensionPixelSize(x);
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    return statusBarHeight;
}

在每个 Fragment 初始化 Toolbar 时都需要调用这个方法来修正位置。

重点又来了:

  1. 修改为浸入式样式主题,要点见上一个案例。
  2. 人为修正 Toolbar 的距离,保证位置摆放正确。
  3. 注意 Fragment 切换时偶尔出现的一些 Toolbar 问题。

Fragment 与 Toolbar 的选项菜单问题

在 Fragment 之间切换的时候很容易遇到 Toolbar 上的菜单无法正确的显示的问题,解决方法也很简单,在 Fragment 的 onCreateView() 方法中添加一行代码:

toolbar.setTitle("title");
((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
fixToolbar(toolbar);
setHasOptionsMenu(true); //重要的一行代码,防止选项菜单错乱

总结

自从 Google 出了 com.android.support:design 包之后,其多样化的定制给 App 应用带来更加酷炫的效果,布局的变化只不过是其中的一部分而已。文章的开头也说了, CoordinatorLayout 不止是带来了布局的变化,也带给了控件更多的UI交互动作。

上一篇下一篇

猜你喜欢

热点阅读