Categories
程式開發

優酷暗黑模式(五):暗黑模式的技術實現策略


正如本系列第一篇文章所介紹的,Google是從Android 10 正式發布了對暗黑模式的支持,之前隨公眾理解的深色氛圍一躍而上成為系統平台​​級能力。暗黑模式帶給了我們什麼呢?

  • 深色界面在專注環境下與內容有更高的契合度,更凸顯內容、緩解視覺疲勞;
  • 深色界面更易營造品質感與沈浸感;
  • 深色界面更易建立填充感。

一、暗黑模式項目背景

暗黑模式作為一種系統平台能力,它打造的是整個用戶使用周期的全鏈路視覺體驗,也就是要覆蓋到用戶能到達的每一個角落。

這包括了分發場景,搜索場景,消費場景等複雜的頁面結構,也包括二級落地頁,活動頁等獨立頁面; 還包括了彈窗,Toast,播放器等常用組件。要全面覆蓋如此復雜細碎的場景,需要實現整體性的視覺呈現效果和低成本的全局平台開關。

優酷暗黑模式(五):暗黑模式的技術實現策略 1

分析完暗黑模式的影響範圍,我們再來看一下我們實踐的對象-優酷APP。優酷App發展到今天,已經從一個單體App,進化成了一個承載集團眾多業務出口的超級App。所以優酷App的頁面是由十幾個完整建制的內部和外部團隊共同開發和維護的。

暗黑模式這種全站範圍的適配將涉及到上百位設計/開發/測試同學,管理成本,溝通和協調成本,最終落地成本都非常高。

對於這種全站規格的需求,以往優酷的經驗是會大量佔用業務需求的開發人力,甚至會因此delay部分業務需求。這種開發模式是很難延續,不可接受的,本次暗黑模式的適配,我們既希望在第一時間將暗黑模式呈現給客戶的,也希望能以更低的成本來完成項目需求。

這給項目方案提出了很大的挑戰,但是得益於優酷之前已經實施了設計標準化體系,因此我們有信心和底氣完成這些挑戰。

二、暗黑模式在優酷的實踐

首先,得益於優酷設計標準化體系的落地,我們已經提煉出公共資源庫,構建了多層的DesignToken體系; 並對大部分一級頁面的業務組件進行了接入。所以我們的工作就變成了兩部分,一部分是已經接入設計標準化的視覺元素,這部分可以通過修改公共資源庫中的Token定義,直接添加對暗黑模式的支持,零成本完成適配。另一部分則是擴大設計標準化體系的覆蓋範圍,在適配暗黑模式的同時,完成更多業務的技術架構的基礎建設。

其次,我們還對於暗黑模式進行了色彩分層,靜態色層是全站使用的基礎色值,它直接對應著一個具體的值。它不會隨著視覺模式的變化而變化。在它之上的是動態色層,動態色在不同的視覺模式下,對應不同的靜態色。這是通過Android原生的資源加載機製完成: 即暗黑模式對應關係在暗黑資源文件中,普通模式在普通資源文件夾中。

在動態色層之上,是代碼編寫的色彩管理器,它在合適的時間會去獲取當前的所有靜態/動態色值。設計這一層有兩個原因: 一個是提高性能,提前緩存一份給更上層調用,另一個是形成中間層。

眾所周知,XML資源文件的動態性是不足的。 XML資源啟動即加載,加載後就是只讀的。有了這一層,我們可以支持服務端動態下髮色值Token的定義,以達成一定程度的動態性。

在色彩管理器之上,是公共的控件和組件層。有了這樣的層次關係,使最終的業務設計可以通過搭建完成,完全不需要從零寫起,也不需要關注設計標註的細節,開發再也不用逐個元素的調整,設計也不需要逐個像素的校對。只要在第一次納入的時候進行一次就可以完成,大幅提高了工作效率。

優酷暗黑模式(五):暗黑模式的技術實現策略 2

靜態Token:

優酷暗黑模式(五):暗黑模式的技術實現策略 3

動態Token:

動態色對照表

優酷暗黑模式(五):暗黑模式的技術實現策略 4

在實際的適配中,佈局文件中需要注意的是要使用Token來設置組件屬性,而在代碼中則可以通過公共資源庫中提供的工具方法UIMode.isDarkMode()來讀取當前是否是暗黑模式,通過ColorConfigureManager.getInstance().getColorMap().get(token名)來獲得色值。

具體的適配工作可以看後面的相關文章。 08暗黑模式在優酷分發場景的落地、09暗黑模式在優酷消費場景的落地 Android。

下面是暗黑模式適配的示例代碼:




    



//获取下拉刷新DesignToken色值
int refresBgColor = ColorConfigureManager.getInstance().getColorMap().get(YKN_DEEP_BLUE_GRADIENT_MIDDLE_POINT);
//设置下拉刷新
mYkClassicsHeader.setBgColor(refresBgColor);

//获取顶部导航背景资源,对应android来说都是一个命名,暗黑模式的资源单独放在night目录下
int defaultImage = R.drawable.yk_top_bg;
//设置顶部导航背景
setPlaceHoldForeground(getResources().getDrawable(defaultImage));

//获取页面背景色
int backGroundColor=ColorConfigureManager.getInstance().getColorMap().get(YKN_PRIMARY_BACKGROUND);
//设置页面背景色
setFragmentBackGroundColor();

此外,我們在適配暗黑的過程中,還遇到了很多具體的問題,比如

1、低版本Android系統如何支持暗黑模式

雖然Google是在Android 10的版本中才默認添加了暗黑模式的切換開關,但是,在之前的系統版本中已經預埋了對暗黑資源文件夾的加載能力; 而且有一部分廠商如小米就在Android 9的MIUI定製版本中提供了切換”暗黑模式”的開關。

所以對於低版本的用戶,我們也提供了適配方案。具體來說,我們是通過調用系統API

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO/MODE_NIGHT_YES);

來觸發模式切換的。

需要特別注意的是,使用這個函數是有一些坑的。比如,在使用它之後,如果沒有在Activity的manifest文件中增加

android:configChanges=“UIMODE”

的話,在Activity轉屏也會引起Activity重建。

一般的Activity開發邏輯可能不會考慮到這種場景,很多邏輯在這裡是會引發Bug的。

2、如何監視暗黑模式的切換

可以通過onConfigurationChanged進行監聽。

這裡一般有兩種情況:

一種是活動需要監聽它,來進行手動刷新,這時需要在manifest文件增加上面的配置。才能夠監聽到系統回調。

另一種是某個控件或組件,或者我們的全局狀態,可能需要獨立監聽,這種情況按下面的代碼進行監聽。

getApplication().registerComponentCallbacks(new ComponentCallbacks() {
        @Override
        public void onConfigurationChanged(Configuration newConfig) {
        //do something
        }
}

3、系統刷新和手動刷新

暗黑模式的切換必然需要重新渲染頁面,這裡我們分兩種刷新方式:

一種是直接交給系統,系統會在切換是重建Activity從而引發頁面重新渲染。這種適合”用戶使用行為不需要記錄狀態”的Native頁面,和Weex/H5等動態頁面。缺點就是會丟失用戶之前的視覺錨點。

另一種, 則是自己監聽onConfigurationChanged事件然後手動進行有目的範圍的”局部刷新”。比如優酷App中的播放頁。請參考《極致酷黑: 優酷暗黑模式實現系列 – (9)暗黑模式在優酷消費場景的落地 Android》

4、設計體系未覆蓋的老舊頁面如何適配

在眾多的頁面中,有一些老舊的頁面改造成本過高,甚至已無人維護。

如果不作任何處理的話,這些頁面會因為同時使用已改造組件和未改造組件而造成不同視覺模式同時出現。為了了避免這種情況的發生,我們會在頁面進入時,強制指定頁面的視覺模式方法是:

getDelegate().setLocalNightMode(MODE_NIGHT_YES/MODE_NIGHT_NO);

這個API的作用範圍是所屬的Activity。

三、未來的展望

我們認為,設計標準化體系大大的提高了類似”暗黑模式”這種全站視覺變更項目的效率,DesignToken的設計將開發從繁瑣的視覺效果開發中解脫了出來。

優酷的暗黑模式適配在兩週之內順利完成,並且沒有影響同期的產品需求。未來我們會繼續深化和豐富標準化設計體系的能力,也希望這種開發方式可以在不同的App間變成通用的開發範式。

設計標準化體系的開發,對於公共組件池的跨應用使用,Weex/Flutter/小程序等的跨應用通投都有重要的參考意義。

作者簡介

涵父,阿里文娛無線開發專家。

相關閱讀

優酷暗黑模式(一):是什麼、為什麼、如何落地?

優酷暗黑模式(二):如何建立設計語言標準化管理體系

優酷暗黑模式(三):暗黑模式設計指南

優酷暗黑模式(四):設計標準化的技術實現