Android 6.0 Marshmallow 預覽版中曾經短暫出現過相關的夜間模式的功能,只是在正式版中被移除了,在Android 7.0 Nougat上,用戶們再次經歷了「得而復失」的遺憾,在開發者預覽版中,夜間模式和暗色模式先是開啓,而後有再次被移除。而在正式版中,夜間模式也沒有出現。但其實相關的代碼一直存在於系統中,只是默認沒有被開啓。如何開啓這項功能,能夠參考少數派的這一篇文章,幫你找回 Android 7.0 夜間模式的 2 款應用。java
不過,今天要介紹的主要內容並非關於系統的夜間模式,而是如何給咱們開發的APP添加夜間模式的功能。絕不誇張的說,夜間模式如今已是閱讀類App的標配了。事實上,日間模式與夜間模式就是給APP定義並應用兩套不一樣顏色的主題。用戶能夠自動或者手動的開啓。咱們先看兩個我認爲實現地很優雅的例子:知乎和Twitter。android
這兩個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
讓咱們本身的主題繼承並應用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>
新建夜間模式資源文件夾:在res
目錄下新建values-night
文件夾,而後在此目錄下新建colors.xml
文件在夜間模式下的應用的資源。固然也能夠根據須要新建drawable-night
,layout-night
等後綴爲-night
的夜間資源文件夾。
個人values
和values-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>
使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 }
雖然上面的靜態應用的設置很是簡單,可是這種方式的應用場景仍是太少了。咱們更多的仍是須要動態的根據須要動態的切換。
檢測當前主題模式
int mode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
設置主題
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:使用主題屬性,例如: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 .
運行效果
在Android 6.0及如下的設備上,本項目運行時會有切換的過渡動畫效果,可是不支持Android 7.0及以上的設備。