簡潔優雅地實現夜間模式

前言

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

不過,今天要介紹的主要內容並非關於系統的夜間模式,而是如何給咱們開發的APP添加夜間模式的功能。絕不誇張的說,夜間模式如今已是閱讀類App的標配了。事實上,日間模式與夜間模式就是給APP定義並應用兩套不一樣顏色的主題。用戶能夠自動或者手動的開啓。咱們先看兩個我認爲實現地很優雅的例子:知乎和Twitter。android

zhihu_twitter_nightmode.gif

這兩個APP在切換的工程中,並無出現閃現黑屏的狀況,切換也比較順滑。咱們的目標就是利用Support Library實現一樣的效果。git

實現

添加依賴

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了。github

準備資源

  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的夜間資源文件夾。
    個人valuesvalues-night目錄下的colors.xml的內容以下:app

<?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>
  1. 使Activity繼承自AppCompatActivity。ide

    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個:ui

  • MODE_NIGHT_NO. Always use the day (light) theme(一直應用日間(light)主題).spa

  • 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就不適用了。

代碼

本項目代碼PaperPlane .
運行效果

paper_plane.gif

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

相關文章
相關標籤/搜索