最近想給自己的一個鬧鐘App增加一個夜間模式,一個比較簡便的切換主題的方式就是在Styles.xml中設(shè)置兩套Theme,分別是白天模式的主題Theme,還有一個是夜間模式的Theme。然后,通過在該Activity中的setContentView()方法之前,使用setTheme(...)方法設(shè)置Activity的Theme。但是重新設(shè)置的主題Theme必須調(diào)用recreate()方法使得Activity重新調(diào)用onCreate()方法才能表現(xiàn)出來。因此,這個時候會閃屏。我們先詳細(xì)介紹下這種方式的實現(xiàn),后面再說解決閃屏的方法。
第一步,確認(rèn)哪些屬性是需要根據(jù)主題變化而改變的
以下面這個活動為例,我希望在點擊Change Theme按鈕后,可以改變
- StatusBar的顏色
- ToolBar的顏色
- 整個Activity的背景顏色
- 以及“Hello Theme”這個TextView的背景顏色
在以上打算隨著主題Theme修改的屬性中,前三個屬性是可以直接在Theme的style.xml文件中設(shè)置的,最后一個TextView的背景顏色是需要首先自定義一個屬性,然后才能夠在style.xml文件中設(shè)置的
第二步自定義屬性
在values文件夾下,新建attrs.xml文件。下面的代碼代表這新建一個名為"Text_bg_Color"的屬性,該屬性值是color類型或者reference引用類型。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="Text_bg_Color" format="color|reference"/>
</resources>
第三步定義不同的主題風(fēng)格
在styles.xml中定義不同Theme,以便后面進(jìn)行切換。下面分別定義了兩個Theme的style。第一個是默認(rèn)的主題,也就是白天模式。第二個是夜間模式。兩個模式都是繼承了"Theme.AppCompat.Light.NoActionBar",所以絕大多屬性都是一樣的,不同的是分別自定義了一些屬性。
- colorPrimary是Toolbar的背景顏色
- colorPrimaryDark是StatusBar的顏色
- android:textColorPrimary是主標(biāo)題的字體顏色
- android:colorControlNormal是控制元件的默認(rèn)狀態(tài)顏色以及overflow menu(三個點)的顏色
- colorAccent是控制元件在選中狀態(tài)的顏色
- android:windowBackground是Activity的背景顏色
<resources
>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"
>
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorLight</item>
<item name="colorPrimaryDark">@color/colorLight</item>
<item name="android:textColorPrimary">@android:color/black</item>
<item name="android:colorControlNormal">@android:color/white</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@android:color/white</item>
<item name="TextView_bg_Color">@android:color/white</item>
</style>
<style name="AppThemeNight" parent="Theme.AppCompat.Light.NoActionBar"
>
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorNight</item>
<item name="colorPrimaryDark">@color/colorNight</item>
<item name="android:textColorPrimary">@android:color/black</item>
<item name="android:colorControlNormal">@android:color/white</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@android:color/black</item>
<item name="TextView_bg_Color">@color/colorGray</item>
</style>
</resources>
在布局文件中,TextView要使用對應(yīng)style中的TextView_bg_Color屬性。"?attr/TextView_bg_Color"代表使用自定義屬性TextView_bg_Color的值,而該屬性的已經(jīng)唄Theme文件所定義。所以,TextView的背景顏色就被Theme所定義了。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/Theme_Button"
android:background="?attr/TextView_bg_Color"
android:id="@+id/text_view"
android:text="Hello Theme"/>
第四步在Activity中切換主題Theme
自定義一個ThemeUtile類,這是一個幫助切換主題的工具類。這個類提供了一個public static的布爾型的變量night,用來記錄整個App所處于的主題模式。這個類還提供了一個public static方法changeTheme(),根據(jù)night的值,來對所有的Activity設(shè)置主題。
public class ThemeUtile {
public static boolean night = false;
public static void changeTheme(Activity activity){
if (ThemeUtile.night){
activity.setTheme(R.style.AppThemeNight);
}else{
activity.setTheme(R.style.AppTheme);
}
}
}
接著設(shè)置切換主題Button的點擊事件。通過設(shè)置不同的night值,來設(shè)置不同的主題模式。因為setTheme()方法必須要在setContentView()方法之前調(diào)用,所以為了使當(dāng)前Activity的主題切換成功,需要調(diào)用recreate()方法來重新調(diào)用onCreate()方法。這樣也導(dǎo)致了當(dāng)前Activity被銷毀,并重新啟動,所以會出現(xiàn)閃屏的現(xiàn)象。
Button button = (Button) findViewById(R.id.Theme_Button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ThemeUtile.night) {
ThemeUtile.night = false;
} else {
ThemeUtile.night = true;
}
recreate();
}
});
夜間模式效果如下。
第五步 解決閃屏的問題
我搜索了很多解決不閃屏切換主題的方法,要么效果不太好,要么比較“難”,需要較深的知識積累,我就想了一個比較取巧的方法,但是沒有那么優(yōu)雅。由于在當(dāng)前屏幕值重新設(shè)置主題,會導(dǎo)致重新調(diào)用onCreate()方法導(dǎo)致閃屏。我們可以在一個新開的Activity中通過ThemeUtile.night變量重新設(shè)置主題,但不調(diào)用recreate()方法切換主題,而是“手動”設(shè)置需要改變的屬性,在退出該Activity時,使用Intent回到之前的界面,并 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);,使之前的Activity全部出棧,重新創(chuàng)建一個新的Activity,執(zhí)行onCreate()方法,從而改變主題。
代碼如下:
//在onCreate()方法中
ThemeUtile.changeTheme(this);
setContentView(R.layout.second_activity);
super.onCreate(savedInstanceState);
...
//設(shè)置改變主題按鈕的點擊事件
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (! ThemeUtile.night) {
findViewById(R.id.sec_activity).setBackgroundColor(getResources().getColor(R.color.colorGray));
getWindow().setStatusBarColor(getResources().getColor(R.color.colorGray));
toolbar.setBackgroundColor(getResources().getColor(R.color.colorGray));
ThemeUtile.night = true;
}else
{
findViewById(R.id.sec_activity).setBackgroundColor(getResources().getColor(R.color.colorWhite));
getWindow().setStatusBarColor(getResources().getColor(R.color.colorLight));
toolbar.setBackgroundColor(getResources().getColor(R.color.colorLight));
ThemeUtile.night = false;
}
}
});
//重寫onBackPressed()方法
@Override
public void onBackPressed() {
Intent intent = new Intent(SecondActivity.this,MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
super.onBackPressed();
}
就是這樣,雖然不是很優(yōu)雅,但是完成了不閃屏切換Android App主題。
2016.06.03更新:
注意,setTheme()方法必須在setContentView()和super.onCreate()之前調(diào)用,否則,Theme中的某些屬性將無法顯示出來。