如果在网上搜换肤,方案五花八门,但是根据app的需求,以及无设计师的情况下,基本上简约风格app,这种风格下只需要几个颜色就行了,根本不需要动态从磁盘加载皮肤apk,而且通过反射操作侵入性太强,因此attr大法才是最适合目前的我所做的app实现。
网上的换肤方法侵入性太强,而纯色app不需要各种花式的皮肤,基本上2三套颜色就行了,主色,次色,而其他则非黑即白。深色模式实现就更简单了,用着色tint就实现了。
经过了几天的研究发现,动态修改setTheme
是有bug的, bug就是状态栏颜色和actionbar在未在activity定义attr背景的情况下实现修改actionbar的就有这个bug,2015年在stackoverflow网站上就有人提出解决方法,这个解决方法是没有办法的方法,需要在baseactivity进行判断,如果有actionbar就设置actionbar颜色,
以及设置状态栏颜色。
达到的效果
在颜色选择器中是可以直接使用实现的属性?attr
而在drawable,则需要包裹一层drawable引用color,直接使用color
属性也是会报错的,
颜色主题需要注意的事项
drawable要使用颜色属性只能包裹一层shape
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/defaultThemeColorSecond"/>
</shape>
第三方自定义ui 有的是只支持颜色不支持attr的。则需要进行额外适配处理,
其处理方式就是根据属性查找对应的颜色 或者id (colorres)
颜色属性找颜色
public static int attrFetchColor(int attrColor) {
int[] attribute = new int[]{attrColor};
TypedArray array = AppContext.getInstance().getTheme().obtainStyledAttributes(attribute);
int color = array.getColor(0, AppContext.getInstance().getResources().getColor(R.color.themeColor));
array.recycle();
return color;
}
查找id
public static int attrFetchColorId(int attrColor) {
int[] attribute = new int[]{attrColor};
TypedArray array = AppContext.getInstance().getTheme().obtainStyledAttributes(attribute);
int colorID = array.getResourceId(0, R.color.themeColor);
array.recycle();
return colorID;
}
在baseactivity onCreate中修改状态栏颜色
public static void updateActionBarAndStatusColor(Activity activity) {
if (activity instanceof AppCompatActivity) {
AppCompatActivity appCompatActivity = (AppCompatActivity) activity;
ActionBar supportActionBar = appCompatActivity.getSupportActionBar();
if (supportActionBar != null) {
int i = AppUtils.attrFetchColor(R.attr.defaultThemeColor);
supportActionBar.setBackgroundDrawable(new ColorDrawable(i));
}
}
android.app.ActionBar actionBar = activity.getActionBar();
if (actionBar != null) {
int color = AppUtils.attrFetchColor(R.attr.defaultThemeColor);
actionBar.setBackgroundDrawable(new ColorDrawable(color));
}
if (Build.VERSION.SDK_INT >= 21) {
// getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
// getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
int statusBarBackground = AppUtils.attrFetchColor(R.attr.statusBarBackground);
Window window = activity.getWindow();
// window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
// window.setStatusBarColor(backgroundColor);
window.setStatusBarColor(statusBarBackground);
}
}
适配起来挺快的,通过各种批量替换
但是需要注意的是,第三方对颜色适配做的很烂,哪怕大家众所周知的smartrefresh也不支持属性引用颜色。 因此主要的适配就是处理第三方框架的问题,
这个适配工作基本上一天就搞定了。
另外花的实际是纠结研究为啥actionbar和状态栏颜色通过setTheme无效花费了很多时间,在attach里面替换,以及研究theme的各种方法,以及引用再引用的方式也解决不了问题,
最后既然无效我只能另外走方法了,强制设置。
attr.xml定义
<attr name="defaultThemeColor" format="reference|color"></attr>
<attr name="defaultThemeColorSecond" format="reference|color"></attr>
其实现在每一个主题都实现了,
下面附上theme.xml 仔细看 都实现了defaultThemeColor defaultThemeColorSecond 属性,
<style name="Theme.MyApplication.Blue.NoActionBar" parent="Theme.MyApplication.Blue">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:actionMenuTextColor">@color/white_no_night</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.MyApplication.Yellow" parent="Theme.MaterialComponents.Light.DarkActionBar">
<item name="defaultThemeColor">@color/themeColorYellow</item>
<item name="defaultThemeColorSecond">@color/purple_200</item>
<item name="android:buttonStyle">@style/Widget.AppCompat.Button.Small</item>
<item name="colorButtonNormal">@color/themeColorYellow</item>
<!-- <style name="Theme.MyApplication" parent="Theme.MaterialComponents.Light.DarkActionBar">-->
<!-- Primary brand color. -->
<item name="colorPrimary">@color/themeColorYellow</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariantYellow</item>
<item name="colorOnPrimary">@color/colorOnPrimaryYellow</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/colorSecondary</item>
<!-- <item name="colorSecondary">@color/teal_200</item>-->
<item name="colorSecondaryVariant">@color/colorSecondaryVariant</item>
<item name="colorOnSecondary">@color/colorOnSecondary</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">@color/colorPrimaryVariantYellow</item>
<item name="controlBackground">@color/themeColorYellow</item>
<item name="statusBarBackground">@color/themeColorYellow</item>
</style>
<style name="Theme.MyApplication.Yellow.NoActionBar" parent="Theme.MyApplication.Yellow">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:actionMenuTextColor">@color/white_no_night</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.MyApplication.GREEN" parent="Theme.MaterialComponents.Light.DarkActionBar">
<item name="defaultThemeColor">@color/themeColorGreen</item>
<item name="defaultThemeColorSecond">@color/purple_200</item>
<item name="android:buttonStyle">@style/Widget.AppCompat.Button.Small</item>
<item name="colorButtonNormal">@color/themeColorGreen</item>
<!-- <style name="Theme.MyApplication" parent="Theme.MaterialComponents.Light.DarkActionBar">-->
<!-- Primary brand color. -->
<item name="colorPrimary">@color/themeColorGreen</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariantGreen</item>
<item name="colorOnPrimary">@color/colorOnPrimaryGreen</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/colorSecondary</item>
<!-- <item name="colorSecondary">@color/teal_200</item>-->
<item name="colorSecondaryVariant">@color/colorSecondaryVariant</item>
<item name="colorOnSecondary">@color/colorOnSecondary</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="lollipop">
@color/colorPrimaryVariantGreen
</item>
<item name="statusBarBackground">@color/themeColorGreen
</item>
</style>
<style name="Theme.MyApplication.GREEN.NoActionBar" parent="Theme.MyApplication.GREEN">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:actionMenuTextColor">@color/white_no_night</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.MyApplication.PURPLE" parent="Theme.MaterialComponents.Light.DarkActionBar">
<item name="defaultThemeColor">@color/themeColorRed</item>
<item name="defaultThemeColorSecond">@color/purple_200</item>
<item name="android:buttonStyle">@style/Widget.AppCompat.Button.Small</item>
<item name="colorButtonNormal">@color/themeColorRed</item>
<!-- <style name="Theme.MyApplication" parent="Theme.MaterialComponents.Light.DarkActionBar">-->
<!-- Primary brand color. -->
<item name="colorPrimary">@color/themeColorRed</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariantRed</item>
<item name="colorOnPrimary">@color/colorOnPrimaryRed</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/colorSecondary</item>
<!-- <item name="colorSecondary">@color/teal_200</item>-->
<item name="colorSecondaryVariant">@color/colorSecondaryVariant</item>
<item name="colorOnSecondary">@color/colorOnSecondary</item>
<!-- Status bar color. -->
<item name="statusBarBackground">@color/themeColorRed</item>
<item name="android:statusBarColor">@color/colorPrimaryVariantRed</item>
</style>
<style name="Theme.MyApplication.PURPLE.NoActionBar" parent="Theme.MyApplication.PURPLE">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:actionMenuTextColor">@color/white_no_night</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.MyApplication.DefaultBlue" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- <style name="Theme.MyApplication" parent="Theme.MaterialComponents.Light.DarkActionBar">-->
<!-- Primary brand color. -->
<item name="defaultThemeColor">@color/themeColor</item>
<item name="defaultThemeColorSecond">@color/purple_200</item>
<!-- style设置没效果代码批量设置-->
<item name="colorButtonNormal">?attr/defaultThemeColorSecond</item>
<item name="android:elevation">0dp</item>
<!-- hide shadow-->
<item name="android:windowContentOverlay">@null</item>
<!-- Primary brand color. -->
<item name="colorPrimary">?attr/defaultThemeColor</item>
<item name="colorPrimaryVariant">?attr/defaultThemeColor</item>
<item name="colorOnPrimary">@color/colorOnPrimary</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/colorSecondary</item>
<item name="colorSecondaryVariant">@color/colorSecondaryVariant</item>
<item name="colorOnSecondary">@color/colorOnSecondary</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/defaultThemeColor</item>
<item name="statusBarBackground">@color/themeColorBlue</item>
<!-- Customize your theme here. -->
</style>
<style name="Theme.MyApplication.BLACK" parent="Theme.MaterialComponents.Light.DarkActionBar">
<item name="defaultThemeColor">@color/themeColorBlack</item>
<item name="defaultThemeColorSecond">@color/purple_200</item>
<item name="android:buttonStyle">@style/Widget.AppCompat.Button.Small</item>
<item name="colorButtonNormal">@color/themeColorBlack</item>
<!-- <style name="Theme.MyApplication" parent="Theme.MaterialComponents.Light.DarkActionBar">-->
<!-- Primary brand color. -->
<item name="colorPrimary">@color/themeColorSecondBlack</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariantBlack</item>
<item name="colorOnPrimary">@color/colorOnPrimaryBlack</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/colorSecondary</item>
<!-- <item name="colorSecondary">@color/teal_200</item>-->
<item name="colorSecondaryVariant">@color/themeColorSecondBlack</item>
<item name="colorOnSecondary">@color/colorOnSecondary</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="lollipop"> @color/colorPrimaryVariantBlack</item>
<item name="statusBarBackground">@color/themeColorBlack</item>
</style>
<style name="Theme.MyApplication.BLACK.NoActionBar" parent="Theme.MyApplication.BLACK">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:actionMenuTextColor">@color/white_no_night</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.MyApplication.DefaultBlue.NoActionBar" parent="Theme.MyApplication.DefaultBlue">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:actionMenuTextColor">@color/white_no_night</item>
<item name="windowNoTitle">true</item>
</style>
color实现
<color name="themeColor" >@color/light_blue_A200</color>
<color name="themeColorSecond" >@color/purple_200</color>
<color name="colorPrimary">@color/purple_200</color>
<color name="colorPrimaryVariant">@color/black_overlay</color>
<color name="colorOnPrimary">@color/black</color>
<color name="colorSecondary">@color/teal_200</color>
<color name="colorSecondaryVariant">@color/teal_200</color>
<color name="colorOnSecondary">@color/black</color>
<color name="themeColorRed" >@color/light_blue_A200</color>
<color name="themeColorSecondRed" >@color/purple_200</color>
<color name="colorPrimaryRed">@color/purple_200</color>
<color name="colorPrimaryVariantRed">@color/black_overlay</color>
<color name="colorOnPrimaryRed">@color/black</color>
<color name="themeColorYellow" >@color/light_blue_A200</color>
<color name="themeColorSecondYellow" >@color/purple_200</color>
<color name="colorPrimaryYellow">@color/purple_200</color>
<color name="colorPrimaryVariantYellow">@color/black_overlay</color>
<color name="colorOnPrimaryYellow">@color/black</color>
<color name="themeColorGreen" >@color/light_blue_A200</color>
<color name="themeColorSecondGreen" >@color/purple_200</color>
<color name="colorPrimaryGreen">@color/purple_200</color>
<color name="colorPrimaryVariantGreen">@color/black_overlay</color>
<color name="colorOnPrimaryGreen">@color/black</color>
<color name="themeColorBlue" >@color/light_blue_A200</color>
<color name="themeColorSecondBlue" >@color/purple_200</color>
<color name="colorPrimaryBlue">@color/purple_200</color>
<color name="colorPrimaryVariantBlue">@color/black_overlay</color>
<color name="colorOnPrimaryBlue">@color/black</color>
<color name="themeColorBlack" >@color/light_blue_A200</color>
<color name="themeColorSecondBlack" >@color/purple_200</color>
<color name="colorPrimaryBlack">@color/purple_200</color>
<color name="colorPrimaryVariantBlack">@color/black_overlay</color>
<color name="colorOnPrimaryBlack">@color/black</color>
深色主题values-night themes.xml
把正常颜色的拷贝过来然后指定颜色就行了,
修改主题的代码 baseactivityonCreate调用在setContentView之前
fun applySetting(context: Context?):Int {
var themeId = 0;
// 检查主题
val themeModeInt = getSpfThemeMode()
val themeMode = ThemeMode.parseOfInt(themeModeInt)
var containActionBar: Boolean = false;
var actName: String = "";
if (context is AppCompatActivity) {
var act: AppCompatActivity = (context as AppCompatActivity);
if (act.supportActionBar is WindowDecorActionBar) {
containActionBar = true;
}
actName =
context.javaClass.simpleName + " ,是否已支持actionBar " + containActionBar;
}
if (themeMode == ThemeMode.BLUE_THEME) {
if (containActionBar) {
themeId = R.style.Theme_MyApplication_Blue
} else {
themeId = R.style.Theme_MyApplication_Blue_NoActionBar
}
Log.w("ThemeStyle", "蓝色主题 $actName");
} else if (themeMode == ThemeMode.YELLOW_THEME) {
if (containActionBar) {
themeId = R.style.Theme_MyApplication_Yellow
} else {
themeId = R.style.Theme_MyApplication_Yellow_NoActionBar
}
Log.w("ThemeStyle", "黄色主题 $actName");
} else if (themeMode == ThemeMode.PURPLE_THEME) {
Log.w("ThemeStyle", "紫色主题 $actName");
if (containActionBar) {
themeId = R.style.Theme_MyApplication_PURPLE
} else {
themeId = R.style.Theme_MyApplication_PURPLE_NoActionBar
}
} else if (themeMode == ThemeMode.GREEN_THEME) {
Log.w("ThemeStyle", "绿色主题 $actName");
if (containActionBar) {
themeId = R.style.Theme_MyApplication_GREEN
} else {
themeId = R.style.Theme_MyApplication_GREEN_NoActionBar;
}
} else if (themeMode == ThemeMode.BLACK_THEME) {
Log.w("ThemeStyle", "黑色主题 $actName");
if (containActionBar) {
themeId = R.style.Theme_MyApplication_BLACK;
} else {
themeId = R.style.Theme_MyApplication_BLACK_NoActionBar;
}
} else {
Log.w("ThemeStyle", "其他主题模式 $actName");
}
if(themeId!=0){
context?.setTheme(themeId)
// return themeId;
}
AppCompatDelegate.setDefaultNightMode(
when (themeMode) {
ThemeMode.MODE_ALWAYS_ON -> AppCompatDelegate.MODE_NIGHT_YES
ThemeMode.MODE_ALWAYS_OFF -> AppCompatDelegate.MODE_NIGHT_NO
ThemeMode.MODE_FOLLOW_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
else -> {
AppCompatDelegate.MODE_NIGHT_NO
}
}
)
return themeId
}
如果不为0,
if (color != 0) {
// getTheme().applyStyle(color, true);
AppUtils.updateActionBarAndStatusColor(this);
}
另外补充一点,按钮这些一般我都没有修改在每一个activity xml设置的,遵循material design以及theme的写法,全局就可以进行操作的,在<item name="colorPrimary">
就完成了适配,
主要的适配工作还是处理selector的颜色以及第三方框架引用了颜色的情况,另外处理深色兼容的适配。