1. 程式人生 > >Android style & Theme 再探析(三)——定製Theme示例和踩坑大彙總

Android style & Theme 再探析(三)——定製Theme示例和踩坑大彙總

基於前兩篇的文章的探究,本次帶來的是面板切換和日夜間模式切換,以及基於重寫部分控制元件展示Theme的作用;以及對於實際使用時的講解,踩過的一些坑的彙總

Android Theme切換主題總結

筆者本文講述在實踐換膚以及日夜間模式切換的簡單Demo

Android Demo展示講解

效果展示

Demo效果展示 由圖可以看到,是本示例由多種Theme和夜間模式配合展示效果,並且針對已有的activity也進行變換,這個也是我目前看到的很多已有的部落格demo缺失部分,針對已經存在的acitivity如何處理

程式碼思路展示

  1. 首先是針對style中的Theme的設定:
 <!-- Base application theme. -->
<style name="ehiTheme" parent="@style/Theme.AppCompat.DayNight.NoActionBar"> <!-- a.將普通的Theme .AppCompat.Light 替換 .AppCompat.DayNight 用於日夜間模式替換 --> <item name="buttonStyle">@style/ehiButton</item> <!-- b.重寫部分控制元件的樣式,修改預設展示的效果 --> <item name="checkboxStyle"
>@style/ehiCheckBox</item> <!--兩者為不同維度 內部按鈕 文字等 alertDialogStyle定義內部按鈕 定義內部window類似 back等屬性 alertDialogTheme--> <item name="alertDialogTheme">@style/AppTheme.blue.AlertDialog</item> <item name="radioButtonStyle">@style/ehiRadioButton</item>
</style> <!-- c.重寫按鈕樣式示例展示 --> <style name="ehiButton" parent="@style/Base.Widget.AppCompat.Button"> <item name="android:background">?colorAccent</item> <item name="android:textColor">@color/white</item> <item name="android:textAppearance">@style/Base.TextAppearance.AppCompat.Small</item> </style>
  1. 定義多種Theme
     
       <style name="ehiTheme" 
    parent="@style/Theme.AppCompat.DayNight.NoActionBar">
        <!--部分程式碼省略-->
    </style> 
    <style name="AppTheme" parent="ehiTheme">
        <!-- Customize your theme here. -->
         <!-- a.定義多種主題Theme前不直接當做父類,而是再次繼承一遍,方便在value -v21的更高資料夾中直接利用該 AppTheme 新增新特性,而上面ehiTheme可以統一新增一些共同具有的屬性 -->
    </style> 
    <!--b.定義藍色主題-->
     <style name="AppTheme.blue">
        <item name="colorPrimary">@color/colorPrimaryBlue</item>
        <item name="colorPrimaryDark">@color/colorPrimaryBlueDark</item>
        <item name="colorAccent">@color/colorPrimaryBlueDark</item>
        <item name="alertDialogTheme">@style/AppTheme.blue.AlertDialog</item>
    </style>
     <!--c.定義紫色主題-->
     <style name="AppTheme.Purple" >
        <item name="colorPrimary">@color/colorPrimaryPurple</item>
        <item name="colorPrimaryDark">@color/colorPrimaryPurpleDark</item>
        <item name="colorAccent">@color/colorPrimaryPurpleDark</item>
        <item name="alertDialogTheme">@style/AppTheme.Purple.AlertDialog</item>
    </style>

     <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:name=".DemoApplication"
        android:theme="@style/AppTheme.blue"></style>
        <!--application中引用其中一個-->
  1. 針對日夜間模式的資原始檔做的調整
  • 原有的value資料夾外,再建立values-night

  • 建立兩份color檔案,替換為不同的顏色(ps:需要在夜間模式替換的顏色定義到night檔案中即可,其他顏色保留在預設資料夾中即可)

value 檔案中的color


      <color name="colorAccent">@color/colorPrimaryBlueDark</color>
    <color name="colorPrimary">@color/colorPrimaryBlue</color>
    <color name="colorBackgroundTrans">#AAFAFAFA</color>

    <color name="colorPrimaryBlue">#bbdefb</color>
    <color name="colorPrimaryBlueDark">#90caf9</color>

     <color name="colorPrimaryPurple">#e1bee7</color>
    <color name="colorPrimaryPurpleDark">#ce93d8</color>

value-night 檔案中的color


      <color name="colorAccent">@color/colorPrimaryBlueDark</color>
    <color name="colorPrimary">@color/colorPrimaryBlue</color>
    <color name="colorBackgroundTrans">#AAFAFAFA</color>

   <color name="colorPrimaryBlue">#82b1ff</color>
    <color name="colorPrimaryBlueDark">#2962ff</color>

    <color name="colorPrimaryPurple">#d500f9</color>
    <color name="colorPrimaryPurpleDark">#aa00ff</color>
  1. 程式碼中使用多Theme和日夜間模式
  • 設定快取Theme變化後的設定的儲存位置 本demo中使用application做為儲存,真實專案請使用xml等持久化方式來儲存使用者設定
public class DemoApplication extends Application {
    private static DemoApplication demoApplication;
   private static final int defaultStyle = R.style.AppTheme_blue;//預設的主題
   private static int tempNightMode = AppCompatDelegate.MODE_NIGHT_NO;//預設日間模式
    private static int style = defaultStyle;
    @Override
    public void onCreate() {
        super.onCreate();
        demoApplication = this;
    }

    //getter 和 setter 方法省略

}

  • 設定介面的佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MDActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="測試按鈕" />
    <RadioGroup
        android:id="@+id/rg_theme"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
          <RadioButton
            android:id="@+id/pink"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="粉色" />
        <RadioButton
            android:id="@+id/blue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="藍色" />
        <RadioButton
            android:id="@+id/Purple"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="紫色" />

    </RadioGroup>

    <Switch
        android:id="@+id/switch_day_night"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="日夜間模式切換"
        android:checked="false"
        />
    
</LinearLayout>
  • 設定介面java程式碼
public class MDActivity extends AppCompatActivity {
     //控制元件定義   
    private RadioGroup rg;
    private Switch switchDayNight;
    private RadioButton pink;
    private RadioButton blue;
    private RadioButton Purple;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //activiy重建後,讀取設定後的style
        setTheme(DemoApplication.getStyle());
        super.onCreate(savedInstanceState);
        //activiy重建後,設定介面讀取日夜間模式
        if (DemoApplication.getTempNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
        } else {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
        }
        setContentView(R.layout.activity_md);
    initView();
    }
    //初始化檢視
     private void initView() {
         //activiy重建後,設定switch開關
        switchDayNight = findViewById(R.id.switch_day_night);
        if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
            switchDayNight.setChecked(true);
        } else {
            switchDayNight.setChecked(false);
        }
        //社會
        switchDayNight.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    DemoApplication.setTempNightMode(AppCompatDelegate.MODE_NIGHT_YES);
                    Intent intent = getIntent();
                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
                    setResult(RESULT_OK);
                    finish();
                    startActivity(intent);
                    overridePendingTransition(0, 0);
                } else {
                    DemoApplication.setTempNightMode(AppCompatDelegate.MODE_NIGHT_NO);
                    Intent intent = getIntent();
                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
                    setResult(RESULT_OK);
                    finish();
                    startActivity(intent);
                    overridePendingTransition(0, 0);
                }
            }
        });
        pink = findViewById(R.id.pink);
        blue = findViewById(R.id.blue);
        Purple = findViewById(R.id.Purple);
        rg = findViewById(R.id.rg_theme);
        setCheck();
        rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                switch (checkedId) {
                    case R.id.pink:
                        setStyle(R.style.AppTheme_Pink);
                        break;
                    case R.id.blue:
                        setStyle(R.style.AppTheme_blue);
                        break;
                    case R.id.Purple:
                        setStyle(R.style.AppTheme_Purple);
                        break;
                    default:
                        setStyle(R.style.AppTheme_blue);
                }


            }
        });
    }
    //設定對應的主題
    private void setCheck() {

        switch (DemoApplication.getStyle()){
            case R.style.AppTheme_Pink:
                pink.setChecked(true);
                break;
            case R.style.AppTheme_blue:
                blue.setChecked(true);
                break;
            case R.style.AppTheme_Purple:
                Purple.setChecked(true);
                break;
            default:
                blue.setChecked(true);
        }
    }
   //將style進行設定,將介面進行重啟
    private void setStyle(int appTheme) {
        //相同主題,不進行重啟activity
        if (DemoApplication.getStyle() == appTheme) {
            return;
        }
        //快取使用者的主題設定
        DemoApplication.setStyle(appTheme);
        Intent intent = getIntent();
        //設定將activity的轉場動畫關閉
        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
        //重啟時Result丟失,返回上一個介面
        setResult(RESULT_OK);
        finish();
        startActivity(intent);
        //關閉轉場動畫
        overridePendingTransition(0, 0);
    }

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK)) {
            setResult(RESULT_OK);//用於通知上一個介面,重啟
            finish();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }


  • 進入設定介面前的介面
public class MainActivity extends AppCompatActivity {
    //設定 setting動作
    private int SETTINGS_ACTION = 1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //讀取新的style
        setTheme(DemoApplication.getStyle());
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
   //用於重新建立已有介面,來展示新的style和日夜間模式
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == SETTINGS_ACTION && resultCode == RESULT_OK){
            finish();
            Intent intent = getIntent();
            startActivity(intent);
        }
    }
}
  1. 實現思路

主要的實現思路非常簡單:

a. 其實就是由使用者選定對應的style和日夜間模式後,關閉動畫的情況下,重新啟動 設定的activity

b. 返回上一介面時,介面style或日夜間模式有改動就在設定介面

setResult(RESULT_OK);

然後在上一個介面獲取,然後重啟介面即可

if (requestCode == SETTINGS_ACTION && resultCode == RESULT_OK){
            finish();
            Intent intent = getIntent();
            startActivity(intent);
        }
  1. 總結

總的來說其實,切換theme主題本身並沒有涉及什麼很高深的技術,只是對於我們所瞭解的基礎的內容進行一個組合,我們可以用這些很簡單知識建立一個更好的使用者體驗!

  1. 注意點
  • theme當中不要直接引用

    <style name="ehiTheme" 
      parent="@style/Theme.AppCompat.DayNight.NoActionBar">
    

    而是應該再次繼承一次

 <style name="AppTheme" parent="ehiTheme">

這樣在 ehiTheme用於管理所有android版本下的預設樣式, 而AppTheme用於在多個value下定義一些版本的新特性屬性

  • 日夜間模式和主題的切換針對已經活著的activity是不起作用的,本文采用的思路是直接,無動畫重新建立activity,此種方式的劣勢 就是需要對app的一些狀態進行儲存,保證重新建立後用戶編輯過的內容不會丟失

  • 夜間模式的 color,style之類的只定義需要變化的部分的顏色或者樣式,不用把其他日夜間都用的統一樣式複製兩份,避免不必要的維護成本