Android 切换主题 (二)

背景

我原来写过一篇文章关于 android 切换主题的文章 -- Android 切换主题以及换肤的实现 , 里面介绍了如何使用 setTheme() 来切换主题,只不过使用这个函数有个缺点那就是你必须得重新启动当前的 Activity 才能生效!那么问题来了,有没有方法保证我们既使用了 setTheme() 又能不重启当前的 Activity 呢?我告诉这是有的,下面我就是详细介绍这个方法 (参考 github 上的一个开源项目进行介绍!文末给出这个项目的地址)。

===

原理

众所周知,setTheme() 之后需要重启当前的 Activity 的原因是:它要重新渲染当前的 当前的 ViewTree 。所以现在我们的做法就是我们来自己给他渲染不就行了!那样的话,就不需要重启当前的 Activity 了!下面我们就来看代码吧!

===

代码

代码的实现核心就是:在用户调用 setTheme() 之后,我们获取当前的 Theme ,之后我们在从中获取到我们用的属性,之后在设置到对应的控件上,这样就ok了!

第一步

我们先定义个接口:

public interface ColorUiInterface {
public View getView();
public void setTheme(Resources.Theme themeId);
}

这样的话我们就可以重写所有的控件,让他继承该接口,并实现对应的函数,那么在 setTheme() 之后就可以直接调用每个控件都有的 setTheme() 方法了!

第二步

现在我们就来实现一个自定义的view

public class ColorTextView extends TextView implements ColorUiInterface {
private int attr_drawable = -1;
private int attr_textAppearance = -1;
private int attr_textColor = -1;
public ColorTextView(Context context) {
super(context);
} public ColorTextView(Context context, AttributeSet attrs) {
super(context, attrs);
this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
} public ColorTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
} @Override
public View getView() {
return this;
} @Override
public void setTheme(Resources.Theme themeId) {
if (attr_drawable != -1) {
ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_drawable);
}
if (attr_textColor != -1) {
ViewAttributeUtil.applyTextColor(this, themeId, attr_textColor);
}
}
}

从以上代码中我们可以看到,我首先获取了一下一些常用的需要换肤的要素,比如:背景色,字体颜色,当让还有其他的,这个随用户定制!当然读者也可以实现其他的自定义 view 。

第三步

现在我们来看下 ViewAttributeUtil 这个类的具体实现!

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);
if(null != ci && ci instanceof TextView) {
((TextView)ci.getView()).setTextColor(resourceId);
}
ta.recycle();
}
}

这个类比较简单,就是根据对应的 themeid 得到对应 themeid 的值!好了目前为止,我们还差一步就是,当我们调用了 Activity 的 setTheme() 方法之后,重新设置到对应的view就行了!

第四步

直接上代码

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();
}
}
}
}
}

代码很简单,就是用了递归,从当前的 ViewTree 中找到 继承了我们在第一步定义的那个接口,之后再调用其 setTheme() 方法就行了!!

最后一步

那么我们现在来看下如何在 Activity 中是如何调用的!

   setTheme(R.style.theme_1);
ColorUiUtil.changeTheme(rootView, getTheme());

是不是很简单呀!

总结

这个方法我感觉蛮好的,要说唯一的缺点吧!就是使用了递归!有点耗性能!但是这个我估计是可以忽略不计的!!

*** 现在给出这个开源项目的地址 MultipleTheme ***

Android 切换主题 (二)的更多相关文章

  1. Android 切换主题以及换肤的实现

    Android 切换主题以及换肤的实现 一.介绍 现在市面上有很多 APP 有切换主题和皮肤的功能!特别是阅读类的 APP! 上面两张图分别是 知乎 APP 和Fuubo APP的两张截图!都带有切换 ...

  2. Android 切换主题换肤实现

    思路以及实现 1.主题的切换以及实现 首先我们先来明确个概念,现在我所说的切换主题,就切换整个app的颜色风格,当然也有少部分的图片的切换.注意哦 我这边说的是少部分图片哦!如果是大面积的换图片的吧! ...

  3. Android应用主题与横竖屏的切换

    很多App,现在都具有了横竖屏切换的功能,或者说"白天"和"黑夜"主题的切换. 实现起来也非常简单.主要需要注意的是,在切换的同时,页面的数据不能丢失,不然给用 ...

  4. Android 实现切换主题皮肤功能(类似于众多app中的 夜间模式,主题包等)

    首先来个最简单的一键切换主题功能,就做个白天和晚上的主题好了. 先看我们的styles文件: <resources> <!-- Base application theme. --& ...

  5. Android Wear和二维码

    这是一篇发布在Android官方开发者社区博客,15年年初的时候就看到了这篇文章,直到现在才有时间把它翻译下来. 这是一篇如何在Android Wear上面如何正确地展示二维码的文章,里面有许多的经验 ...

  6. 【Android Studio安装部署系列】八、Android Studio主题皮肤更换

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 Android Studio具有自己的主题皮肤,但是如果想要更换自己喜欢的主题皮肤,可以参考下面的步骤. 注意,更换主题皮肤,之前的 ...

  7. Android JNI学习(二)——实战JNI之“hello world”

    本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...

  8. Android开发 QRCode二维码开发第三方框架

    前言 Android开发里二维码开发经常用到,这里简单的介绍下Android开发里的二维码. 最广泛使用的二维码库zxing zxing是最广泛的二维码库各个平台都可以适用它,但是Android平台使 ...

  9. Android Studio(十二):打包多个发布渠道的apk文件

    Android Studio相关博客: Android Studio(一):介绍.安装.配置 Android Studio(二):快捷键设置.插件安装 Android Studio(三):设置Andr ...

随机推荐

  1. for循环和增强版的for循环

    增强的for循环. 缺点:   对于数组.不能方便的訪问下标值.   对于集合,与使用Interator相比.不能方便的删除集合中的内容(在内部也是调用Interator). 除了简单遍历并读取当中的 ...

  2. 《开源框架那点事儿23》:採用TinyDB组件方式开发

    採用TinyDB组件方式开发 步骤 Icon 前文介绍四则运算的流程编程开发时,说过流程编排在开发反复功能时.能够利用已有的组件库高速开发.对于开发者而言仅仅须要简单配置流程就能够完毕工作了.开发增删 ...

  3. [Jobdu] 题目1455:珍惜现在,感恩生活

    题目描述: 为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买.请问:你用有限的资金最 ...

  4. CUGBACM Codeforces Tranning 3 题解

    链接:http://acm.hust.edu.cn/vjudge/contest/view.action? cid=62515#overview 描写叙述:第三场CF训练了.这次做的挺搞笑的,我记得这 ...

  5. A+P+M+W

    安装之前先做个目录的安排, D盘根目录建立”Dev”文件夹,在文件夹下建立: Php Apache Mysql 三个文件夹 所需软件: 0.    安装Apache的前必须安装VC11 vcredis ...

  6. Ubuntu打开core dump

    输入ulimit -a 如果core file size为0,那就说明没有打开core dump,尽管你的程序crash的时候会显示core dumped,但实际上不会生成core file 输入ul ...

  7. [转]C++中的三种继承public,protected,private

    链接:http://www.cnblogs.com/BeyondAnyTime/archive/2012/05/23/2514964.html

  8. python把中文文档变为拼音

    缘由 新看到的一篇文章,被吓尿.Text Understanding from Scratch,认为word的cnn抽象能力还不够好,使用character来做cnn效果更佳.结果是,由于论文的使用的 ...

  9. zombie处理

    僵尸进程处理 程序处理(预处理) 父进程wait/waitpid. signal(SIGCHLD, SIG_IGN); 捕捉SIGCHLD,signal(SIGCHLD, handler);可获取子进 ...

  10. Spider Studio 新版本 (x-mas) - 可以引入第三方程序集, 可以将脚本生成为DLL

    Merry X'mas! Spider Studio本年度最后一次重大更新发生在圣诞节, 又是一次美好的巧合 :) 本次更新主要包含两个重要功能: 1. 引入第三方程序集 在"设置" ...