1,状态栏背景更换
2,标题栏背景更换
3,标题栏字体颜色更换
4,界面背景更换
5,界面字体颜色更换
这里状态栏更换只支持android版本号大于20的
方法有两种:
方法1:
setThem(int resid)设置,缺点是每次设置完都要重启该activity,才能应用主题
android项目新建时都会在styles.xml中生成类似以下的代码
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>//标题栏颜色
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>//状态栏颜色
<item name="colorAccent">@color/colorPrimary</item>//特殊醒目的颜色
<item name="android:windowBackground">@color/windowBg</item>//背景颜色
</style>
android系统有提供Theme设置的方法setTheme(@StyleRes final int resid),对应的调用方法setThem(R.style.AppTheme)
在上边的基础上我们就可建多个Theme,然后每个Activity都可以设置成不同的Theme
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme">
我们主要是目的是在一个Activity里更换多种主题,那么我们就不在manifest中设置Theme了改为在代码中设置
public void myTheme(){
SharedPreferences sharedPreferences = context.getSharedPreferences(
APPTHEME, context.MODE_PRIVATE);
int currentTheme = sharedPreferences.getInt("theme_type", 3);
int theme ;
switch (currrentTheme) {
case 1:
theme = R.style.Theme1
break;
case 2:
theme = R.style.Theme2
break;
case 3:
theme = R.style.Theme3
break;
}
setThem(theme )
}
//选择Theme时
public void changeTheme(int theme){
SharedPreferences sharedPreferences = context.getSharedPreferences(APPTHEME, context.MODE_PRIVATE);
sharedPreferences.edit().putInt("theme_type",theme).commit();
//重启activity
}
方法2:
步骤1:layout文件把需要改变字体或背景颜色的控件做上标记
步骤2:在Activity绘制ui的时候将有标记的控件全部存储到内存中
步骤3:根据标记的类型改变控件的背景,字体等以达到更换主题的效果
当然也有异曲同工的笨方法:在Activity中把需要更改字体或背景颜色的控件实例化,存储起来,换肤时,逐个改变控件的设置
此笨方法需要在每个需要换肤的Activity中都做重复的工作,而且没有技术含量,乃程序员之大忌,所以我们继续我们这种优雅的换肤方法。
关于收集有标记的View,可以通过android 提供的setFactory(LayoutInflater inflater, LayoutInflaterFactory factory),关于该方法,android有以下说明:
/**
* Attach a custom Factory interface for creating views while using
* this LayoutInflater. This must not be null, and can only be set once;
* after setting, you can not change the factory.
*
* @see LayoutInflater#setFactory(android.view.LayoutInflater.Factory)
*/
大致意思是说这个方法在系统绘制view时用到的,仅只能使用一次
既然是在绘制view时用到,那么view的各种属性都可在这里拿到,比如grivity,layout_height等。当然我们自己做的标记也能拿到
public class MyInflaterFactory implements LayoutInflaterFactory {
public static final String TAG = "MyInflaterFactory";
public static final String NAMESPACE = "http://schemas.android.com/android/skin";
public static final String ATTR_SKIN_TYPE = "theme_type";
public static final String BackGround = "BackGround";
public static final String TextColor = "TextColor";
public static final String BackGround_Primary = "BackGround|Primary";
public static final String ColorAccent = "ColorAccent";
private Context context;
private List<View> views = new ArrayList<>();
int[] colors = {0x03a9f4,0x038dcc,0xffffff,0x84ffff};//colorPrimary,colorPrimaryDark,TextColor,windowBg
public MyInflaterFactory(){
updateStatusBar();
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
this.context = context;
// 检测当前View是否有更换皮肤的需求
// boolean isSkinEnable = attrs.getAttributeBooleanValue(NAMESPACE, ATTR_SKIN_ENABLE, false);
String changeType = attrs.getAttributeValue(NAMESPACE, ATTR_SKIN_TYPE);
if (TextUtils.isEmpty(changeType)) {
return null;//返回空就使用默认的InflaterFactory
}
View view = createView(context,name, attrs);
if (view == null) {//没有找到这个View
return null;
}
view.setTag(changeType);
views.add(view);//将有标记的控件存入数组,以便换肤
updateItemView(view,colors);//更新默认皮肤
return view;
}
public void setNewTheme(int[] colors){//外部换肤调用
this.colors = colors;
for(View view:views){
updateItemView(view,this.colors);
}
}
public int[] getThemeColors() {
return colors;
}
public int[] updateStatusBar(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
((Activity)context).getWindow().setStatusBarColor(colors[1]);//android版本号>=21改变状态栏
}
((Activity)context).getWindow().setBackgroundDrawable(new ColorDrawable(colors[3]));//改变Activity背景颜色
return colors;
}
private View createView(Context context, String name, AttributeSet attrs) {
Log.i(TAG, "createView:" + name);
View view = null;
try {
if (-1 == name.indexOf('.')) {
if ("View".equals(name)) {
view = LayoutInflater.from(context).createView(name, "android.view.", attrs);
}
if (view == null) {
view = LayoutInflater.from(context).createView(name, "android.widget.", attrs);
}
if (view == null) {
view = LayoutInflater.from(context).createView(name, "android.webkit.", attrs);
}
} else {
view = LayoutInflater.from(context).createView(name, null, attrs);
}
} catch (Exception e) {
Log.e(TAG, "error while create 【" + name + "】 : " + e.getMessage());
view = null;
}
return view;
}
@SuppressLint("NewApi")
private void updateItemView(View v,int[]colors){
String type_color = (String) v.getTag();
if(type_color.equals(BackGround)){
if (v instanceof CardView) {//这里对CardView特殊处理下
CardView cardView = (CardView) v;
cardView.setCardBackgroundColor(colors[1]);
} else {
v.setBackgroundColor(colors[1]);
}
}
else if(type_color.equals(TextColor)){
if(v instanceof TextView){
((TextView) v).setTextColor(colors[2]);
}else if (v instanceof EditText){
((EditText) v).setTextColor(colors[2]);
}
}
else if(type_color.equals(BackGround_Primary)){
v.setBackgroundColor(colors[0]);
}
if(type_color.equals(ColorAccent)){
v.setBackgroundColor(colors[2]);
}
}
}
Activity onCreate(...)方法中在调用setContentView()之前:
MyInflaterFactory factory = new MyInflaterFactory();
LayoutInflaterCompat.setFactory(getLayoutInflater(), factory);
布局文件中做标记:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:skin="http://schemas.android.com/android/skin"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="zhao.edifier.com.mynotepaper.MainActivity">
<RelativeLayout
android:id="@+id/al_title"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
skin:theme_type="BackGround|Primary"
android:elevation="10dp"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="17sp"
android:text="@string/app_name"
android:padding="8dp"
skin:theme_type="TextColor"
/>
<ImageView
android:id="@+id/iv_more"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:src="@drawable/ic_more_withe"
android:padding="16dp"
android:scaleType="centerInside"
android:layout_centerVertical="true"
android:background="@drawable/recycler_bg"
/>
</RelativeLayout>
</RelativeLayout>
根目录中的xmlns:skin="http://schemas.android.com/android/skin"
和其他skin:theme_type="XX"都是我们自己的标记
XX是 MyInflaterFactory中的
public static final String BackGround = "BackGround";
public static final String TextColor = "TextColor";
public static final String BackGround_Primary = "BackGround|Primary";
public static final String ColorAccent = "ColorAccent";
在点击按钮换肤时调用factory.setNewTheme(int[] colors);
这种发法可以不用重启activity,的缺点是收集不到最新控件的view,比如:FloatingActionButton,和toolbar,遇到这些控件都报异常,暂时只能以其他常用控件代替之