• Android主题切换方案总结


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

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

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

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

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

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

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

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

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

    1.内置主题

    1.1定义属性

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

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

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

    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。

    Xml代码  收藏代码
    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布局文件。

    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.   
    6.     <TextView  
    7.         android:id="@+id/textView1"  
    8.         android:layout_width="wrap_content"  
    9.         android:layout_height="wrap_content"  
    10.         android:layout_alignParentLeft="true"  
    11.         android:layout_alignParentTop="true"  
    12.         android:text="@string/theme_text" />  
    13.   
    14.     <TextView  
    15.         android:id="@+id/textView3"  
    16.         android:layout_width="wrap_content"  
    17.         android:layout_height="wrap_content"  
    18.         android:layout_alignParentLeft="true"  
    19.         android:layout_below="@+id/textView1"  
    20.         android:text="@string/theme_color" />  
    21.   
    22.     <TextView  
    23.         android:id="@+id/themeColor"  
    24.         android:layout_width="wrap_content"  
    25.         android:layout_height="wrap_content"  
    26.         android:layout_alignLeft="@+id/themeText"  
    27.         android:layout_below="@+id/themeText"  
    28.         android:text="TextView"  
    29.         android:textColor="?attr/colorValue" />  
    30.   
    31.     <TextView  
    32.         android:id="@+id/textView5"  
    33.         android:layout_width="wrap_content"  
    34.         android:layout_height="wrap_content"  
    35.         android:layout_alignParentLeft="true"  
    36.         android:layout_alignTop="@+id/theme_image"  
    37.         android:text="@string/theme_image" />  
    38.   
    39.     <TextView  
    40.         android:id="@+id/themeText"  
    41.         android:layout_width="wrap_content"  
    42.         android:layout_height="wrap_content"  
    43.         android:layout_alignParentTop="true"  
    44.         android:layout_centerHorizontal="true"  
    45.         android:text="?attr/stringValue" />  
    46.   
    47.     <ImageView  
    48.         android:id="@+id/theme_image"  
    49.         android:layout_width="wrap_content"  
    50.         android:layout_height="wrap_content"  
    51.         android:layout_alignLeft="@+id/themeColor"  
    52.         android:layout_below="@+id/themeColor"  
    53.         android:src="?attr/referenceValue" />  
    54.   
    55. </RelativeLayout>  

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

    1.4设置主题及布局文件

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

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

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

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

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

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

    1.5在代码中获取主题资源

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

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

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

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

    代码如下:

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

    命令行输出结果如下:

    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)方法。

    实例代码如下:

    Java代码  收藏代码
    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.   
    9. public class DemoRemoteThemeActivity extends Activity {  
    10.   
    11.     @Override  
    12.     protected void onCreate(Bundle savedInstanceState) {  
    13.         super.onCreate(savedInstanceState);  
    14.         setContentView(R.layout.activity_theme);  
    15.         TextView text = (TextView) findViewById(R.id.remoteText);  
    16.         TextView color = (TextView) findViewById(R.id.remoteColor);  
    17.         ImageView image = (ImageView) findViewById(R.id.remote_image);  
    18.         try {  
    19.             String remotePackage = "com.xxx.themepackage";  
    20.             Context remoteContext = createPackageContext(remotePackage,  
    21.                     CONTEXT_IGNORE_SECURITY);  
    22.             Resources remoteResources = remoteContext.getResources();  
    23.             text.setText(remoteResources.getText(remoteResources.getIdentifier("application_name", "string", remotePackage)));  
    24.             color.setTextColor(remoteResources.getColor(remoteResources.getIdentifier("delete_target_hover_tint", "color", remotePackage)));  
    25.             image.setImageDrawable(remoteResources.getDrawable(remoteResources.getIdentifier("ic_launcher_home", "drawable", remotePackage)));  
    26.         } catch (NameNotFoundException e) {  
    27.             e.printStackTrace();  
    28.         }     
    29.   
    30.     }  
    31. }  

    首先,我通过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。剩下的图片则直接从主题包中获取。

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

  • 相关阅读:
    程序员是这样区分Null和Undefined
    JavaScript实现
    获取页面中任意一个元素距离body的偏移量
    js检测数据类型的方法你都掌握了几个?
    处理浏览器兼容你最喜欢用哪种方式
    算法竞赛入门经典 第四章 学习笔记 1
    算法竞赛入门经典 程序4-1 组合数
    算法竞赛入门经典 例题3-1 TeX中的引号
    算法竞赛入门经典 第3章 数组和字符串 学习笔记 3
    算法竞赛入门经典 第3章 数组和字符串 学习笔记 2
  • 原文地址:https://www.cnblogs.com/dongweiq/p/4234413.html
Copyright © 2020-2023  润新知