Android4.0以上版本自定义Titlebar遇到的坑

虽然Android原生的控件是越来越完善和强大,但是很多时候还是需要我们自定义一些控件来让我们开发的app看起来很好看,让人舒服,比如android4.0以上默认的edittext我就觉得不好看,没有边角,还是喜欢那种四周有边角的看起来让人舒服,当然这个不同主题下显示效果是不一样的。题外话就扯到这里吧,现在进入正题:

Titlebar就是Activity最上面显示app name的那个东东,今天在coding的时候遇到一个需要自定义titlebar的,当然这个很简单了,就是自己根据需要写一个布局然后替换掉默认的titlebar就可以了,替换代码baidu一下有很多,简单的说就是在activity的onCreate方法中加入下面两句:

  1. 告诉activity我要使用自己定义的titlebar,不用你默认的

    1
    requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
  2. 让activity加载自己定义的titlebar的布局

    1
    getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);

然后在manifest中自定义titlebar的那个activity中加入自定义的theme,贴上我自定义theme的代码,我使用的是4.4的版本

1
2
3
4
5
6
7
<style name="HomePage" parent="AppTheme">
<item name="android:windowTitleSize">60dp</item>
<item name="android:windowTitleBackgroundStyle">@style/WindowTitleBackground</item>
</style>
<style name="WindowTitleBackground">
<item name="android:background">@color/blue</item>
</style>

第一个坑,报如下错误:

1
2
android.util.AndroidRuntimeException:
You cannot combine custom titles with other title features

原因是说android4.0以上使用actionbar替代titlebar了,所以调用

1
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);

网上搜到的解决办法:使用android自带的主题就OK了,也就是将parent=“AppTheme”换成parent=“android:Theme”,自己试了一下发现确实不报错了,可以正常运行了,但是问题来了:整个app背景都变成黑色的了,这根本不是我想要的颜色啊。。。于是查看android:Theme的源码(一部分)发现是这样写的:

1
2
3
4
5
6
7
<style name="Theme">
<item name="colorForeground">@android:color/bright_foreground_dark</item>
<item name="colorForegroundInverse">@android:color/bright_foreground_dark_inverse</item>
<item name="colorBackground">@android:color/background_dark</item>
<item name="colorBackgroundCacheHint">?android:attr/colorBackground</item>
....
</style>

看到colorBackground那句了吗,明白为什么黑了吧,但是我不想要黑的啊,那就继续想办法解决呗,最终在一国外网址上看到一个老外也遇到这个问题了,他给的解决方法是在自定义的主题中加入这样一句代码:

1
<item name="android:windowActionBar">false</item>

那么更改过后的自定义主题就变成这样的了

1
2
3
4
5
6
7
8
<style name="HomePage" parent="AppTheme">
<item name="android:windowTitleSize">60dp</item>
<item name="android:windowTitleBackgroundStyle">@style/WindowTitleBackground</item>
<item name="android:windowActionBar">false</item>
</style>
<style name="WindowTitleBackground">
<item name="android:background">@color/blue</item>
</style>

这样这个问题就顺利解决了,这句话的用处就是disable actionbar

第二个坑:启动activity时默认的titlebar会瞬间闪一下

第一个坑填平之后就开始遇到第二个坑了,为什么我每次启动这个自定义title的activity时候,都会闪一下默认的titlebar才加载自定义的titlebar呢,我不是自己定义了吗?为毛默认的还要闪一下,刷存在感吗。。。

首先google了一下,发现是这样的:在启动一个application的时候,为了尽快地响应用户,application会先从manifest中读取相关属性,然后尽快展现一个预览界面给用户,这个界面的展现是可能先于activity的加载的,所以预览界面读取到manifest中有定义theme这个属性,且属性值并不是@android:style/Theme.NoTitleBar,默认就会加载显示app name的titlebar,等到activity加载的时候执行到了要自定义titlebar的代码,并且加载了自定义的titlebar,那么就会变成显示自定义的titlebar,所以会出现闪了一下默认的titlebar的情况

我们可以将activity的theme属性设置为@android:style/Theme.NoTitleBar,告诉app 我不需要titlebar,那样就不会预加载带app name的titlebar了,但是我们自定义的标题栏就没法加载了,现在就需要在activity加载自定义的标题栏的时候让activity知道我还是要加载titlebar的,那么就需要加上这么一句代码:

1
setTheme(R.style.HomePage);

还有一点我需要特别强调的是:如果没有加这句

1
<item name="android:windowActionBar">false</item>

那么setTheme方法要放到super.onCreate(savedInstanceState);之前的,否则会报错,查看super.onCreate(savedInstanceState)的代码可以知道是和actionbar有关系的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE)
Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
}
if (mActivityInfo.parentActivityName != null) {
if (mActionBar == null) {
mEnableDefaultActionBarUp = true;
} else {
mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
}
}
...
}

哎,遇到的两个坑总算解决了,贴一张运行的效果图