Android主題切換方案總結

所謂的主題切換,就是可以根據不一樣的設定,呈現不一樣風格的界面給用戶,也就是所謂的換膚。
java

一、將主題包(圖片與配置)存到SD卡上(可經過下載或手動放入指定目錄),在代碼裏強制從本地文件建立圖片與配置文字大小、顏色等信息。android

二、Android平臺獨有的主題設置功能,在values文件夾中定義若干種style,在Activity的onCreate中使用setTheme方法設置主題。編程

三、將主題包作成APK的形式,使用遠程Context的方式訪問主題包中的資源。數組

四、相似小米的深度主題,修改framework中Resources類獲取資源的流程,將資源重定向到主題包中。app

對於第一種,因爲這種方法比較傳統,不受限於編程語言,任何平臺均可以實現。就很少介紹了。編程語言

而第四種,因爲涉及的知識比較多,我會在後續專門寫一篇文章來描述。ide

這裏,我將主要經過兩個小例子來講明第二與第三種方案的實現方式。佈局

我將其分別命名爲內置主題與APK主題。spa

1.內置主題

1.1定義屬性

要想根據主題的不一樣,設置不一樣屬性,咱們至少須要定義下屬性的名字吧。要否則系統怎麼知道去哪找啊!命令行

定義屬性,是在values下進行的。

本例中,我在attrs.xml裏定義了幾種屬性。

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <attr name="colorValue" format="color" />  
    <attr name="floatValue" format="float" />  
    <attr name="integerValue" format="integer" />  
    <attr name="booleanValue" format="boolean" />  
    <attr name="dimensionValue" format="dimension" />  
    <attr name="stringValue" format="string" />  
    <attr name="referenceValue" format="reference" />  
</resources>

 從上面的xml文件的內容能夠看到,attr裏能夠定義各類屬性類型,如color、float、integer、boolean、dimension(sp、dp/dip、px、pt...)、reference(指向本地資源)等等。

1.2定義主題

接着,咱們須要在資源文件中定義若干套主題。而且在主題中設置各個屬性的值。

本例中,我在styles.xml裏定義了SwitchTheme1與SwitchTheme2。

<style name="SwitchTheme1" parent="@android :style/Theme.Black">  
    <item name="colorValue">#FF00FF00</item>  
    <item name="floatValue">0.35</item>  
    <item name="integerValue">33</item>  
    <item name="booleanValue">true</item>  
    <item name="dimensionValue">76dp</item>  
    <!-- 若是string類型不是填的引用而是直接放一個字符串,在佈局文件中使用正常,但代碼裏獲取的就有問題 -->  
    <item name="stringValue">@string/hello_world</item>  
    <item name="referenceValue">@drawable/hand</item>  
</style>  
<style name="SwitchTheme2" parent="@android :style/Theme.Wallpaper">  
    <item name="colorValue">#FFFFFF00</item>  
    <item name="floatValue">1.44</item>  
    <item name="integerValue">55</item>  
    <item name="booleanValue">false</item>  
    <item name="dimensionValue">76px</item>  
    <item name="stringValue">@string/action_settings</item>  
    <item name="referenceValue">@drawable/ic_launcher</item>  
</style>

1.3在佈局文件中使用

定義好了屬性,咱們接下來就要在佈局文件中使用了。

爲了使用主題中的屬性來配置界面,我定義了一個名爲activity_theme_switch.xml佈局文件。

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
  
    <TextView  
        android:id="@+id/textView1"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignParentLeft="true"  
        android:layout_alignParentTop="true"  
        android:text="@string/theme_text" />  
  
    <TextView  
        android:id="@+id/textView3"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignParentLeft="true"  
        android:layout_below="@+id/textView1"  
        android:text="@string/theme_color" />  
  
    <TextView  
        android:id="@+id/themeColor"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignLeft="@+id/themeText"  
        android:layout_below="@+id/themeText"  
        android:text="TextView"  
        android:textColor="?attr/colorValue" />  
  
    <TextView  
        android:id="@+id/textView5"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignParentLeft="true"  
        android:layout_alignTop="@+id/theme_image"  
        android:text="@string/theme_image" />  
  
    <TextView  
        android:id="@+id/themeText"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignParentTop="true"  
        android:layout_centerHorizontal="true"  
        android:text="?attr/stringValue" />  
  
    <ImageView  
        android:id="@+id/theme_image"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignLeft="@+id/themeColor"  
        android:layout_below="@+id/themeColor"  
        android:src="?attr/referenceValue" />  
  
</RelativeLayout>

 從這個佈局文件中能夠看到,我在id爲themeColor、themeText及theme_image的控件上,分別使用了?attr/colorValue、?attr/stringValue與?attr/referenceValue來引用主題中的顏色值、字符串以及圖片。

1.4設置主題及佈局文件

 佈局文件與主題都寫好了,接下來咱們就要在Activity的onCreate方法裏使用了。

爲了顯示兩種主題的效果,我寫了一個DemoStyleThemeActivity。根據useThemeBlack來設置不一樣的主題。沒打開一次DemoStyleThemeActivity,useThemeBlack就會取反。這樣,只要打開兩次,就能看到不一樣的效果了。代碼以下:

import android.app.Activity;  
import android.content.res.TypedArray;  
import android.graphics.Color;  
import android.os.Bundle;  
  
public class DemoStyleThemeActivity extends Activity {  
  
    private static boolean useThemeBlack = true;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        if (useThemeBlack)  
            setTheme(R.style.SwitchTheme1);  
        else  
            setTheme(R.style.SwitchTheme2);  
        useThemeBlack = !useThemeBlack;  
        setContentView(R.layout.activity_theme_switch);  
  
    }  
  
}

 固然,要想主題生效,有一點很是重要:「setTheme必定要在setContentView以前被調用」。要否則,界面都解析完了,再設置主題也不會觸發從新建立界面。

更嚴重的是,要是默認主題裏沒那些屬性,解析佈局文件時候是會掛的啊!這點在配置多個不一樣style時要主題,屬性能夠多,但必定不能少。 

 以上代碼的執行結果以下:
 

1.5在代碼中獲取主題資源

固然,有的人就要問了,若是我想在代碼裏獲取主題中的資源該怎麼辦啊?畢竟整數、浮點數、布爾值這些東西在佈局文件裏沒地方用啊。

很簡單,要想在代碼中獲取當前設置的主題的屬性值,咱們只須要使用Context.obtainStyledAttributes就好了。

還記得1.1裏定義的那些屬性名吧,Android在編譯R.java時爲每個屬性都分配了一個標識。咱們只須要將這些標識的數組傳給obtainStyledAttributes就好了。

而後,咱們就能得到一個TypedArray對象。經過它的getXXX方法就能獲取到各類屬性值了。

代碼以下:

import android.app.Activity;  
import android.content.res.TypedArray;  
import android.graphics.Color;  
import android.os.Bundle;  
  
public class DemoStyleThemeActivity extends Activity {  
  
    private static boolean useThemeBlack = true;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        if (useThemeBlack)  
            setTheme(R.style.SwitchTheme1);  
        else  
            setTheme(R.style.SwitchTheme2);  
        useThemeBlack = !useThemeBlack;  
        setContentView(R.layout.activity_theme_switch);  
          
        TypedArray a = obtainStyledAttributes(new int[] {  
                R.attr.colorValue, R.attr.floatValue, R.attr.integerValue, R.attr.booleanValue,  
                R.attr.dimensionValue, R.attr.stringValue, R.attr.referenceValue  
        });  
        System.out.println("colorValue="+a.getColor(0, Color.BLACK));  
        System.out.println("floatValue="+a.getFloat(1, 99.99f));  
        System.out.println("integerValue="+a.getInt(2, Integer.MAX_VALUE));  
        System.out.println("booleanValue="+a.getBoolean(3, false));  
        System.out.println("dimensionValue="+a.getDimensionPixelSize(4, 66));  
        System.out.println("stringValue="+a.getString(5));  
        // Drawable打印了也沒什麼用,就不輸出了  
        //System.out.println("referenceValue="+a.getDrawable(6));          
    }  
}

命令行輸出結果以下:

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

實例代碼以下:

import android.app.Activity;  
import android.content.Context;  
import android.content.pm.PackageManager.NameNotFoundException;  
import android.content.res.Resources;  
import android.os.Bundle;  
import android.widget.ImageView;  
import android.widget.TextView;  
  
public class DemoRemoteThemeActivity extends Activity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_theme);  
        TextView text = (TextView) findViewById(R.id.remoteText);  
        TextView color = (TextView) findViewById(R.id.remoteColor);  
        ImageView image = (ImageView) findViewById(R.id.remote_image);  
        try {  
            String remotePackage = "com.xxx.themepackage";  
            Context remoteContext = createPackageContext(remotePackage,  
                    CONTEXT_IGNORE_SECURITY);  
            Resources remoteResources = remoteContext.getResources();  
            text.setText(remoteResources.getText(remoteResources.getIdentifier("application_name", "string", remotePackage)));  
            color.setTextColor(remoteResources.getColor(remoteResources.getIdentifier("delete_target_hover_tint", "color", remotePackage)));  
            image.setImageDrawable(remoteResources.getDrawable(remoteResources.getIdentifier("ic_launcher_home", "drawable", remotePackage)));  
        } catch (NameNotFoundException e) {  
            e.printStackTrace();  
        }     
  
    }  
}

首先,我經過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。剩下的圖片則直接從主題包中獲取。

這樣,能夠減小對代碼的修改,方便主題包的製做。

相關文章
相關標籤/搜索