「技术」打造一个漂亮的Android标题栏

Cover: 清少納言 - Mika Pikazo@Pixiv

前言

最近因为正在做新坑Pixiu,本身的计划是照着Pivision做一个UI几乎没区别的复刻版,但是苦于已经好久没有正经开发过安卓App了,很多UI自己根本不会做,一个滑动的题图卡了我一天的时间,不过最后总算是连抄带蒙的做出来了,而且实际上也很简单,所以就写一个日志来记录一下。

效果演示

效果演示

开发过程

整个效果实现需要三部分的工作,分别解决三个问题

  • StatusBar透明化
  • 带图片可滑动的ToolBar
  • 图片延伸到StatusBar

StatusBar透明化

首先需要在style.xml里添加如下的style,名字随便起。

<style name="AppTheme.NoActionBar.TranslucentStatusBar" parent="AppTheme.NoActionBar">
    <item name="android:windowTranslucentStatus" tools:targetApi="KITKAT">true</item>
    <item name="android:statusBarColor" tools:targetApi="lollipop">
        @android:color/transparent
    </item>
</style>

首先需要先继承NoActionBar,否则会带上默认的ActionBar,就没办法实现效果。

其次中间有一个tools:targetApi="KITKAT",这个android:windowTranslucentStatus就是设置是否开启透明StatusBar的参数,这个是从Android4.4,也就是Kitkat开始加入的参数,如果不这么写,在value-19里的style.xml写也是可以,不过我比较偷懒,这么写就不用做很多的value文件夹了。

而另一个参数则是Android5.0后加入的,设置StatusBar颜色的参数,这个把他设置成透明的就可以了。

最后则是把目标的Activity设置上这个新的主题。

<activity
    android:name=".view.MainActivity"
    android:theme="@style/AppTheme.NoActionBar.TranslucentStatusBar" />

这样咱们就获得了一个没有标题栏,而且也是透明的,还可以在里面放控件的Activity了。

带图片可滑动的ToolBar

这一步我玩了一上午,但是只是实现一个这样的简单效果并没有多难,只是我坑踩太多。

打开咱们Activity的布局文件,例如这里是activity_main.xml,把根布局换成CoordinatorLayout,然后在里面塞一个AppBarLayout,AppBarLayout里再塞一个CollapsingToolbarLayout,最后在CollapsingToolbarLayout里再分别塞一个ImageView,和一个ToolBar。最后把咱们的主要的布局放在CoordinatorLayout下面,这里放了个LinearLayout

<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
            app:titleEnabled="false">

            <ImageView
                android:id="@+id/iv_title_cover"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:scaleType="centerCrop"
                app:layout_collapseMode="pin"/>

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/tb_main"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_collapseMode="pin"
                app:title="@string/app_name" />

        </com.google.android.material.appbar.CollapsingToolbarLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

    </LinearLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

这里解释一下各个细节,首先是这种滑动变化的布局,必须在CoordinatorLayout里进行,所以说根布局要换成这个,不过如果这个只是页面UI一部分的话,外面也是可以再套其他布局的。

因为要做有多种元素还能滚动的复杂TitleBar,所以要用AppBarLayout包裹ToolBar,这样才能配合CoordinatorLayout实现滚动。

CollapsingToolbarLayout则是相当于一个高级的ToolBar,需要包裹ToolBar才能实现效果。参数中的app:layout_scrollFlags代表了这个控件的可滑动状态。这个参数一共有五种,可以共存,分别是。

参数 功能
scroll 代表这个控件可以被滚动
enterAlways 代表这个控件会被优先滚动,需要和scroll共用
enterAlwaysCollapsed 代表这个控件会被分段滚动,首先先滚动会显示最小高度,到达滚动边界后再完全显示,需要和enterAlways共用
exitUntilCollapsed 代表这个控件会被保留最小高度,不会完全滚动出窗口,需要和scroll共用
snap 代表这个控件滚动时会有边缘吸附效果,需要和scroll共用

因为咱们的控件属于保留最小高度,同时有吸附效果,所以最后需要的是scroll|exitUntilCollapsed|snap

至于app:titleEnabled则是代表是否使用系统自带的标题,这里因为要用ToolBar,所以关了就好。

接下来是ToolBar中的,app:layout_collapseMode,这个参数是开启了滚动后才会生效的效果,这个有三种参数。

参数 功能
none 代表这个控件会被滚出屏幕
pin 代表这个控件不会被滚出屏幕
parallax 代表这个控件滚动时会有滚动差

最后是LinearLayout中的app:layout_behavior,behavior就是在CoordinatorLayout中负责处理各种效果的工具,这里使用了一个系统默认的参数@string/appbar_scrolling_view_behavior,效果为跟随appbar滚动,所以这个控件就相当于咱们整个窗口中的根布局了,其他控件放这里就好。

这样咱们就有了一个具有带图片可滑动的ToolBar,而且StatusBar也是透明的Activity了。

图片延伸到StatusBar

这一步卡的时间最久,因为我改了好多布局的方案,最后图片总是不能好好地显示到StatusBar上,要么是不显示,要么是UI错位,最后发现最简单的办法就是给ToolBar设置一个顶部padding,大小为StatusBar的高度,就可以完美实现了。

ToolBar tb_main = findViewById(R.id.tb_main);
ViewGroup.LayoutParams toolbarParams = tb_main.getLayoutParams();
ViewGroup.MarginLayoutParams toolbarMarginParams;
if (toolbarParams instanceof ViewGroup.MarginLayoutParams) {
    toolbarMarginParams = (ViewGroup.MarginLayoutParams) toolbarParams;
} else {
    toolbarMarginParams = new ViewGroup.MarginLayoutParams(toolbarParams);
}
toolbarMarginParams.setMargins(0, ViewSize.getStatusBarHeight(this), 0, 0);
tb_main.setLayoutParams(toolbarMarginParams);

里面的ViewSize.getStatusBarHeight(Activity activity)如下。

public static int getStatusBarHeight(Activity activity) {
    int statusBarHeight = 0;
    int resourceId_status = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId_status > 0) {
        statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId_status);
    }
    return statusBarHeight;
}

这样就可以给ToolBar设置上padding了,我们的布局也终于正常了。

结语

于是我终于可以开发其他模块了233。