Android每个页面都有自己的主题风格,而主题样式可以在Style.xml里面自定义。自然就可以在这里面做文章,并且便于管理。
首先在attrs.xml里面定义属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="main_bg" format="reference|color"/>
<attr name="main_textcolor" format="reference|color"/>
<attr name="second_bg" format="reference|color"/>
<attr name="second_textcolor" format="reference|color"/>
</resources>
然后在style.xml里面设置相应的属性值:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="theme_1" >
<item name="main_bg">@color/bg_main_normal</item>
<item name="main_textcolor">@color/textcolor_main_normal</item>
<item name="second_bg">@color/bg_second_normal</item>
<item name="second_textcolor">@color/textcolor_second_normal</item>
</style>
<style name="theme_2">
<item name="main_bg">@color/bg_main_dark</item>
<item name="main_textcolor">@color/textcolor_main_dark</item>
<item name="second_bg">@color/bg_second_dark</item>
<item name="second_textcolor">@color/textcolor_second_dark</item>
</style>
</resources>
相应的颜色值color.xml中去定义:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="bg_main_normal">#ffffff</color>
<color name="textcolor_main_normal">#ff0000</color>
<color name="bg_main_dark">#000000</color>
<color name="textcolor_main_dark">#ffffff</color>
<color name="bg_second_normal">#0000ff</color>
<color name="textcolor_second_normal">#00ff00</color>
<color name="bg_second_dark">#ffffff</color>
<color name="textcolor_second_dark">#000000</color>
</resources>
所有的Activity都继承BaseActivity,在oncreate()创建Activity实例时,会设置该Activity的Theme(主题),然后布局文件各元素会自定获取Style.xml定义好的属性进行展示;
package derson.com.multipletheme;
import android.app.Activity;
import android.os.Bundle;
import android.os.PersistableBundle;
import derson.com.multipletheme.colorUi.util.SharedPreferencesMgr;
/** * Created by chengli on 15/6/14. */
public class BaseActivity extends Activity{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(SharedPreferencesMgr.getInt("theme", 0) == 1) {
setTheme(R.style.theme_2);
} else {
setTheme(R.style.theme_1);
}
}
}
相应的布局文件
<derson.com.multipletheme.colorUi.widget.ColorRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/main_bg"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<derson.com.multipletheme.colorUi.widget.ColorTextView
android:textColor="?attr/main_textcolor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<derson.com.multipletheme.colorUi.widget.ColorButton
android:id="@+id/btn"
android:text="换肤"
android:layout_centerInParent="true"
android:textColor="?attr/main_textcolor"
android:layout_width="100dip"
android:layout_height="80dip" />
<derson.com.multipletheme.colorUi.widget.ColorButton
android:id="@+id/btn_2"
android:layout_centerHorizontal="true"
android:text="下一页"
android:layout_below="@id/btn"
android:layout_marginTop="30dip"
android:textColor="?attr/main_textcolor"
android:layout_width="100dip"
android:layout_height="80dip" />
</derson.com.multipletheme.colorUi.widget.ColorRelativeLayout>
我们来看看MainActivity:
package derson.com.multipletheme;
import android.animation.Animator;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import derson.com.multipletheme.colorUi.util.ColorUiUtil;
import derson.com.multipletheme.colorUi.util.SharedPreferencesMgr;
import derson.com.multipletheme.colorUi.widget.ColorButton;
public class MainActivity extends BaseActivity {
ColorButton btn,btn_next;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (ColorButton)findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(SharedPreferencesMgr.getInt("theme", 0) == 1) {
SharedPreferencesMgr.setInt("theme", 0);
setTheme(R.style.theme_1);
} else {
SharedPreferencesMgr.setInt("theme", 1);
setTheme(R.style.theme_2);
}
final View rootView = getWindow().getDecorView();
if(Build.VERSION.SDK_INT >= 14) {
rootView.setDrawingCacheEnabled(true);
rootView.buildDrawingCache(true);
final Bitmap localBitmap = Bitmap.createBitmap(rootView.getDrawingCache());
rootView.setDrawingCacheEnabled(false);
if (null != localBitmap && rootView instanceof ViewGroup) {
final View localView2 = new View(getApplicationContext());
localView2.setBackgroundDrawable(new BitmapDrawable(getResources(), localBitmap));
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
((ViewGroup) rootView).addView(localView2, params);
localView2.animate().alpha(0).setDuration(400).setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
ColorUiUtil.changeTheme(rootView, getTheme());
}
@Override
public void onAnimationEnd(Animator animation) {
((ViewGroup) rootView).removeView(localView2);
localBitmap.recycle();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}).start();
}
} else {
ColorUiUtil.changeTheme(rootView, getTheme());
}
}
});
btn_next = (ColorButton)findViewById(R.id.btn_2);
btn_next.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
在MainActivity中,我们设置一个切换主题点击事件。这里我们会把主题标志保存到本地,这里用到的是SharedPreferencesMgr:
package derson.com.multipletheme.colorUi.util;
import android.content.Context;
import android.content.SharedPreferences;
/** * SharedPreferences管理类 */
public class SharedPreferencesMgr {
private static Context context;
private static SharedPreferences sPrefs;
private SharedPreferencesMgr(Context context, String fileName) {
this.context = context;
sPrefs = context.getSharedPreferences(
fileName, Context.MODE_WORLD_READABLE);
}
public static void init(Context context, String fileName) {
new SharedPreferencesMgr(context, fileName);
}
public static String fileName;
public static int getInt(String key, int defaultValue) {
return sPrefs.getInt(key, defaultValue);
}
public static void setInt(String key, int value) {
sPrefs.edit().putInt(key, value).commit();
}
public static boolean getBoolean(String key, boolean defaultValue) {
return sPrefs.getBoolean(key, defaultValue);
}
public static void setBoolean(String key, boolean value) {
sPrefs.edit().putBoolean(key, value).commit();
}
public static String getString(String key, String defaultValue) {
if (sPrefs == null)
return defaultValue;
return sPrefs.getString(key, defaultValue);
}
public static void setString(String key, String value) {
if (sPrefs == null)
return;
sPrefs.edit().putString(key, value).commit();
}
public static void clearAll() {
if (sPrefs == null)
return;
sPrefs.edit().clear().commit();
}
}
在切换主题点击事件中逻辑看起来有点复杂,我们来一起分析分析吧;
if(SharedPreferencesMgr.getInt("theme", 0) == 1) {
SharedPreferencesMgr.setInt("theme", 0);
setTheme(R.style.theme_1);
} else {
SharedPreferencesMgr.setInt("theme", 1);
setTheme(R.style.theme_2);
}
final View rootView = getWindow().getDecorView();
if(Build.VERSION.SDK_INT >= 14) {
rootView.setDrawingCacheEnabled(true);
rootView.buildDrawingCache(true);
final Bitmap localBitmap = Bitmap.createBitmap(rootView.getDrawingCache());
rootView.setDrawingCacheEnabled(false);
if (null != localBitmap && rootView instanceof ViewGroup) {
final View localView2 = new View(getApplicationContext());
localView2.setBackgroundDrawable(new BitmapDrawable(getResources(), localBitmap));
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
((ViewGroup) rootView).addView(localView2, params);
localView2.animate().alpha(0).setDuration(400).setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
ColorUiUtil.changeTheme(rootView, getTheme());
}
@Override
public void onAnimationEnd(Animator animation) {
((ViewGroup) rootView).removeView(localView2);
localBitmap.recycle();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}).start();
}
} else {
ColorUiUtil.changeTheme(rootView, getTheme());
}
这里同步改变各控件主题样式的触发操作主要由ColorUiUtil完成:
package derson.com.multipletheme.colorUi.util;
import android.app.Activity;
import android.content.res.Resources;
import android.provider.Settings;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AbsListView;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import derson.com.multipletheme.colorUi.ColorUiInterface;
/** * Created by chengli on 15/6/10. */
public class ColorUiUtil {
/** * 切换应用主题 * * @param rootView */
public static void changeTheme(View rootView, Resources.Theme theme) {
if (rootView instanceof ColorUiInterface) {
((ColorUiInterface) rootView).setTheme(theme);
if (rootView instanceof ViewGroup) {
int count = ((ViewGroup) rootView).getChildCount();
for (int i = 0; i < count; i++) {
changeTheme(((ViewGroup) rootView).getChildAt(i), theme);
}
}
if (rootView instanceof AbsListView) {
try {
Field localField = AbsListView.class.getDeclaredField("mRecycler");
localField.setAccessible(true);
Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear", new Class[0]);
localMethod.setAccessible(true);
localMethod.invoke(localField.get(rootView), new Object[0]);
} catch (NoSuchFieldException e1) {
e1.printStackTrace();
} catch (ClassNotFoundException e2) {
e2.printStackTrace();
} catch (NoSuchMethodException e3) {
e3.printStackTrace();
} catch (IllegalAccessException e4) {
e4.printStackTrace();
} catch (InvocationTargetException e5) {
e5.printStackTrace();
}
}
} else {
if (rootView instanceof ViewGroup) {
int count = ((ViewGroup) rootView).getChildCount();
for (int i = 0; i < count; i++) {
changeTheme(((ViewGroup) rootView).getChildAt(i), theme);
}
}
if (rootView instanceof AbsListView) {
try {
Field localField = AbsListView.class.getDeclaredField("mRecycler");
localField.setAccessible(true);
Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear", new Class[0]);
localMethod.setAccessible(true);
localMethod.invoke(localField.get(rootView), new Object[0]);
} catch (NoSuchFieldException e1) {
e1.printStackTrace();
} catch (ClassNotFoundException e2) {
e2.printStackTrace();
} catch (NoSuchMethodException e3) {
e3.printStackTrace();
} catch (IllegalAccessException e4) {
e4.printStackTrace();
} catch (InvocationTargetException e5) {
e5.printStackTrace();
}
}
}
}
}
这里逻辑还是比较清晰的,会去遍历所有子视图,如果实现了ColorUiInterface接口,就会通过多态形式回调给实现该接口的实体,这里一点需要注意就是如果该控件是AbsListView的实体,这里会清空ReclycleBin里面的视图缓存(为什么这样做,目前我不是很清晰);
package derson.com.multipletheme.colorUi.widget;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.View;
import derson.com.multipletheme.colorUi.ColorUiInterface;
import derson.com.multipletheme.colorUi.util.ViewAttributeUtil;
/** * Created by chengli on 15/6/8. */
public class ColorView extends View implements ColorUiInterface {
private int attr_background = -1;
public ColorView(Context context) {
super(context);
}
public ColorView(Context context, AttributeSet attrs) {
super(context, attrs);
this.attr_background = ViewAttributeUtil.getBackgroundAttibute(attrs);
}
public ColorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.attr_background = ViewAttributeUtil.getBackgroundAttibute(attrs);
}
@Override
public View getView() {
return this;
}
@Override
public void setTheme(Resources.Theme themeId) {
if(attr_background != -1) {
ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_background);
}
}
}
主要是通过setTheme去改变该空间的样式,这里就要看一下ViewAttributeUtil干了些什么:
package derson.com.multipletheme.colorUi.util;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.TextView;
import derson.com.multipletheme.colorUi.ColorUiInterface;
/** * Created by chengli on 15/6/8. */
public class ViewAttributeUtil {
public static int getAttributeValue(AttributeSet attr, int paramInt) {
int value = -1;
int count = attr.getAttributeCount();
for(int i = 0; i <count;i++) {
if(attr.getAttributeNameResource(i) == paramInt) {
String str = attr.getAttributeValue(i);
if(null != str && str.startsWith("?")) {
value = Integer.valueOf(str.substring(1,str.length())).intValue();
return value;
}
}
}
return value;
}
public static int getBackgroundAttibute(AttributeSet attr) {
return getAttributeValue(attr , android.R.attr.background);
}
public static int getCheckMarkAttribute(AttributeSet attr) {
return getAttributeValue(attr, android.R.attr.checkMark);
}
public static int getSrcAttribute(AttributeSet attr) {
return getAttributeValue(attr, android.R.attr.src);
}
public static int getTextApperanceAttribute(AttributeSet attr) {
return getAttributeValue(attr, android.R.attr.textAppearance);
}
public static int getDividerAttribute(AttributeSet attr) {
return getAttributeValue(attr, android.R.attr.divider);
}
public static int getTextColorAttribute(AttributeSet attr) {
return getAttributeValue(attr, android.R.attr.textColor);
}
public static void applyBackgroundDrawable(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
Drawable drawable = ta.getDrawable(0);
if(null != ci) {
(ci.getView()).setBackgroundDrawable(drawable);
}
ta.recycle();
}
public static void applyImageDrawable(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
Drawable drawable = ta.getDrawable(0);
if(null != ci && ci instanceof ImageView) {
((ImageView)ci.getView()).setImageDrawable(drawable);
}
ta.recycle();
}
public static void applyTextAppearance(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
int resourceId = ta.getResourceId(0,0);
if(null != ci && ci instanceof TextView) {
((TextView)ci.getView()).setTextAppearance(ci.getView().getContext(), resourceId);
}
ta.recycle();
}
public static void applyTextColor(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
int resourceId = ta.getColor(0,0);
((TextView)ci.getView()).setTextColor(resourceId);
if(null != ci && ci instanceof TextView) {
}
ta.recycle();
}
}
从代码中我们可以看出通过paramInt去获取当前主题中对应的属性值,然后根据属性值进行进行操作;
public ColorRadioButton(Context context, AttributeSet attrs) {
super(context, attrs);
this.attr_background = ViewAttributeUtil.getBackgroundAttibute(attrs);
this.attr_textAppearance = ViewAttributeUtil.getTextApperanceAttribute(attrs);
}
public ColorRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.attr_background = ViewAttributeUtil.getBackgroundAttibute(attrs);
this.attr_textAppearance = ViewAttributeUtil.getTextApperanceAttribute(attrs);
}
public static int getAttributeValue(AttributeSet attr, int paramInt) {
int value = -1;
int count = attr.getAttributeCount();
for(int i = 0; i <count;i++) {
if(attr.getAttributeNameResource(i) == paramInt) {
String str = attr.getAttributeValue(i);
if(null != str && str.startsWith("?")) {
value = Integer.valueOf(str.substring(1,str.length())).intValue();
return value;
}
}
}
return value;
}
一目了然,主要是通过AttributeSet去获取paramInt相应的参数;
最重要的一点是需要动态变换主题的组件都要使用自定义组件,个别没有的根据规则可以自己实现:
大体的功能应该就这么多吧。
这个框架对换肤操作的确管用,但是需要自己去设置对应的自定义组件,感觉代码量颇大,可定制型不是很强,而且功能解耦不是很清晰。