Android style & Theme 再探析(三)——定製Theme示例和踩坑大彙總
基於前兩篇的文章的探究,本次帶來的是面板切換和日夜間模式切換,以及基於重寫部分控制元件展示Theme的作用;以及對於實際使用時的講解,踩過的一些坑的彙總
Android Theme切換主題總結
筆者本文講述在實踐換膚以及日夜間模式切換的簡單Demo
Android Demo展示講解
效果展示
由圖可以看到,是本示例由多種Theme和夜間模式配合展示效果,並且針對已有的activity也進行變換,這個也是我目前看到的很多已有的部落格demo缺失部分,針對已經存在的acitivity如何處理
程式碼思路展示
- 首先是針對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>
- 定義多種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中引用其中一個-->
- 針對日夜間模式的資原始檔做的調整
-
原有的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>
- 程式碼中使用多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);
}
}
}
- 實現思路
主要的實現思路非常簡單:
a. 其實就是由使用者選定對應的style和日夜間模式後,關閉動畫的情況下,重新啟動 設定的activity
b. 返回上一介面時,介面style或日夜間模式有改動就在設定介面
setResult(RESULT_OK);
然後在上一個介面獲取,然後重啟介面即可
if (requestCode == SETTINGS_ACTION && resultCode == RESULT_OK){
finish();
Intent intent = getIntent();
startActivity(intent);
}
- 總結
總的來說其實,切換theme主題本身並沒有涉及什麼很高深的技術,只是對於我們所瞭解的基礎的內容進行一個組合,我們可以用這些很簡單知識建立一個更好的使用者體驗!
- 注意點
-
theme當中不要直接引用
<style name="ehiTheme" parent="@style/Theme.AppCompat.DayNight.NoActionBar">
而是應該再次繼承一次
<style name="AppTheme" parent="ehiTheme">
這樣在 ehiTheme用於管理所有android版本下的預設樣式, 而AppTheme用於在多個value下定義一些版本的新特性屬性
-
日夜間模式和主題的切換針對已經活著的activity是不起作用的,本文采用的思路是直接,無動畫重新建立activity,此種方式的劣勢 就是需要對app的一些狀態進行儲存,保證重新建立後用戶編輯過的內容不會丟失
-
夜間模式的 color,style之類的只定義需要變化的部分的顏色或者樣式,不用把其他日夜間都用的統一樣式複製兩份,避免不必要的維護成本