rea 文件下values 里面 colors.xml
setTheme方式
<resources> <color name="colorPrimary">#3F51B5color> <color name="colorPrimaryDark">#303F9Fcolor> <color name="colorAccent">#FF4081color> <color name="nightColorPrimary">#3b3b3bcolor> <color name="nightColorPrimaryDark">#383838color> <color name="nightColorAccent">#a72b55color> resources>
styles.xml
<resources> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimaryitem> <item name="colorPrimaryDark">@color/colorPrimaryDarkitem> <item name="colorAccent">@color/colorAccentitem> <item name="android:textColor">@android:color/blackitem> <item name="mainBackground">@android:color/whiteitem> style> <style name="NightAppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/nightColorPrimaryitem> <item name="colorPrimaryDark">@color/nightColorPrimaryDarkitem> <item name="colorAccent">@color/nightColorAccentitem> <item name="android:textColor">@android:color/whiteitem> <item name="mainBackground">@color/nightColorPrimaryDarkitem> style> resources>
values里建一个attrs.xml
<resources> <attr name="mainBackground" format="color|reference">attr> resources> 布局
代码
public class MainActivity extends AppCompatActivity {
//默认的日间模式
private int theme = R.style.AppTheme;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//恢复数据 做判空
if (savedInstanceState != null) {
theme = savedInstanceState.getInt(“theme”);
//设置主题 此方法必须在setContentView()之前调用
setTheme(theme);
}
setContentView(R.layout.activity_main);
//找控件
Button bt_change = (Button) findViewById(R.id.bt_change);
bt_change.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//切换日夜间模式
theme = (theme == R.style.AppTheme) ? R.style.NightAppTheme : R.style.AppTheme;
//重新创建
recreate();
}
});
}
//保存数据
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(“theme”, theme);
}
//恢复数据
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
theme = savedInstanceState.getInt(“theme”);
}
}
第二种:
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // 默认设置为日间模式 AppCompatDelegate.setDefaultNightMode( AppCompatDelegate.MODE_NIGHT_NO); } } |
- MODE_NIGHT_NO: 使用亮色(light)主题,不使用夜间模式;
- MODE_NIGHT_YES:使用暗色(dark)主题,使用夜间模式;
- MODE_NIGHT_AUTO:根据当前时间自动切换 亮色(light)/暗色(dark)主题;
- MODE_NIGHT_FOLLOW_SYSTEM(默认选项):设置为跟随系统,通常为 MODE_NIGHT_NO
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn_theme = (Button) findViewById(R.id.btn_theme); btn_theme.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; getDelegate().setLocalNightMode(currentNightMode == Configuration.UI_MODE_NIGHT_NO ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); // 同样需要调用recreate方法使之生效 recreate(); } }); } } |
recreate() 使之生效。而让 Activity 重新创建就必须涉及到一些状态的保存。这就增加了一些难度。所以,我们一起来看看第三种解决方法。
|
public class ThemeManager { // 默认是日间模式 private static ThemeMode mThemeMode = ThemeMode.DAY; // 主题模式监听器 private static List // 夜间资源的缓存,key : 资源类型, 值 private static HashMap // 夜间模式资源的后缀,比如日件模式资源名为:R.color.activity_bg, 那么夜间模式就为 :R.color.activity_bg_night private static final String RESOURCE_SUFFIX = “_night”; /** * 主题模式,分为日间模式和夜间模式 */ public enum ThemeMode { DAY, NIGHT } /** * 设置主题模式 * * @param themeMode */ public static void setThemeMode(ThemeMode themeMode) { if (mThemeMode != themeMode) { mThemeMode = themeMode; if (mThemeChangeListenerList.size() > 0) { for (OnThemeChangeListener listener : mThemeChangeListenerList) { listener.onThemeChanged(); } } } } /** * 根据传入的日间模式的resId得到相应主题的resId,注意:必须是日间模式的resId * * @param dayResId 日间模式的resId * @return 相应主题的resId,若为日间模式,则得到dayResId;反之夜间模式得到nightResId */ public static int getCurrentThemeRes(Context context, int dayResId) { if (getThemeMode() == ThemeMode.DAY) { return dayResId; } // 资源名 String entryName = context.getResources().getResourceEntryName(dayResId); // 资源类型 String typeName = context.getResources().getResourceTypeName(dayResId); HashMap // 先从缓存中去取,如果有直接返回该id if (cachedRes == null) { cachedRes = new HashMap<>(); } Integer resId = cachedRes.get(entryName + RESOURCE_SUFFIX); if (resId != null && resId != 0) { return resId; } else { //如果缓存中没有再根据资源id去动态获取 try { // 通过资源名,资源类型,包名得到资源int值 int nightResId = context.getResources().getIdentifier(entryName + RESOURCE_SUFFIX, typeName, context.getPackageName()); // 放入缓存中 cachedRes.put(entryName + RESOURCE_SUFFIX, nightResId); sCachedNightResrouces.put(typeName, cachedRes); return nightResId; } catch (Resources.NotFoundException e) { e.printStackTrace(); } } return 0; } /** * 注册ThemeChangeListener * * @param listener */ public static void registerThemeChangeListener(OnThemeChangeListener listener) { if (!mThemeChangeListenerList.contains(listener)) { mThemeChangeListenerList.add(listener); } } /** * 反注册ThemeChangeListener * * @param listener */ public static void unregisterThemeChangeListener(OnThemeChangeListener listener) { if (mThemeChangeListenerList.contains(listener)) { mThemeChangeListenerList.remove(listener); } } /** * 得到主题模式 * * @return */ public static ThemeMode getThemeMode() { return mThemeMode; } /** * 主题模式切换监听器 */ public interface OnThemeChangeListener { /** * 主题切换时回调 */ void onThemeChanged(); } } |
getCurrentThemeRes 方法了。在这里解释一下
getCurrentThemeRes 的逻辑。参数中的 dayResId 是日间模式的资源id,如果当前主题是日间模式的话,就直接返回 dayResId 。反之当前主题为夜间模式的话,先根据 dayResId 得到资源名称和资源类型。比如现在有一个资源为 R.color.colorPrimary ,那么资源名称就是 colorPrimary ,资源类型就是 color 。然后根据资源类型和资源名称去获取缓存。如果没有缓存,那么就要动态获取资源了。这里使用方法的是
- name 参数就是资源名称,不过要注意的是这里的资源名称还要加上后缀 “_night” ,也就是上面在 colors.xml 中定义的名称;
- defType 参数就是资源的类型了。比如 color,drawable等;
- defPackage 就是资源文件的包名,也就是当前 APP 的包名。
public class MainActivity extends AppCompatActivity implements ThemeManager.OnThemeChangeListener { private TextView tv; private Button btn_theme; private RelativeLayout relativeLayout; private ActionBar supportActionBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ThemeManager.registerThemeChangeListener(this); supportActionBar = getSupportActionBar(); btn_theme = (Button) findViewById(R.id.btn_theme); relativeLayout = (RelativeLayout) findViewById(R.id.relativeLayout); tv = (TextView) findViewById(R.id.tv); btn_theme.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ThemeManager.setThemeMode(ThemeManager.getThemeMode() == ThemeManager.ThemeMode.DAY ? ThemeManager.ThemeMode.NIGHT : ThemeManager.ThemeMode.DAY); } }); } public void initTheme() { tv.setTextColor(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.textColor))); btn_theme.setTextColor(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.textColor))); relativeLayout.setBackgroundColor(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.backgroundColor))); // 设置标题栏颜色 if(supportActionBar != null){ supportActionBar.setBackgroundDrawable(new ColorDrawable(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.colorPrimary)))); } // 设置状态栏颜色 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.setStatusBarColor(getResources().getColor(ThemeManager.getCurrentThemeRes(MainActivity.this, R.color.colorPrimary))); } } @Override public void onThemeChanged() { initTheme(); } @Override protected void onDestroy() { super.onDestroy(); ThemeManager.unregisterThemeChangeListener(this); } } |
initTheme() 中去重新设置 UI 的相关颜色属性值。还有别忘了要在
onDestroy() 中移除 ThemeChangeListener 。
总结:
- setTheme 方法:可以配置多套主题,比较容易上手。除了日/夜间模式之外,还可以有其他五颜六色的主题。但是需要调用 recreate() ,切换瞬间会有黑屏闪现的现象;
- UiMode 方法:优点就是 Android Support Library 中已经支持,简单规范。但是也需要调用 recreate() ,存在黑屏闪现的现象;
- 动态获取资源 id ,回调接口:该方法使用起来比前两个方法复杂,另外在回调的方法中需要设置每一项 UI 相关的属性值。但是不需要调用 recreate() ,没有黑屏闪现的现象。