前言
在android 10.0,新增一个深色主题功能,具体如官网说明:
https://developer.android.google.cn/guide/topics/ui/look-and-feel/darktheme
说白了,就是一个黑白主题动态切换,那对于android 10.0以前的版本,就不支持这个功能了。
需求来源
一天,领导来找我,说小明,现在有一个android的深色主题功能开发,就是黑白主题,你在我们的项目上也来搞一个吧。
我一查项目android版本为8.1,这....,不支持深色主题功能。但是领导要求要,好吧,来开整。
方案一:设置应用黑白不同的主题方案
1.设置应用的黑白主题配置
主要是将应用的Application主题设置动态根据不同的主题来设置
public class MtkSettingsApplication extends Application {
......
public void onCreate() {
super.onCreate();
......
int themeMode = Helper.getThemeMode(context);
if(themeMode == Settings.Secure.THEME_MODE_LIGHT){
//白色主题
setTheme(R.style.Theme_Settings);
}else if(themeMode == Settings.Secure.THEME_MODE_DARK){
//黑色主题
setTheme(R.style.Theme_Settings_Dark);
}
}
......
}
MtkSettings\res\values\themes.xml
<style name="Theme.Settings" parent="Theme.SettingsBase">
<item name="preferenceTheme">@style/PreferenceTheme</item>
<item name="android:listPreferredItemHeight">72dip</item>
<item name="*android:preferenceHeaderPanelStyle">@style/PreferenceHeaderPanelSinglePane</item>
<item name="*android:preferencePanelStyle">@style/PreferencePanelSinglePane</item>
<item name="*android:preferenceListStyle">@style/PreferenceHeaderListSinglePane</item>
<item name="*android:preferenceFragmentListStyle">@style/PreferenceFragmentListSinglePane</item>
<item name="*android:preferenceFragmentPaddingSide">@dimen/settings_side_margin</item>
<item name="fingerprint_layout_theme">@style/FingerprintLayoutTheme</item>
<item name="ic_menu_moreoverflow">@*android:drawable/ic_menu_moreoverflow_holo_dark</item>
<item name="wifi_signal">@drawable/wifi_signal</item>
<item name="wifi_signal_color">?android:attr/colorAccent</item>
<item name="wifi_friction">@drawable/wifi_friction</item>
<item name="side_margin">@dimen/settings_side_margin</item>
<item name="suwListItemIconColor">?android:attr/colorAccent</item>
<!-- Redefine the ActionBar style for contentInsetStart -->
<item name="android:actionBarStyle">@style/Theme.ActionBar</item>
<item name="switchBarTheme">@style/ThemeOverlay.SwitchBar.Settings</item>
<item name="preferenceBackgroundColor">@drawable/preference_background</item>
<!-- For all Alert Dialogs -->
<item name="android:alertDialogTheme">@style/Theme.AlertDialog</item>
<item name="*android:lockPatternStyle">@style/LockPatternStyle.Setup</item>
<!-- For battery status icons in -->
<item name="batteryGoodColor">@color/battery_good_color_light</item>
<item name="batteryMaybeColor">@color/battery_maybe_color_light</item>
<item name="batteryBadColor">@color/battery_bad_color_light</item>
</style>
<style name="Theme.Settings_Dark" parent="Theme.SettingsBase">
<item name="preferenceTheme">@style/PreferenceTheme</item>
<item name="android:listPreferredItemHeight">72dip</item>
<item name="*android:preferenceHeaderPanelStyle">@style/PreferenceHeaderPanelSinglePane</item>
<item name="*android:preferencePanelStyle">@style/PreferencePanelSinglePane</item>
<item name="*android:preferenceListStyle">@style/PreferenceHeaderListSinglePane</item>
<item name="*android:preferenceFragmentListStyle">@style/PreferenceFragmentListSinglePane</item>
<item name="*android:preferenceFragmentPaddingSide">@dimen/settings_side_margin</item>
<item name="fingerprint_layout_theme">@style/FingerprintLayoutTheme</item>
<item name="ic_menu_moreoverflow">@*android:drawable/ic_menu_moreoverflow_holo_dark</item>
<item name="wifi_signal">@drawable/wifi_signal</item>
<item name="wifi_signal_color">?android:attr/colorAccent</item>
<item name="wifi_friction">@drawable/wifi_friction</item>
<item name="side_margin">@dimen/settings_side_margin</item>
<item name="suwListItemIconColor">?android:attr/colorAccent</item>
<!-- Redefine the ActionBar style for contentInsetStart -->
<item name="android:actionBarStyle">@style/Theme.ActionBar_Dark</item>
<item name="switchBarTheme">@style/ThemeOverlay.SwitchBar.Settings_Dark</item>
<item name="preferenceBackgroundColor">@*android:color/black</item>
<!-- For all Alert Dialogs -->
<item name="android:alertDialogTheme">@style/Theme.AlertDialog_Dark</item>
<item name="*android:lockPatternStyle">@style/LockPatternStyle.Setup</item>
<!-- For battery status icons in -->
<item name="batteryGoodColor">@color/battery_good_color_light</item>
<item name="batteryMaybeColor">@color/battery_maybe_color_light</item>
<item name="batteryBadColor">@color/battery_bad_color_light</item>
<item name="*android:isLightTheme">false</item>
<item name="android:windowBackground">@*android:color/black</item>
<item name="android:colorPrimary">@*android:color/primary_device_default_settings</item>
<item name="android:colorPrimaryDark">@*android:color/primary_dark_device_default_settings</item>
<item name="android:colorSecondary">@*android:color/secondary_device_default_settings</item>
<item name="android:colorAccent">@*android:color/accent_device_default_dark</item>
<item name="android:colorControlNormal">?android:attr/textColorPrimary</item>
<item name="android:colorBackgroundFloating">@*android:color/material_grey_900</item>
<item name="android:panelColorBackground">@*android:color/material_grey_800</item>
<item name="android:background">@*android:color/material_grey_800</item>
<item name="android:colorPrimary">@*android:color/white</item>
<item name="android:colorPrimaryDark">@*android:color/white</item>
<item name="android:colorSecondary">@*android:color/white</item>
<item name="android:colorForeground">@*android:color/foreground_material_dark</item>
<item name="android:colorForegroundInverse">@*android:color/foreground_material_light</item>
<item name="android:colorBackground">@*android:color/material_grey_900</item>
<item name="android:colorBackgroundFloating">@*android:color/background_floating_material_dark</item>
<item name="android:colorBackgroundCacheHint">@*android:color/background_cache_hint_selector_material_dark</item>
<item name="android:colorError">@*android:color/error_color_material_dark</item>
<item name="android:textColorPrimary">@*android:color/text_color_primary</item>
<item name="android:textColorPrimaryInverse">@*android:color/primary_text_material_light</item>
<item name="android:textColorPrimaryDisableOnly">@*android:color/primary_text_disable_only_material_dark</item>
<item name="android:textColorSecondary">@*android:color/text_color_secondary</item>
<item name="android:textColorSecondaryInverse">@*android:color/secondary_text_material_light</item>
<item name="android:textColorTertiary">@*android:color/secondary_text_material_dark</item>
<item name="android:textColorTertiaryInverse">@*android:color/secondary_text_material_light</item>
<item name="android:textColorHint">@*android:color/hint_foreground_material_dark</item>
<item name="android:textColorHintInverse">@*android:color/hint_foreground_material_light</item>
<item name="android:textColorHighlight">@*android:color/highlighted_text_material</item>
<item name="android:textColorHighlightInverse">@*android:color/highlighted_text_material</item>
<item name="android:textColorAlertDialogListItem">@*android:color/white</item>
<item name="android:textCheckMark">@*android:drawable/indicator_check_mark_dark</item>
<item name="android:textCheckMarkInverse">@*android:drawable/indicator_check_mark_light</item>
<item name="android:colorControlHighlight">@*android:color/ripple_material_dark</item>
<item name="android:colorButtonNormal">@*android:color/btn_default_material_dark</item>
<item name="android:textColor">@*android:color/white</item>
<item name="android:selectableItemBackground">@*android:color/material_grey_800</item>
</style>
2.设置应用对应Activity的黑白主题配置
主要是将应用对应Activity的动态根据不同的黑白主题来设置,如SettingsActivity:
public class SettingsActivity extends SettingsDrawerActivity
implements PreferenceManager.OnPreferenceTreeClickListener,
PreferenceFragment.OnPreferenceStartFragmentCallback,
ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
.....
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
.....
int themeMode = Helper.getThemeMode(getApplicationContext());
if(themeMode == android.provider.Settings.Secure.THEME_MODE_LIGHT){
//白色主题
setTheme(R.style.Theme_SubSettings);
}else if(themeMode == android.provider.Settings.Secure.THEME_MODE_DARK){
//黑色主题
setTheme(R.style.Theme_SubSettings_Dark);
}
.....
}
.....
}
MtkSettings\res\values\themes.xml
<style name="Theme.SubSettings" parent="Theme.Settings">
<!-- Redefine the ActionBar style for contentInsetStart -->
<item name="android:actionBarStyle">@style/Theme.ActionBar.SubSettings</item>
<item name="switchBarTheme">@style/ThemeOverlay.SwitchBar.Settings</item>
</style>
<style name="Theme.SubSettings_Dark" parent="Theme.Settings">
<item name="android:actionBarStyle">@style/Theme.ActionBar.SubSettings_Dark</item>
<item name="switchBarTheme">@style/ThemeOverlay.SwitchBar.Settings_Dark</item>
<item name="*android:isLightTheme">false</item>
<item name="android:windowBackground">@*android:color/black</item>
<item name="android:colorPrimary">@*android:color/primary_device_default_settings</item>
<item name="android:colorPrimaryDark">@*android:color/primary_dark_device_default_settings</item>
<item name="android:colorSecondary">@*android:color/secondary_device_default_settings</item>
<item name="android:colorAccent">@*android:color/accent_device_default_dark</item>
<item name="android:colorControlNormal">?android:attr/textColorPrimary</item>
<item name="android:colorBackgroundFloating">@*android:color/material_grey_900</item>
<item name="android:panelColorBackground">@*android:color/material_grey_800</item>
<item name="android:background">@*android:color/material_grey_800</item>
<item name="android:colorPrimary">@*android:color/white</item>
<item name="android:colorPrimaryDark">@*android:color/white</item>
<item name="android:colorSecondary">@*android:color/white</item>
<item name="android:colorForeground">@*android:color/foreground_material_dark</item>
<item name="android:colorForegroundInverse">@*android:color/foreground_material_light</item>
<item name="android:colorBackground">@*android:color/material_grey_900</item>
<item name="android:colorBackgroundFloating">@*android:color/background_floating_material_dark</item>
<item name="android:colorBackgroundCacheHint">@*android:color/background_cache_hint_selector_material_dark</item>
<item name="android:colorError">@*android:color/error_color_material_dark</item>
<item name="android:textColorPrimary">@*android:color/text_color_primary</item>
<item name="android:textColorPrimaryInverse">@*android:color/primary_text_material_light</item>
<item name="android:textColorPrimaryDisableOnly">@*android:color/primary_text_disable_only_material_dark</item>
<item name="android:textColorSecondary">@*android:color/text_color_secondary</item>
<item name="android:textColorSecondaryInverse">@*android:color/secondary_text_material_light</item>
<item name="android:textColorTertiary">@*android:color/secondary_text_material_dark</item>
<item name="android:textColorTertiaryInverse">@*android:color/secondary_text_material_light</item>
<item name="android:textColorHint">@*android:color/hint_foreground_material_dark</item>
<item name="android:textColorHintInverse">@*android:color/hint_foreground_material_light</item>
<item name="android:textColorHighlight">@*android:color/highlighted_text_material</item>
<item name="android:textColorHighlightInverse">@*android:color/highlighted_text_material</item>
<item name="android:textColorAlertDialogListItem">@*android:color/white</item>
<item name="android:textCheckMark">@*android:drawable/indicator_check_mark_dark</item>
<item name="android:textCheckMarkInverse">@*android:drawable/indicator_check_mark_light</item>
<item name="android:colorControlHighlight">@*android:color/ripple_material_dark</item>
<item name="android:colorButtonNormal">@*android:color/btn_default_material_dark</item>
<item name="android:textColor">@*android:color/white</item>
<item name="android:textColorSecondary">@*android:color/white</item>
<item name="android:selectableItemBackground">@*android:color/material_grey_800</item>
</style>
3.样式中的item说明
上面这些样式中的item,特别多,各有各有配置意义,那这些item是从那里来的呢?
以textColorHighlightInverse为例:
3.1 定义textColorPrimaryInverseDisableOnly:
./frameworks/base/core/res/res/values/attrs.xml
<attr name="textColorHighlightInverse" format="reference|color" />
./frameworks/base/core/res/res/values/public.xml
<public type="attr" name="textColorHighlightInverse" id="0x0101034f" />
3.2 在framework中来使用item定义对应Theme:
frameworks\base\core\res\res\values\themes.xml
<style name="Theme">
<item name="isLightTheme">false</item>
<item name="textColorHighlightInverse">@color/highlighted_text_light</item>
......
</style>
3.3 在app应用中,我们再overlay此值:
<style name="Theme.Settings_Dark" parent="Theme.SettingsBase">
......
<item name="android:textColorHighlightInverse">@*android:color/highlighted_text_material</item>
......
</style>
事实上,主题细节上的item把握和区分,才是这个知识点的核心。
3.4 对于我们来说,主题的item详细列表信息,主要是在文件
frameworks\base\core\res\res\values\styles.xml
frameworks\base\core\res\res\values\styles_device_defaults.xml
frameworks\base\core\res\res\values\styles_holo.xml
frameworks\base\core\res\res\values\styles_leanback.xml
frameworks\base\core\res\res\values\styles_material.xml
frameworks\base\core\res\res\values\themes.xml
frameworks\base\core\res\res\values\themes_device_defaults.xml
frameworks\base\core\res\res\values\themes_holo.xml
frameworks\base\core\res\res\values\themes_leanback.xml
frameworks\base\core\res\res\values\themes_material.xml
在这以frameworks\base\core\res\res\values\themes.xml文件中定义的Theme(黑底白字)为例,列一些item:
<style name="Theme">
<item name="isLightTheme">false</item>
<item name="colorForeground">@color/bright_foreground_dark</item>
<item name="colorForegroundInverse">@color/bright_foreground_dark_inverse</item>
<item name="colorBackground">@color/background_dark</item>
<item name="colorBackgroundFloating">?attr/colorBackground</item>
<item name="colorBackgroundCacheHint">?attr/colorBackground</item>
<item name="disabledAlpha">0.5</item>
<item name="primaryContentAlpha">@dimen/primary_content_alpha_material_dark</item>
<item name="secondaryContentAlpha">@dimen/secondary_content_alpha_material_dark</item>
<item name="backgroundDimAmount">0.6</item>
<item name="colorError">@color/red</item>
<!-- Text styles -->
<item name="textAppearance">@style/TextAppearance</item>
<item name="textAppearanceInverse">@style/TextAppearance.Inverse</item>
<item name="textColorPrimary">@color/primary_text_dark</item>
<item name="textColorPrimaryInverse">@color/primary_text_light</item>
<item name="textColorPrimaryActivated">@color/primary_text_dark</item>
<item name="textColorPrimaryDisableOnly">@color/primary_text_dark_disable_only</item>
<item name="textColorPrimaryInverseDisableOnly">@color/primary_text_light_disable_only</item>
<item name="textColorPrimaryInverseNoDisable">@color/primary_text_light_nodisable</item>
<item name="textColorPrimaryNoDisable">@color/primary_text_dark_nodisable</item>
<item name="textColorSecondary">@color/secondary_text_dark</item>
<item name="textColorSecondaryInverse">@color/secondary_text_light</item>
<item name="textColorSecondaryActivated">@color/secondary_text_dark</item>
<item name="textColorSecondaryNoDisable">@color/secondary_text_dark_nodisable</item>
<item name="textColorSecondaryInverseNoDisable">@color/secondary_text_light_nodisable</item>
<item name="textColorTertiary">@color/tertiary_text_dark</item>
<item name="textColorTertiaryInverse">@color/tertiary_text_light</item>
<item name="textColorHint">@color/hint_foreground_dark</item>
<item name="textColorHintInverse">@color/hint_foreground_light</item>
<item name="textColorHighlight">@color/highlighted_text_dark</item>
<item name="textColorHighlightInverse">@color/highlighted_text_light</item>
<item name="textColorLink">@color/link_text_dark</item>
<item name="textColorLinkInverse">@color/link_text_light</item>
<item name="textColorSearchUrl">@color/search_url_text</item>
<item name="textColorAlertDialogListItem">@color/primary_text_light_disable_only</item>
<item name="textAppearanceLarge">@style/TextAppearance.Large</item>
<item name="textAppearanceMedium">@style/TextAppearance.Medium</item>
<item name="textAppearanceSmall">@style/TextAppearance.Small</item>
<item name="textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item>
<item name="textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
<item name="textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
<item name="textAppearanceSearchResultTitle">@style/TextAppearance.SearchResult.Title</item>
<item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.SearchResult.Subtitle</item>
<item name="textAppearanceEasyCorrectSuggestion">@style/TextAppearance.EasyCorrectSuggestion</item>
<item name="textAppearanceMisspelledSuggestion">@style/TextAppearance.MisspelledSuggestion</item>
<item name="textAppearanceAutoCorrectionSuggestion">@style/TextAppearance.AutoCorrectionSuggestion</item>
<item name="textAppearanceButton">@style/TextAppearance.Widget.Button</item>
<item name="editTextColor">@color/primary_text_light</item>
<item name="editTextBackground">@drawable/edit_text</item>
<item name="candidatesTextStyleSpans">@string/candidates_style</item>
<item name="textCheckMark">@drawable/indicator_check_mark_dark</item>
<item name="textCheckMarkInverse">@drawable/indicator_check_mark_light</item>
<item name="textAppearanceLargePopupMenu">@style/TextAppearance.Widget.PopupMenu.Large</item>
<item name="textAppearanceSmallPopupMenu">@style/TextAppearance.Widget.PopupMenu.Small</item>
......
</style>
方案二:各个应用中各个控件实时根据黑白主题的来更新方案
此方案主要是根据应用主题改变时,各控件(如TextView,ImageView等)来实时的更新。
此方案做为一个常规方法,主要是处理一些因为自己客制化导致不能和主题保持一致的UI界面显示效果。
此方案常规,事实上也是最有效的。
方案三:SystemUI应用的黑白主题动态切换
此应用黑白主题动态切换,采用的方案是RRO替换资源文件
1 定义SysuiDarkThemeOverlay的要替换的不同主题的资源apk
frameworks\base\packages\overlays\SysuiDarkThemeOverlay
最核心的就是定义此apk应用overlay应用名,如(com.android.systemui):
frameworks\base\packages\overlays\SysuiDarkThemeOverlay\AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.systemui.theme.dark"
android:versionCode="1"
android:versionName="1.0">
<overlay android:targetPackage="com.android.systemui" android:priority="1"/>
<application android:label="@string/sysui_overlay_dark" android:hasCode="false"/>
</manifest>
然后在这个应用的res中客制化不同主题的资源文件。
2 在systemui应用中设置不同主题的资源apk
SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
......
private IOverlayManager mOverlayManager;
//初始化
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
/**
* Switches theme from light to dark and vice-versa.
*/
protected void updateTheme() {
......
int themeMode = Settings.Secure.getInt(mContext.getContentResolver(), "theme_mode", 2);
final boolean useDarkTheme = themeMode == Settings.Secure.THEME_MODE_DARK;
if (isUsingDarkTheme() != useDarkTheme) {
mUiOffloadThread.submit(() -> {
try {
//这个就是切换成黑色主题
mOverlayManager.setEnabled("com.android.systemui.theme.dark",
useDarkTheme, mLockscreenUserManager.getCurrentUserId());
} catch (RemoteException e) {
Log.w(TAG, "Can't change theme", e);
}
});
}
}
总结
通过上面三种方案,基本上可以实现各个应用的黑白主题动态切换。
个人经验,推荐方案一,方案二。