今天是端午節,祝大家節日快樂
這篇文章著實醞釀了許久,一直懶得寫。網上關于通知欄樣式適配的文章很多,但還不夠完美,這也是我寫這篇文章的動力所在。
溫故而知新
Android通知有兩種,默認通知與自定義通知。默認通知簡單調用系統接口就能實現,如下:
自定義通知就稍微麻煩一些,需要定義一個layout文件,使用RemoteViews加載它并設置一些點擊事件,再設置到builder,如下:
這個通知很簡單,就是兩行文本加上一個按鈕,按鈕具有單獨的點擊事件,點擊后跳轉到AnotherActivity。
注意:smallIcon對于自定義通知和默認通知都是必須的,否則通知顯示不出來。道理很簡單,smallIcon需要在狀態欄上顯示,不設置怎么行。在5.0及以上,smallIcon必須符合Material Design風格,即白色內容,透明背景。不然系統會使用默認的圖片替換。具體可參考Android通知欄微技巧,那些你所沒關注過的小細節 標簽: android通知通知欄微技巧。后面我會有一篇更詳細的文章來介紹這個。contentIntent對于2.3及以下的系統是必須的,否則發送通知時會拋異常。道理也很簡單,Android 2.3及以下系統不支持給自定義通知上的元素綁定單獨的點擊事件,因此必須設置整個通知的點擊事件。
為什么要進行樣式適配?
默認通知不存在樣式適配的問題,因為默認通知的布局、顏色、背景什么的都是系統的,系統總會正確的顯示默認通知。但自定義通知就不一樣了,自定義通知的布局完全由我們自己掌控,我們可以為元素設置任何背景、顏色。那么,問題來了。Android通知欄的背景各種各樣,不同的ROM有不同的背景,白色、黑色、透明等。不同的Android版本通知欄背景也不一樣,一旦我們為自定義通知上的元素設置了特定背景或顏色,就肯定會帶來兼容性問題(主要是文本啦)。這樣的應用一大把,貼個圖大家就明白了:
怎么適配?
適配的方式大概有兩種,一種簡單粗暴:為自定義通知設置固定的背景(上圖中的360衛士就這么干的),比如黑色。那么內容自然就是白色或近似白色。這樣,在所有的手機上都能正常顯示,不會出現在黑色背景通知欄上顯示良好,到了白色背景通知欄上就幾乎啥也看不見。使用這種方案的應用太多了。我個人很不推崇這種方式,這樣會使得自定義通知在將近一半的手機上顯示得很突兀,和系統的通知欄不夠沉浸,影響整體美觀。另一種方案就稍微合理一些:通過讀取系統的通知欄樣式文件,獲取到title和content的顏色,進而將這個顏色設置到自定義通知上。讀取通知欄樣式文件本身有兼容性問題,不同Android版本的樣式文件有變,具體可參考這篇博客 通知欄設置系統字體顏色 ,這種方式也不是在所有手機上生效,實際測試發現,還是有小部分機型沒法讀取或是讀取到的是錯誤的。拿到title和content的顏色后,還可以通過算法(后面細說)判斷這個顏色是近似白色還是近似黑色,進而能判斷出通知欄的背景是近似黑色還是近似白色,這樣就能根據不同的通知欄背景加載不同的自定義通知布局。進而做到良好的適配。
更好的適配
現在切入主題,談談如何來更好的適配自定義通知。有過鎖屏開發經驗的人應該知道,如果你的應用有讀取系統通知欄的權限,那么每當應用程序發出一個通知,你的應用都會收到對應的notification對象,這個時候,我們一般會執行以下操作:
調用addView之后,應用程序的通知就會顯示在我們的應用里。顯然,上面的代碼并沒有對apply返回的notificationItemLayout做任何其他操作,但確實這個View顯示出來時就是樣式良好的,可見,notificationItemLayout本身就是帶有樣式的,即便是默認通知。那么方案來了!我們先構造一個默認通知:
通知并不發送出去,只是用來獲取通知欄title的顏色,如果你還想獲取content的顏色,抱歉,不能通過查找android.R.id.text來獲取,這個字段是訪問不到的。可通過反射獲取,更好的辦法是先預先設置一個content,然后遍歷viewGoup根據content內容找到對應的TextView再獲取顏色。
拿到顏色后,可根據算法判斷這個顏色是近似白色還是近似黑色,我們使用黑色作為基準色,使用方差來計算這個顏色是否近似黑色:
baseColor傳入Color.BLACK,color傳入剛剛獲取到的title的顏色,根據我實測,閾值為180.0較為合理。上述方法返回true,即表示title的顏色近似黑色,也就是說通知欄背景近似白色。
額,經驗豐富的同學應該已經洞察到第二段代碼存在的兼容性問題了:根據android.R.id.title去找到title對應的TextView是不靠譜的,因為有些ROM廠商會把id改掉,導致找到的title為空。
同時還有另外一個問題:使用上述方法,Activity不能繼承自AppCompatActivity(實測5.0以下機型可以,5.0及以上機型不行),大致的原因是默認通知布局文件中的ImageView(largeIcon和smallIcon)被替換成了AppCompatImageView,而在5.0及以上系統中,AppCompatImageView的setBackgroundResource(int)未被標記為RemotableViewMethod,導致apply時拋異常。
為了解決這兩個問題,我們改進getNotificationColor方法:
在getNotificationColorInternal中,設置一個默認的title文本,如果根據id找不到title,則遍歷notificationRoot根據設置的title文本找到title:
在getNotificationColorCompat中,我們先構造一個默認通知,獲取到默認通知的布局文件id,并將布局加載到notificationRoot,此時,如果根據id找不到title,顯然設置默認title的辦法已經失效了。如何從notificationRoot中找到title是個問題。我的解決辦法是:反正都已經拿到notificationRoot了,不如就遍歷它,先找到其中的所有TextView,取字體最大的TextView作為title(這是合理的,因為默認通知中最多也就4個TextView,分別是title、content、info、when,title肯定是字體最大,最顯眼的),并返回其顏色:
實際測試
拿到了通知欄背景的顏色后,我們就可以加載不同樣式的布局,達到適配的目的。代碼如下:
效果:
好了,我的第一篇技術博客到此為止,大家假期玩得Happy!