程式碼寫多了,總是會有技術債要還。剛開始改造 FOSS Browser 時,因為懶,而且為了求快,在把既有的 icon 改成純黑色時,都是直接用 @android:color/black 寫死在 xml 中。將各種對話框改成純黑白型式,或是加外框時,也都是直接用上面的黑色色碼。

現在,因為我也常常在一般手機中使用 EinkBro App,就很希望它能也提供黑色的介面,讓我在使用手機時不要那麼刺眼。但一想到要改一大堆 icon 的顏色和對話框的顏色,就覺得很麻煩,所以遲遲未動手。

昨天心血來潮,開始了這個大工程。下面列出我實作的步驟,以後如果有類似的需求,就可以拿這次的經驗當作參考。


設定符合 App 需求的 Theme

使用 DayNight 的主題

原本 EinkBro 使用的主題是從 Theme.AppCompat.Light.NoActionBar 延伸而來的。如果想讓 App 可以跟著系統當下的設定採用一般或夜間的主題,要將主題改成像是 Theme.AppCompat.DayNight.NoActionBar 才可以。

在主題中指定自己想要的主要顏色

以下是針對電子紙,也就是在一般主題下的設定值

<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">  
    <item name="android:textColor">@android:color/black</item>  
    <item name="colorControlNormal">@android:color/black</item>  
    <item name="android:colorAccent">@android:color/black</item>  
    <item name="backgroundColor">@android:color/white</item>  
</style>

colorControlNormal 是用來指定 Vector Asset 中的線條顏色。以正常主題而言,我希望它們全是純黑色的,可以在電子紙上達到最高的對比度。

設定夜間主題的顏色

雖然 DayNight 的主題已經有幫忙指定夜間模式下的相關顏色變化,但因為我還是想要微調,所以,必須先建立 values-night 目錄,在底下也放入 styles.xml,然後在相同的主題名稱下,指定夜間模式的顏色選擇。

<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">  
    <item name="android:textColor">@color/lightGray</item>  
    <item name="android:colorAccent">@color/lightGray</item>  
    <item name="colorControlNormal">@color/lightGray</item>  
    <item name="backgroundColor">@android:color/black</item>  
    <item name="background">@android:color/black</item>  
</style>

針對夜間模式的字型顏色,我希望它不是完全純白的,稍微有點灰灰的感覺看起來比較舒服,所以我自己定義了一個 lightGray 的顏色。


原先寫死的顏色設定,改成參考 Theme 中的值

先從 Vector Asset 改起

很多 icon 是我利用 Android Studio 的 Asset Studio 產生的,附檔名全是 xml,所以比較容易利用字串找到所有 @android:color/black 的 xml 檔,然後再利用取代的功能把它們全部換成 ?attr/colorControlNormal 。在正常模式下,會是全黑的;在夜間模式下則是 lightGray

處理各種對話框和 layout xml

因為之前寫介面時的壞習慣,色碼散在到處的 layout 中;有的 TextView 也莫名奇妙地寫死了顏色。所以當一切到夜間模式時,很多對話框都是一片黑,都看不到字。

關於這一個步驟,只能一個一個修。有些 icon 並不是 xml 型式,而是png。這時,只能利用 app:tint=”?attr/colorControlNormal” 將它的顏色換掉。


修改程式碼

開啟 App 對夜間模式的支援

新建一個 Application class,並在裡頭加入 setDefaultNightMode

動態改變主題

雖然使用到的機會不多,但總是希望當使用者從通知欄開啟夜間模式時,當下的 App 介面能馬上切換到黑色的主題。

為了達到這功能,Activity 必須要去聽 configuration change,當改變的是 uiMode時,再做相對應的處置。簡單起見,我直接將 App重啟。

WebView 的夜間模式

因為 EinkBro 是個瀏覽器,所以除了改改介面變黑色模式外,如果 Web 部分也可以變成夜間模式的話,會是更好的體驗。Android 官方有專門的文章在解釋怎麼做。以下我只講我的實作方式。

https://developer.android.com/guide/webapps/dark-theme

首先要在 build.gradle 加入 webkit 的函式庫:

然後在 WebView 初始化的時候,根據系統的狀態和對夜間模式的支援程度來開啟夜間模式。

修正:上面畫面中設定的 ForceDarkStragegy 用的是 DARK_STRATEGY_WEB_THEME_DARKENING_ONLY。這麼設定的話,其實只有少部分的網站內容會跟著變成黑色底的顯示方式。
如果把它改為 DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING 則大部分的網站都可以以黑底的方式呈現

修正後如下:


看來畫面啦

後話

如此一下,就大功告成啦。美中不足的是,很多網站目前還沒有支援 Dark mode,所以常常還是只能看到工具列是黑的,但內容是白的。以後有時間的話,應該要再研究一下 Firefox 的 plugin — Dark Reader 是怎麼動態把大部分的網頁都變成黑色底的。