簡潔優雅地實現夜間模式

louboutink8 7年前發布 | 13K 次閱讀 XML 安卓開發 Android開發 移動開發

前言

Android 6.0 Marshmallow 預覽版中曾經短暫出現過相關的夜間模式的功能,只是在正式版中被移除了,在Android 7.0 Nougat上,用戶們再次經歷了「得而復失」的遺憾,在開發者預覽版中,夜間模式和暗色模式先是開啟,然后有再次被移除。而在正式版中,夜間模式也沒有出現。但其實相關的代碼一直存在于系統中,只是默認沒有被開啟。如何開啟這項功能,可以參考少數派的這一篇文章, 幫你找回 Android 7.0 夜間模式的 2 款應用

不過,今天要介紹的主要內容并不是關于系統的夜間模式,而是如何給我們開發的APP添加夜間模式的功能。毫不夸張的說,夜間模式現在已經是閱讀類App的標配了。事實上,日間模式與夜間模式就是給APP定義并應用兩套不同顏色的主題。用戶可以自動或者手動的開啟。我們先看兩個我認為實現地很優雅的例子:知乎和推ter。

這兩個APP在切換的工程中,并沒有出現閃現黑屏的情況,切換也比較順滑。我們的目標就是利用Support Library實現同樣的效果。

實現

添加依賴

compile 'com.android.support:appcompat-v7:25.1.0'

由于Support Library在23.2.0的版本中才添加了Theme.AppCompat.DayNight主題,所以依賴的版本必須是高于23.2.0的,并且,這個特性支持的最低SDK版本為14,所以,需要兼容Android 4.0的設備,是不能使用這個特性的,在API Level 14以下的設備會默認使用亮色主題。不過現在4.0以下的設備應該比較少了吧,畢竟微信的minSdkVersion都設置為14了。

準備資源

  1. 讓我們自己的主題繼承并應用DayNight主題。

    <style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
    
             <item name="colorPrimary">@color/colorPrimary</item>
             <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
             <item name="colorAccent">@color/colorAccent</item>
             <!--customize your theme here-->
    
     </style>
  2. 新建夜間模式資源文件夾:在 res 目錄下新建 values-night 文件夾,然后在此目錄下新建 colors.xml 文件在夜間模式下的應用的資源。當然也可以根據需要新建 drawable-night , layout-night 等后綴為 -night 的夜間資源文件夾。

    我的 values 和 values-night 目錄下的 colors.xml 的內容如下:

    <?xml version="1.0" encoding="utf-8"?>
     <!--values-colors.xml-->
     <resources>
         <color name="colorPrimary">#009688</color>
         <color name="colorPrimaryDark">#00796B</color>
         <color name="colorAccent">#009688</color>
         <color name="textColorPrimary">#616161</color>
         <color name="viewBackground">@android:color/white</color>
     </resources>
    <?xml version="1.0" encoding="utf-8"?>
     <!--values-night-colors.xml>
     <resources>
         <color name="colorPrimary">#35464e</color>
         <color name="colorPrimaryDark">#212a2f</color>
         <color name="colorAccent">#212a2f</color>
         <color name="textColorPrimary">#616161</color>
         <color name="viewBackground">#212a2f</color>
     </resources>
  3. 使Activity繼承自AppCompatActivity。

    public class MainActivity extends AppCompatActivity {
    
         // code here
         @Override
         protected void onCreate(Bundle savedInstanceState) {
    
         }
    
     }

應用

靜態應用

在Application的繼承類下設置初始主題。

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
        // other code here

}

這里 AppCompatDelegate.setDefaultNightMode() 方法可以接受的參數值有4個:

  • MODE_NIGHT_NO. Always use the day (light) theme(一直應用日間(light)主題).
  • MODE_NIGHT_YES. Always use the night (dark) theme(一直使用夜間(dark)主題).
  • MODE_NIGHT_AUTO. Changes between day/night based on the time of day(根據當前時間在day/night主題間切換).
  • MODE_NIGHT_FOLLOW_SYSTEM(默認選項). This setting follows the system’s setting, which is essentially MODE_NIGHT_NO(跟隨系統,通常為MODE_NIGHT_NO).

我們可以在任何時候調用這個方法,因為這個方法是靜態的。但是這個值并不是一直存在的,每次在開啟進程時需要重新設置。在上面的代碼中,我是在 onCreate() 方法中設置的,網上也有大神建議在Activity或者Application的 static 代碼塊中設置。如下所示:

public class App extends Application {

    static {
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // other code here

}

動態應用

雖然上面的靜態應用的設置非常簡單,但是這種方式的應用場景還是太少了。我們更多的還是需要動態的根據需要動態的切換。

  1. 檢測當前主題模式

    int mode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
  2. 設置主題

    if(mode == Configuration.UI_MODE_NIGHT_YES) {
         AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
     } else if(mode == Configuration.UI_MODE_NIGHT_NO) {
         AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
     } else {
         // blah blah
     }
    
     recreate();

    在調用 recreate() 方法之前,還可以創建一些動畫進行過渡。而且,眾所周知,調用 recreate() 需要對一些數據進行保存,例如fragment,CheckBox,RadioBox等。如下所示:

    public class MainFragment extends Fragment {
    
         @Override
         public void onCreate(@Nullable Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
    
              if (savedInstanceState != null) {
                  FragmentManager manager = getChildFragmentManager();
                 doubanMomentFragment = (DoubanMomentFragment) manager.getFragment(savedInstanceState, "douban");
              } else {
                  doubanMomentFragment = DoubanMomentFragment.newInstance();
              }
         }
    
         @Override
         public void onSaveInstanceState(Bundle outState) {
             super.onSaveInstanceState(outState);
             FragmentManager manager = getChildFragmentManager();
             manager.putFragment(outState, "douban", doubanMomentFragment);
     }

    我們也可以把主題的值存儲到SharedPreference中,已便于應用在下一次啟動時自動應用主題。

Q&A

  • Q:系統默認的顏色不合我的口味怎么辦?

    A:使用主題屬性,例如: textColor:?android:attr/textColorPrimary , color:?attr/colorControlNormal 等。

  • Q:為什么我的WebView顏色沒有變化?

    A:因為WebView不能使用主題屬性。WebView的顏色實際上取決于網頁內容顏色。網頁的默認背景色是白色,所以盡管設置了主題為 AppCompatDelegate.MODE_NIGHT_YES ,網頁仍然是白色,所以看起來就很不搭了。所以,網頁的內容和背景色等資源也需要適配了。

  • Q:為什么不直接設置為 MODE_NIGHT_AUTO 呢?

    A:因為使用 MODE_NIGHT_AUTO 需要請求坐標權限,獲取系統的位置。你肯定會說了,這尼瑪不是坑爹嗎?如果程序已經授予了坐標權限(location permission)(如果你的target SDK為23或者更高,需要考慮運行時權限),AppCompat會試著去獲取上次保存的坐標,根據坐標來計算日出與日落的時間。如果程序沒有位置權限或者LocationManager沒有存儲上次坐標的信息呢?系統或默認設置為早上6點鐘為日出,下午10點為日落。用戶調整系統時間,當前的主題也會隨之改變。如果我們不希望用戶在設定主題后,主題還會隨著時間改變, MODE_NIGHT_AUTO 就不適用了。

運行效果

在Android 6.0及以下的設備上,本項目運行時會有切換的過渡動畫效果,但是不支持Android 7.0及以上的設備。

 本文由用戶 louboutink8 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!