所谓的主题切换,就是能够根据不同的设定,呈现不同风格的界面给用户,也就是所谓的换肤。

1、将主题包(图片与配置)存到SD卡上(可通过下载或手动放入指定目录),在代码里强制从本地文件创建图片与配置文字大小、颜色等信息。

2、Android平台独有的主题设置功能,在values文件夹中定义若干种style,在Activity的onCreate中使用setTheme方法设置主题。

3、将主题包做成APK的形式,使用远程Context的方式访问主题包中的资源。

4、类似小米的深度主题,修改framework中Resources类获取资源的流程,将资源重定向到主题包中。

对于第一种,由于这种方法比较传统,不受限于编程语言,任何平台都可以实现。就不多介绍了。

而第四种,由于涉及的知识比较多,我会在后续专门写一篇文章来描述。

这里,我将主要通过两个小例子来说明第二与第三种方案的实现方式。

我将其分别命名为内置主题与APK主题。

1.内置主题

1.1定义属性

要想根据主题的不同,设置不同属性,我们至少需要定义下属性的名字吧。要不然系统怎么知道去哪找啊!

定义属性,是在values下进行的。

本例中,我在attrs.xml里定义了几种属性。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <attr name="colorValue" format="color" />
  4. <attr name="floatValue" format="float" />
  5. <attr name="integerValue" format="integer" />
  6. <attr name="booleanValue" format="boolean" />
  7. <attr name="dimensionValue" format="dimension" />
  8. <attr name="stringValue" format="string" />
  9. <attr name="referenceValue" format="reference" />
  10. </resources>

从上面的xml文件的内容可以看到,attr里可以定义各种属性类型,如color、float、integer、boolean、dimension(sp、dp/dip、px、pt...)、reference(指向本地资源)等等。

1.2定义主题

接着,我们需要在资源文件中定义若干套主题。并且在主题中设置各个属性的值。

本例中,我在styles.xml里定义了SwitchTheme1与SwitchTheme2。

  1. <style name="SwitchTheme1" parent="@android:style/Theme.Black">
  2. <item name="colorValue">#FF00FF00</item>
  3. <item name="floatValue">0.35</item>
  4. <item name="integerValue">33</item>
  5. <item name="booleanValue">true</item>
  6. <item name="dimensionValue">76dp</item>
  7. <!-- 如果string类型不是填的引用而是直接放一个字符串,在布局文件中使用正常,但代码里获取的就有问题 -->
  8. <item name="stringValue">@string/hello_world</item>
  9. <item name="referenceValue">@drawable/hand</item>
  10. </style>
  11. <style name="SwitchTheme2" parent="@android:style/Theme.Wallpaper">
  12. <item name="colorValue">#FFFFFF00</item>
  13. <item name="floatValue">1.44</item>
  14. <item name="integerValue">55</item>
  15. <item name="booleanValue">false</item>
  16. <item name="dimensionValue">76px</item>
  17. <item name="stringValue">@string/action_settings</item>
  18. <item name="referenceValue">@drawable/ic_launcher</item>
  19. </style>

1.3在布局文件中使用

定义好了属性,我们接下来就要在布局文件中使用了。

为了使用主题中的属性来配置界面,我定义了一个名为activity_theme_switch.xml布局文件。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent" >
  5. <TextView
  6. android:id="@+id/textView1"
  7. android:layout_width="wrap_content"
  8. android:layout_height="wrap_content"
  9. android:layout_alignParentLeft="true"
  10. android:layout_alignParentTop="true"
  11. android:text="@string/theme_text" />
  12. <TextView
  13. android:id="@+id/textView3"
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:layout_alignParentLeft="true"
  17. android:layout_below="@+id/textView1"
  18. android:text="@string/theme_color" />
  19. <TextView
  20. android:id="@+id/themeColor"
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. android:layout_alignLeft="@+id/themeText"
  24. android:layout_below="@+id/themeText"
  25. android:text="TextView"
  26. android:textColor="?attr/colorValue" />
  27. <TextView
  28. android:id="@+id/textView5"
  29. android:layout_width="wrap_content"
  30. android:layout_height="wrap_content"
  31. android:layout_alignParentLeft="true"
  32. android:layout_alignTop="@+id/theme_image"
  33. android:text="@string/theme_image" />
  34. <TextView
  35. android:id="@+id/themeText"
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content"
  38. android:layout_alignParentTop="true"
  39. android:layout_centerHorizontal="true"
  40. android:text="?attr/stringValue" />
  41. <ImageView
  42. android:id="@+id/theme_image"
  43. android:layout_width="wrap_content"
  44. android:layout_height="wrap_content"
  45. android:layout_alignLeft="@+id/themeColor"
  46. android:layout_below="@+id/themeColor"
  47. android:src="?attr/referenceValue" />
  48. </RelativeLayout>

从这个布局文件中可以看到,我在id为themeColor、themeText及theme_image的控件上,分别使用了?attr/colorValue、?attr/stringValue与?attr/referenceValue来引用主题中的颜色值、字符串以及图片。

1.4设置主题及布局文件

布局文件与主题都写好了,接下来我们就要在Activity的onCreate方法里使用了。

为了显示两种主题的效果,我写了一个DemoStyleThemeActivity。根据useThemeBlack来设置不同的主题。没打开一次DemoStyleThemeActivity,useThemeBlack就会取反。这样,只要打开两次,就能看到不同的效果了。代码如下:

  1. import android.app.Activity;
  2. import android.content.res.TypedArray;
  3. import android.graphics.Color;
  4. import android.os.Bundle;
  5. public class DemoStyleThemeActivity extends Activity {
  6. private static boolean useThemeBlack = true;
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. if (useThemeBlack)
  11. setTheme(R.style.SwitchTheme1);
  12. else
  13. setTheme(R.style.SwitchTheme2);
  14. useThemeBlack = !useThemeBlack;
  15. setContentView(R.layout.activity_theme_switch);
  16. }
  17. }

当然,要想主题生效,有一点非常重要:“setTheme一定要在setContentView之前被调用”。要不然,界面都解析完了,再设置主题也不会触发重新创建界面。

更严重的是,要是默认主题里没那些属性,解析布局文件时候是会挂的啊!这点在配置多个不同style时要主题,属性可以多,但一定不能少。

以上代码的执行结果如下:
 

1.5在代码中获取主题资源

当然,有的人就要问了,如果我想在代码里获取主题中的资源该怎么办啊?毕竟整数、浮点数、布尔值这些东西在布局文件里没地方用啊。

很简单,要想在代码中获取当前设置的主题的属性值,我们只需要使用Context.obtainStyledAttributes就行了。

还记得1.1里定义的那些属性名吧,Android在编译R.java时为每一个属性都分配了一个标识。我们只需要将这些标识的数组传给obtainStyledAttributes就行了。

然后,我们就能获得一个TypedArray对象。通过它的getXXX方法就能获取到各种属性值了。

代码如下:

  1. import android.app.Activity;
  2. import android.content.res.TypedArray;
  3. import android.graphics.Color;
  4. import android.os.Bundle;
  5. public class DemoStyleThemeActivity extends Activity {
  6. private static boolean useThemeBlack = true;
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. if (useThemeBlack)
  11. setTheme(R.style.SwitchTheme1);
  12. else
  13. setTheme(R.style.SwitchTheme2);
  14. useThemeBlack = !useThemeBlack;
  15. setContentView(R.layout.activity_theme_switch);
  16. TypedArray a = obtainStyledAttributes(new int[] {
  17. R.attr.colorValue, R.attr.floatValue, R.attr.integerValue, R.attr.booleanValue,
  18. R.attr.dimensionValue, R.attr.stringValue, R.attr.referenceValue
  19. });
  20. System.out.println("colorValue="+a.getColor(0, Color.BLACK));
  21. System.out.println("floatValue="+a.getFloat(1, 99.99f));
  22. System.out.println("integerValue="+a.getInt(2, Integer.MAX_VALUE));
  23. System.out.println("booleanValue="+a.getBoolean(3, false));
  24. System.out.println("dimensionValue="+a.getDimensionPixelSize(4, 66));
  25. System.out.println("stringValue="+a.getString(5));
  26. // Drawable打印了也没什么用,就不输出了
  27. //System.out.println("referenceValue="+a.getDrawable(6));
  28. }
  29. }

命令行输出结果如下:

2.APK主题

内置主题虽然可以很方便地修改界面,并且不需要怎么改代码。但它有一个比较致命的缺陷,就是一旦程序发布后,应用所支持的主题风格就固定了。要想增加更多的效果,只能发布新的版本。

为了解决这种问题,便有了APK主题方案。

APK主题方案很像之前提到的第一种将主题包保存到SD卡上的方案很像。

它们的共同点是,都需要在代码中手动设置图片、文字、颜色等信息。

主要的区别就是,第一种方案需要我们自行编写资源解析的方法。而通过APK主题方案,可以让Android系统帮我们搞定。

APK主题方案的基本思路是:在Android中,所有的资源都是基于包的。资源以id进行标识,在同一个应用中,每个资源都有唯一标识。但在不同的应用中,可以有相同的id。因此,只要获取到了其他应用的Context对象,就可以通过它的getRsources获取到其绑定的资源对象。然后,就可以使用Resources的getXXX方法获取字符串、颜色、dimension、图片等。

要想获取其他应用的Context对象,Android已经为我们提供好了接口。那就是android.content.ContextWrapper.createPackageContext(String packageName, int flags)方法。

实例代码如下:

  1. import android.app.Activity;
  2. import android.content.Context;
  3. import android.content.pm.PackageManager.NameNotFoundException;
  4. import android.content.res.Resources;
  5. import android.os.Bundle;
  6. import android.widget.ImageView;
  7. import android.widget.TextView;
  8. public class DemoRemoteThemeActivity extends Activity {
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.activity_theme);
  13. TextView text = (TextView) findViewById(R.id.remoteText);
  14. TextView color = (TextView) findViewById(R.id.remoteColor);
  15. ImageView image = (ImageView) findViewById(R.id.remote_image);
  16. try {
  17. String remotePackage = "com.xxx.themepackage";
  18. Context remoteContext = createPackageContext(remotePackage,
  19. CONTEXT_IGNORE_SECURITY);
  20. Resources remoteResources = remoteContext.getResources();
  21. text.setText(remoteResources.getText(remoteResources.getIdentifier("application_name", "string", remotePackage)));
  22. color.setTextColor(remoteResources.getColor(remoteResources.getIdentifier("delete_target_hover_tint", "color", remotePackage)));
  23. image.setImageDrawable(remoteResources.getDrawable(remoteResources.getIdentifier("ic_launcher_home", "drawable", remotePackage)));
  24. } catch (NameNotFoundException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }

首先,我通过createPackageContext获取到了包名为com.xxx.themepackage的应用的上下文。

然后,通过Resources的getIdentifier方法获取到相应资源名在该应用中的id。当然,有的人也可以通过使用自身应用的id的方式,不过这有一个前提,那就是同名资源在主题包应用与当前应用中的id相同。这貌似可以通过修改编译流程来实现,就像framework里的public.xml那样。不过这不在本文的讨论范畴内。

最后,就是通过Resources的getXXX方法获取资源了。

具体效果如下图所示。

3.高级应用

其实对于内置主题与APK主题来说,两者各有利弊。

内置主题实现简单,配置方便。但要想增加新的视效,就需要重新发布一次软件版本。对于目前的大部分用户来说,软件更新太频繁容易降低用户信赖感。

而APK主题虽然扩展性很高,但由于该方案需要在代码中设置所有的可变资源,软件实现周期较长,写代码时容易出错。而且第一版耗时较旧,一旦界面布局改变,需要较长的时间进行代码的编写。

因此,我们可以将以上两种结合起来使用,在代码中预置几种内置主题。

这些内置的主题,主要在界面风格上有所区别,如整体色调。

我们可以在这些内置主题中设置不同的文字颜色。

而对于图片,则在APK主题包中提供。

另外,在每个主题包中保存一个内置主题风格选择属性,这样不同的主题包就可以设置界面显示不同的风格。

比如说,我们的应用中内置两种主题,一种是暗色背景,一种是两色背景(就像Android自带的Holo.Dark与Holo.Light)。在这两种主题中分别设置相应的文字颜色。

而每个主题包中都存储一个字符串,如theme_type。这样,当显示页面时,我们先根据主题包中的theme_type来决定使用哪个主题来setTheme。剩下的图片则直接从主题包中获取。

这样,可以减少对代码的修改,方便主题包的制作。

Android主题切换方案总结的更多相关文章

  1. Android主题切换—夜间/白天模式探究

    现在市面上众多阅读类App都提供了两种主题:白天or夜间. 上述两幅图片,正是两款App的夜间模式效果,所以,依据这个功能,来看看切换主题到底是怎么实现的(当然现在github有好多PluginThe ...

  2. Android 主题切换 小结

    前言 我们用手机的时候经常看到 设置里面有夜间模式和白天模式来更换APP的主题,以前以为很简单,但是实际做起来还是有各种不完美,那么我们也要去了解各种解决方案来丰富我们的知识,现在我们就来看看各种优劣 ...

  3. Android主题切换

    一.APK文件方式:(CM9方案) 我们知道Android给每个APK进程分配一个单独的用户空间,其manifest中的userid就是对应一个Linux用户的(Android 系统是基于Linux) ...

  4. vue+less换肤,主题切换方案

    新的项目对于客户自定义要求很高,然后换肤是其中一个很小的模块,经过了一段时间的摸索,看了许多文章,找到了几种方案. https://www.cnblogs.com/leiting/p/11203383 ...

  5. Android 主题动态切换框架:Prism

    Prism(棱镜) 是一个全新的 Android 动态主题切换框架,虽然是头一次发布,但它所具备的基础功能已经足够强大了!本文介绍了 Prism 的各种用法,希望对你会有所帮助,你也可以对它进行扩展, ...

  6. Android原生多语言切换方案,兼容Android10

    前言 一个应用若需要国际化,至少需要支持中文和英语这两种语言,而同时随着谷歌的系统的更新,安卓系统可以设置当前语言的首选语言.因此,本文立足于此,多语言的切换方案为:App固定的文字内容,跟随系统,中 ...

  7. Android主题换肤实现

    本系列文章主要是对一个Material Design的APP的深度解析,主要包括以下内容 基于Material Design Support Library作为项目整体框架.对应博文:Android ...

  8. 【JeeSite】登录和主题切换

    最高管理员账号,用户名:thinkgem 密码:admin 1.    密码加密:登录用户密码进行SHA1散列加密,此加密方法是不可逆的.保证密文泄露后的安全问题. 在spring-shiro配置文件 ...

  9. 《推送开发全面盘点当前Android后台保活方案的真实运行效果》

        登录 立即注册 TCP/IP详解 资讯 动态 社区 技术精选 首页   即时通讯网›专项技术区›推送开发全面盘点当前Android后台保活方案的真实运行效果(截止2 ...   帖子 打赏 分 ...

随机推荐

  1. .Net中C#的DllImport的用法

    大家在实际工作学习C#的时候,可能会问:为什么我们要为一些已经存在的功能(比如 Windows中的一些功能,C++中已经编写好的一些方法)要重新编写代码,C#有没有方法可以直接都用这些原本已经存在的功 ...

  2. 【HDOJ】2155 小黑的镇魂曲

    线段树+SPFA最短路可以过.或者DP也能过.需要注意的是xl的范围是错的,测试用例中xl可能为0,他妈的,因为这个一直莫名其妙的wa.1. spfa建图增加一倍的点即可(讨论左端点和右端点). /* ...

  3. 定制属于自己的自动化安装的linux系统镜像

    使用软件和平台 1.基于平台:                  Vmware workstation 8.0 2.基于系统镜像:               rhel-server-5.8-i386 ...

  4. CodeForces 450

    A - Jzzhu and Children Time Limit:1000MS     Memory Limit:262144KB     64bit IO Format:%I64d & % ...

  5. 步步为营 SharePoint 开发学习笔记系列总结

    转:http://www.cnblogs.com/springyangwc/archive/2011/08/03/2126763.html 概要 为时20多天的sharepoint开发学习笔记系列终于 ...

  6. 谈谈以下关键字的作用auto static register const volatile extern

    (1)auto 这个这个关键字用于声明变量的生存期为自动,即将不在任何类.结构.枚举.联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量.这个关键字不怎么多写,因为所有的变量默认就是 ...

  7. codeforces629C Famil Door and Brackets (dp)

    题意:给你一个长度为n的括号匹配串(不一定恰好匹配),让你在这个串的前面加p串和后面加上q串,使得这个括号串平衡(平衡的含义是对于任意位置的括号前缀和大于等于0,且最后的前缀和为0). 思路:枚举这个 ...

  8. 计蒜客 取数游戏(dp)

    有如下一个双人游戏:N个正整数的序列放在一个游戏平台上,两人轮流从序列的两端取数,每次有数字被一个玩家取走后,这个数字被从序列中去掉并累加到取走该数的玩家的得分中,当数取尽时,游戏结束.以最终得分多者 ...

  9. 教程-Delphi操作快捷键

    ************************************************************** Delphi快捷键-全-高手用-南山古桃(新手)-同学共进 ******* ...

  10. poj 2117 Electricity【点双连通求删除点后最多的bcc数】

    Electricity Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 4727   Accepted: 1561 Descr ...