
Android Oreo出來一段時(shí)間了,提供了很多新特性,比如通知欄樣式、自動填充、電池壽命優(yōu)化、自動調(diào)整TextView、XML中使用字體、可下載的字體和表情(有兩篇文章推薦Android Oreo可下載字體和android開發(fā)排版指南)、自適應(yīng)圖標(biāo)、快捷方式固定、應(yīng)用程序的寬色域顏色、WebView功能增強(qiáng)、Java 8語言API和運(yùn)行時(shí)優(yōu)化,等等。
本文介紹Oreo 8.0對通知欄帶來了的變化以及用戶體驗(yàn)。
1,通知渠道
作為android開發(fā)者,肯定能很熟悉地寫出彈出通知欄的代碼:
var id = 1000
private fun showNotification() {
var message = "message:${Math.random()}"
var mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager//初始化要用到的系統(tǒng)服務(wù)
var mBuilder = NotificationCompat.Builder(this)
mBuilder.setContentTitle(getString(R.string.app_name))
.setContentText("內(nèi)容")
// .setContentIntent(getDefalutIntent(Notification.FLAG_AUTO_CANCEL))
// .setNumber(number)//顯示數(shù)量
.setTicker("測試通知來啦")//通知首次出現(xiàn)在通知欄,帶上升動畫效果的
.setWhen(System.currentTimeMillis())//通知產(chǎn)生的時(shí)間,會在通知信息里顯示
.setPriority(Notification.PRIORITY_MAX)//設(shè)置該通知優(yōu)先級
.setAutoCancel(true)//設(shè)置這個(gè)標(biāo)志當(dāng)用戶單擊面板就可以讓通知將自動取消
.setOngoing(false)//ture,設(shè)置他為一個(gè)正在進(jìn)行的通知。他們通常是用來表示一個(gè)后臺任務(wù),用戶積極參與(如播放音樂)或以某種方式正在等待,因此占用設(shè)備(如一個(gè)文件下載,同步操作,主動網(wǎng)絡(luò)連接)
// .setDefaults(Notification.DEFAULT_VIBRATE)//向通知添加聲音、閃燈和振動效果的最簡單、最一致的方式是使用當(dāng)前的用戶默認(rèn)設(shè)置,使用defaults屬性,可以組合:
//Notification.DEFAULT_ALL Notification.DEFAULT_SOUND 添加聲音 // requires VIBRATE permission
.setSmallIcon(R.mipmap.icon_anim_plane_1)
mBuilder.setAutoCancel(true)//點(diǎn)擊后讓通知將消失
.setContentTitle(getString(R.string.app_name))
.setContentText(message)
.setTicker(message)
mNotificationManager.notify(id, mBuilder.build())
}
這是Oreo 8.0 (API 26)之前規(guī)范的寫法,但是這種寫法在Oreo 8.0手機(jī)上不能喚起通知欄,而且NotificationCompat.Builder(Context context)方法已經(jīng)被標(biāo)記為deprecated,取而代之的是NotificationCompat.Builder(Context context, String channelId)??梢钥吹?,構(gòu)造器添加了一個(gè)新的參數(shù)String channelId,這個(gè)是是渠道ID,每個(gè)通知都必須有一個(gè)渠道ID,其允許您為要顯示的每種通知類型創(chuàng)建用戶可自定義的渠道。用戶界面將通知渠道稱之為通知類別。
//NotificationBuilder.kt
@TargetApi(Build.VERSION_CODES.O)
class NotificationBuilder(
private val context: Context,
private val safeContext: Context = context.safeContext(),
private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(safeContext),
private val channelBuilder: NotificationChannelBuilder = NotificationChannelBuilder(context, CHANNEL_IDS),
private val random: Random = Random()
) {
private var notificationId: Int = 62//by bindSharedPreference(context, KEY_NOTIFICATION_ID, 0)
fun sendBundledNotification(message: Message) =
with(notificationManager) {
channelBuilder.ensureChannelsExist(createChannel)
randomChannelId.also {
notify(notificationId++, buildNotification(message, it))
notify(SUMMARY_ID, buildSummary(message, it))
}
}
private val randomChannelId
get() = CHANNEL_IDS[random.nextInt(CHANNEL_IDS.size)]
private val createChannel: (channelId: String) -> NotificationChannel? = { channelId ->
when (channelId) {
IMPORTANT_CHANNEL_ID -> NotificationChannel(channelId,
context.getString(R.string.important_channel_name),
NotificationManager.IMPORTANCE_HIGH)
.apply {
description = context.getString(R.string.important_channel_description)
}
NORMAL_CHANNEL_ID -> NotificationChannel(channelId,
context.getString(R.string.normal_channel_name),
NotificationManager.IMPORTANCE_DEFAULT)
.apply {
description = context.getString(R.string.normal_channel_description)
}
LOW_CHANNEL_ID -> NotificationChannel(channelId,
context.getString(R.string.low_channel_name),
NotificationManager.IMPORTANCE_LOW)
.apply {
description = context.getString(R.string.low_channel_description)
}
else -> null
}
}
private fun buildNotification(message: Message, channelId: String): Notification =
with(NotificationCompat.Builder(context, channelId)) {
message.apply {
setContentTitle(sender)
setContentText(text)
setWhen(timestamp?.toEpochMilli() ?: System.currentTimeMillis())
}
setSmallIcon(getIconId(channelId))
setShowWhen(true)
setGroup(GROUP_KEY)
build()
}
private fun getIconId(channelId: String) =
when (channelId) {
IMPORTANT_CHANNEL_ID -> R.drawable.ic_important
LOW_CHANNEL_ID -> R.drawable.ic_low
else -> R.drawable.ic_message
}
private fun buildSummary(message: Message, channelId: String): Notification =
with(NotificationCompat.Builder(context, channelId)) {
setContentTitle(SUMMARY_TITLE)
setContentText(SUMMARY_TEXT)
setWhen(message.timestamp?.toEpochMilli() ?: System.currentTimeMillis())
setSmallIcon(R.drawable.ic_message)
setShowWhen(true)
setGroup(GROUP_KEY)
setGroupSummary(true)
build()
}
companion object {
private const val KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID"
private const val GROUP_KEY = "Messenger"
private const val SUMMARY_ID = 0
private const val SUMMARY_TITLE = "Nougat Messenger"
private const val SUMMARY_TEXT = "You have unread messages"
private const val IMPORTANT_CHANNEL_ID = "IMPORTANT_CHANNEL_ID"
private const val NORMAL_CHANNEL_ID = "NORMAL_CHANNEL_ID"
private const val LOW_CHANNEL_ID = "LOW_CHANNEL_ID"
private val CHANNEL_IDS =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
listOf(IMPORTANT_CHANNEL_ID, NORMAL_CHANNEL_ID, LOW_CHANNEL_ID)
} else {
listOf(NORMAL_CHANNEL_ID)
}
}
}
//NotificationChannelBuilder.kt
class NotificationChannelBuilder(
context: Context,
private val channelIds: List<String>,
private val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
) {
fun ensureChannelsExist(createChannel: (channelId: String) -> NotificationChannel?) =
ifAtLeast(Build.VERSION_CODES.O) {
notificationManager.ensureChannelsExist(createChannel)
}
@TargetApi(Build.VERSION_CODES.O)
private fun NotificationManager.ensureChannelsExist(createChannel: (channelId: String) -> NotificationChannel?) {
channelIds
.filter { !notificationChannelIds().contains(it) }
.forEach {
createChannel(it)?.also {
notificationManager.createNotificationChannel(it)
}
}
}
@TargetApi(Build.VERSION_CODES.O)
private fun NotificationManager.notificationChannelIds() =
notificationChannels.map { it.id }
}
上面代碼有點(diǎn)長,其實(shí)和API 26之前的寫法差別大概只有三點(diǎn):1,NotificationCompat.Builder使用了新的帶channelId參數(shù)的構(gòu)造器,分別在函數(shù)buildNotification()和函數(shù)buildSummary()中調(diào)用;2,根據(jù)channelId設(shè)置不同的smallIcon,參考方法getIconId();3,有了一個(gè)新的調(diào)用ChannelBuilder.ensureChannelsExist,它檢查不存在的channelId并創(chuàng)建相應(yīng)的渠道。
通知渠道提供了一些屬性:id(唯一標(biāo)識)、name(名字)、importance(重要性)、description(描述)、sound(聲音)、light(光)、vibrate(震動)、LockscreenVisibility(鎖屏可見性)、bypassDnd(免打擾)、showBadge(類似iOS的3DTouch)等等。
通過創(chuàng)建渠道組可以對渠道進(jìn)行分組,調(diào)用 setGroup()方法將某個(gè)渠道關(guān)聯(lián)到某個(gè)渠道組。需要注意的是,只能在將渠道提交給通知管理器NotificationManager之前修改渠道與渠道組之間的關(guān)聯(lián),這所以因?yàn)镹otificationManager調(diào)用createNotificationChannel()方法之后,再對NotificationChannel進(jìn)行的任何修改都將無效。代碼如下:
// 通知渠道組的id.
val group = "channel_group_01"
// 用戶可見的通知渠道組名稱.
var name = "name"
notificationManager.createNotificationChannelGroup(NotificationChannelGroup(group, name))
如果長按一條通知欄,會出現(xiàn)它的渠道名字和其它相關(guān)信息,如圖所示。

點(diǎn)擊“ALL CATEGORIES”按鈕進(jìn)入渠道相關(guān)屬性設(shè)置頁面。
使用新的API創(chuàng)建通知欄基本就這么多,貌似東西也不是很多哦。
2,通知標(biāo)志
Android 8.0中,長按app的啟動圖標(biāo)會顯示通知標(biāo)志,有點(diǎn)類似iOS的3DTouch。只有通知渠道設(shè)置屬性setShowBadge(true)(默認(rèn)是true)的時(shí)候,通知標(biāo)志才會起作用。通知標(biāo)志可反映某個(gè)應(yīng)用是否存在與其關(guān)聯(lián)、并且用戶尚未予以清除也未對其采取行動的通知。通知標(biāo)志也稱為通知點(diǎn)。

3,休眠
用戶可以將通知置于休眠狀態(tài),以便稍后重新顯示它。重新顯示時(shí)通知的重要程度與首次顯示時(shí)相同。應(yīng)用可以移除或更新已休眠的通知,但更新休眠的通知并不會使其重新顯示。操作方式是左滑或者右滑,這時(shí)會出現(xiàn)兩個(gè)按鈕:時(shí)鐘按鈕和設(shè)置按鈕,點(diǎn)擊時(shí)鐘按鈕。
4,通知超時(shí)
現(xiàn)在,使用 setTimeoutAfter() 創(chuàng)建通知時(shí)您可以設(shè)置超時(shí)。您可以使用此函數(shù)指定一個(gè)持續(xù)時(shí)間,超過該持續(xù)時(shí)間后,通知應(yīng)取消。如果需要,您可以在指定的超時(shí)持續(xù)時(shí)間之前取消通知。
5,通知設(shè)置
當(dāng)您使用Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCESIntent 從通知?jiǎng)?chuàng)建指向應(yīng)用通知設(shè)置的鏈接時(shí),您可以調(diào)用 setSettingsText() 來設(shè)置要顯示的文本。此系統(tǒng)可以提供以下 Extra 數(shù)據(jù)和 Intent,用于過濾應(yīng)用必須向用戶顯示的設(shè)置:EXTRA_CHANNEL_ID、NOTIFICATION_TAG 和 NOTIFICATION_ID。
6,通知清除
系統(tǒng)現(xiàn)在可區(qū)分通知是由用戶清除,還是由應(yīng)用移除。要查看清除通知的方式,您應(yīng)實(shí)現(xiàn) NotificationListenerService 類的新 onNotificationRemoved() 函數(shù)。
7,背景顏色
您現(xiàn)在可以設(shè)置和啟用通知的背景顏色。只能在用戶必須一眼就能看到的持續(xù)任務(wù)的通知中使用此功能。例如,您可以為與駕車路線或正在進(jìn)行的通話有關(guān)的通知設(shè)置背景顏色。您還可以使用 Notification.Builder.setColor() 設(shè)置所需的背景顏色。這樣做將允許您使用 Notification.Builder.setColorized() 啟用通知的背景顏色設(shè)置。
8,消息樣式
現(xiàn)在,使用 MessagingStyle 類的通知可在其折疊形式中顯示更多內(nèi)容。對于與消息有關(guān)的通知,您應(yīng)使用 MessagingStyle 類。您還可以使用新的 addHistoricMessage() 函數(shù),通過向與消息相關(guān)的通知添加歷史消息為會話提供上下文。
參考文獻(xiàn):
- https://blog.stylingandroid.com/oreo-notifications-channels-part-1/
- https://blog.stylingandroid.com/oreo-notifications-channels-part-2/
- https://developer.android.com/reference/android/app/NotificationChannel.html
- https://www.androidauthority.com/android-8-0-oreo-app-implementing-notification-channels-801097/
- https://developer.android.com/about/versions/oreo/index.html
- http://www.itdecent.cn/p/92afa56aee05