通知栏Notification
参考资料:
https://blog.csdn.net/shanshui911587154/article/details/105683683
https://blog.csdn.net/yechaoa/article/details/125465158
https://developer.android.google.cn/develop/ui/views/notifications?hl=zh-cn#appearances
概述
通知是 Android 在您的应用 UI 之外显示的消息(一般是在屏幕顶部出现),用于向用户提供提醒、来自其他人的通信或来自您的应用的其他及时信息。用户可以点击通知打开您的应用或直接从通知中执行操作。
通知以不同的位置和格式向用户显示,例如状态栏中的图标、通知抽屉中更详细的条目、应用程序图标上的徽章以及自动配对的可穿戴设备。当发出通知时,它首先在状态栏中显示为一个图标。
- 用户可以在状态栏上向下滑动以打开通知抽屉,他们可以在其中查看更多详细信息并根据通知执行操作。
- 用户可以向下拖动抽屉中的通知以显示展开的视图,该视图显示其他内容和操作按钮(如果通知的样式有提供)。从 Android 13 开始,此展开视图包含一个按钮,可让用户停止具有持续前台服务的应用。
- 通知在通知抽屉中保持可见,直到被应用程序或用户关闭。
各版本的适配改动
下面的说明来自于安卓开发者文档
Android 5.0,API 级别 21
- 引入了锁定屏幕和浮动通知。
- 允许用户将手机设为勿扰模式,并配置允许哪些通知在设备处于“仅限优先”模式时打扰他们。
- 添加了设置是否在锁定屏幕上显示通知的方法(例如
setVisibility()
),并指定通知文本的“公开”版本(即设定内容是否在锁屏时可见)。 - 添加了
setPriority()
方法,告知系统通知的干扰程度。例如,将优先级设置为“高”会使通知以浮动通知的形式显示。 - 为 Android Wear(现称为 Wear OS)设备添加了通知堆栈支持。使用
setGroup()
) 将通知放入堆栈。在 Android 7.0(API 级别 24)之前,平板电脑或手机不支持通知堆栈(之后称为组或软件包)。
Android 7.0,API 级别 24
- 调整了通知模板的样式,以强调大图片和头像。
- 添加了三个通知模板:一个用于即时通讯应用,另外两个用于使用可展开功能和其他系统装饰来装饰自定义内容视图。
- 添加了对通知组的手持设备(例如手机和平板电脑)的支持。使用与 Android 5.0(API 级别 21)中引入的 Android Wear(现称为 Wear OS)通知堆栈相同的 API。
- 允许用户使用内嵌回复功能在通知内回复。用户可以输入文本,系统会将文本路由到通知的父级应用。
Android 8.0,API 级别 26
- 将各个通知放入特定渠道。(注意,渠道和通知组不是同一个概念)
- 允许用户按渠道关闭通知,而不是关闭来自某个应用的所有通知。
- 让具有活动通知的应用在主屏幕或启动器屏幕上的应用图标上方显示通知标志。
- 允许用户暂停抽屉式通知栏中的通知。您可以为通知设置自动超时时间
- 通过此设置,您可以设置通知的背景颜色。
- 将一些与通知行为相关的 API 从
Notification
移至NotificationChannel
。例如,对于 Android 8.0 及更高版本,请使用NotificationChannel.setImportance()
而非NotificationCompat.Builder.setPriority()
。(意思是将一些Notification的配置工作移到NotificationChannel中进行)
Android 13.0,API 级别 33
Android 14.0,API 级别 34
- 仅限提供通话和闹钟的应用使用全屏 intent 通知。使用
NotificationManager.canUseFullScreenIntent
API 检查您的应用是否具有权限。否则,您的应用可以使用ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT
启动设置页面,在该页面中,用户可以授予权限。 - 即使设置了
Notification.FLAG_ONGOING_EVENT
标志,也允许用户关闭通知操作来更改用户体验不可关闭通知的方式。如果已设置Notification.FLAG_ONGOING_EVENT
标志或设备政策控制器 (DPC) 和企业支持软件包,则这不适用于CallStyle
通知。当手机处于锁定状态或用户选择全部清除时,此规则也不适用。
通知的样式解析
通知的设计由系统模板决定,您的应用会定义模板的每个部分的内容。通知的某些详细信息仅在展开视图中显示。下面是开发者文档中的通知示例图:
上图显示了通知的最常见部分,如下所示:
- 小图标:必需;使用 setSmallIcon() 进行设置。
- 应用名称:由系统提供。
- 时间戳:由系统提供,但您可以使用 setWhen() 替换它,也可以使用 setShowWhen(false) 隐藏它。
- 大图标:可选;通常仅用于联系人照片。 请勿将其用于应用图标。使用 setLargeIcon() 进行设置。
- 标题:可选;使用 setContentTitle() 进行设置。
- 文本:可选;使用 setContentText() 进行设置。
我们强烈建议使用系统模板,以便在所有设备上实现适当的设计兼容性。如有必要,您可以创建自定义通知布局。
通知上的交互处理
虽然并非强制要求,但最好让每个通知在被点击时做一些处理(如音乐播放器的暂停开始等)。
- 从 Android 7.0(API 级别 24)开始,您可以添加操作来回复消息或直接从通知中输入其他文本。
- 从 Android 10(API 级别 29)开始,平台可以自动生成操作按钮,此类按钮包含基于 intent 的建议操作。
展开式通知
默认情况下,通知的文字内容会被截断以放在一行。如果您想更长的通知,可以通过应用一个额外的模板来启用更大的可展开文本区域,如下图所示:
除了大文本样式,您还可以使用图片、收件箱样式、聊天对话或媒体播放控件来创建展开式通知。一般可以使用Builder中的setStyle()方法来实现。通常情况下,收起后的视图布局的高度上限为 64 dp,展开后的视图布局的高度上限为 256 dp。
优先级分类
Android 利用通知的重要性来确定通知在视觉和听觉上对用户干扰的程度。通知的重要性越高,干扰程度就越高。
- 在 Android 7.1(API 级别 25)及更低版本中,通知的重要性由通知的
priority
决定。 - 在搭载 Android 8.0(API 级别 26)及更高版本的设备上,通知的重要性由通知发布到的渠道的
importance
决定。
通知可以根据重要程度来设定优先级,有以下分类:
优先级 | 描述 |
---|---|
MAX | 重要而紧急的通知,通知用户这个事件是时间上紧迫的或者需要立即处理的。【发出声音并显示为提醒通知】 |
HIGH | 高优先级用于重要的通信内容,例如短消息或者聊天,这些都是对用户来说比较有兴趣的。【发出声音并显示为提醒通知】 |
DEFAULT | 默认优先级用于没有特殊优先级分类的通知。【发出声音】 |
LOW | 低优先级可以通知用户但又不是很紧急的事件。【没有声音】 |
MIN | 用于后台消息 (例如天气或者位置信息)。最低优先级通知将只在状态栏显示图标,只有用户下拉通知抽屉才能看到内容。【无声音且不出现在状态栏中】 |
PRIORITY_MIN
(-2): 最低优先级的通知,可能不会出现在状态栏,并且不会打断用户。PRIORITY_LOW
(-1): 低优先级的通知,可能只会在展开的状态栏中显示。PRIORITY_DEFAULT
(0): 默认优先级,这将是大多数通知的优先级。PRIORITY_HIGH
(1): 高优先级的通知将在状态栏中更显眼的位置显示,如弹出式通知。PRIORITY_MAX
(2): 最高优先级的通知,会立即显示在状态栏顶部,并可能伴有全屏意图。
使用步骤
这里先介绍下使用Notification几个相关的类:
NotificationManager // 通知管理器,用来发起、更新、删除通知
NotificationChannel // 通知渠道,8.0及以上配置渠道以及优先级
NotificationCompat.Builder // 通知构造器,用来配置通知的布局显示以及操作相关
使用步骤如下:
- 使用NotificationChannel创建通知渠道(Android 8.0及以上)
- 使用NotificationCompat.Builder构造器来创建一个Notification对象;这样的好处是,可以解决不同版本下的API不兼容性。
- 设定Notification对象的属性,包括图标、标题、跳转的activity等;
- 创建一个NotificationManager对象,通过该对象的notify()方法启动通知栏。
- 更新通知,依然是使用notify()方法,不过这里需要传入指定的NotificationI,用来告诉系统你要更新的是哪个通知。
一个简单的操作示例:
// 安卓8.0及以上需要创建渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
NORMAL_CHANNEL_ID,
"CHANNEL_NAME",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}
// 构建一个PendingIntent
val intent = Intent(this, SecondActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE)
// 构建Notification配置
val builder = NotificationCompat.Builder(this@MainActivity, NORMAL_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Normal")
.setContentText("Normal Message")
.setContentIntent(pendingIntent)
.setAutoCancel(true)
// 发起通知
notificationManager.notify(1, builder.build())
Notification.Builder常用方法
这里比较重要的是Notification.Builder 中涉及到构造Notification的各个方法,常用的方法如下:
-
setContentTitle(CharSequence):设置标题
-
setContentText(CharSequence):设置内容
-
setSubText(CharSequence):设置内容描述(内容下面一小行的文字)
-
setTicker(CharSequence):设置收到通知时在顶部显示的文字信息
-
setWhen(long):设置通知时间,一般设置的是收到通知时的System.currentTimeMillis()
-
setSmallIcon(int):设置右下角的小图标,在接收到通知的时候顶部也会显示这个小图标
-
setLargeIcon(Bitmap):设置左边的大图标
-
setAutoCancel(boolean):用户点击Notification点击面板后是否让通知取消(默认不取消)
-
setDefaults(int):向通知添加声音、闪灯和振动效果的最简单、 使用默认(defaults)属性,可以组合多个属性:
- Notification.DEFAULT_VIBRATE(添加默认震动提醒);
- Notification.DEFAULT_SOUND(添加默认声音提醒);
- Notification.DEFAULT_LIGHTS(添加默认三色灯提醒)
- Notification.DEFAULT_ALL(添加默认以上3种全部提醒)
-
setVibrate(long[]):设置振动方式,比如:
setVibrate(new long[] {0,300,500,700}); 含义是:延迟0ms,然后振动300ms,在延迟500ms, 接着再振动700ms。
-
setLights(int argb, int onMs, int offMs):设置三色灯,参数依次是:灯光颜色, 亮持续时间,暗的时间,不是所有颜色都可以,这跟设备有关,有些手机还不带三色灯; 另外,还需要为Notification设置flags为Notification.FLAG_SHOW_LIGHTS才支持三色灯提醒!
-
setSound(Uri):设置接收到通知时的铃声,可以用系统的,也可以自己设置,例子如下:
.setDefaults(Notification.DEFAULT_SOUND) //获取默认铃声
.setSound(Uri.parse(“file:///sdcard/xx/xx.mp3”)) //获取自定义铃声
.setSound(Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, “5”)) //获取Android多媒体库内的铃声 -
setOngoing(boolean):设置为ture,表示它为一个正在进行的通知。他们通常是用来表示 一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载, 同步操作,主动网络连接)。如果不想用户把这个通知清理掉,可以调用setOngoing(true)方法,这样除非你的app死掉或者在代码中取消,否则它都不会消失。
-
setProgress(int,int,boolean):设置带进度条的通知。参数依次为:进度条最大数值、当前进度、进度是否不确定。如果为确定的进度条:调用setProgress(max, progress, false)来设置通知, 在更新进度的时候在此发起通知更新progress,并且在下载完成后要移除进度条 ,通过调用setProgress(0, 0, false)既可。如果为不确定(持续活动)的进度条, 这是在处理进度无法准确获知时显示活动正在持续,所以调用setProgress(0, 0, true) ,操作结束时,调用setProgress(0, 0, false)并更新通知以移除指示条
-
setContentIntent(PendingIntent):PendingIntent和Intent略有不同,它可以设置执行次数, 主要用于远程服务通信、闹铃、通知、启动器、短信中。比如这里通过Pending启动Activity:getActivity(Context, int, Intent, int),当然还可以启动Service或者Broadcast (getService()或getBroadcast())。
//PendingIntent的位标识符(第四个参数的可选值):PendingIntent.class public static final int FLAG_CANCEL_CURRENT; // 表示相应的PendingIntent已经存在,则取消前者,然后创建新的PendingIntent, 这个有利于数据保持为最新的,可以用于即时通信的通信场景 public static final int FLAG_IMMUTABLE; // 这个标志表示创建的PendingIntent是不可变的,也就是说,一旦创建,它的意图和数据不能被改变。 public static final int FLAG_MUTABLE; // FLAG_MUTABLE允许PendingIntent在其生命周期内被修改。这意味着创建PendingIntent的应用程序可以在任何时候改变其行为,这可能带来安全风险,尤其是在与其他应用程序共享PendingIntent的情况下。 public static final int FLAG_NO_CREATE; // 表示如果描述的PendingIntent不存在,并不创建相应的PendingIntent,而是返回NULL public static final int FLAG_ONE_SHOT; // 表示返回的PendingIntent仅能执行一次,执行完后自动取消 public static final int FLAG_UPDATE_CURRENT; // 表示更新新的PendingIntent
使用示例:
// 点击后跳转Activity Intent intent = new Intent(context,XXX.class); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); mBuilder.setContentIntent(pendingIntent)
注意:安卓12之后,对PendingIntent的使用做了限制:
pendingIntent必须声明可变性,如需声明特定 PendingIntent 对象是否可变,请分别使用 PendingIntent.FLAG_MUTABLE 或 PendingIntent.FLAG_IMMUTABLE 标志。如果您的应用试图在不设置任何可变标志的情况下创建 PendingIntent 对象,系统会抛出 IllegalArgumentException。
#报错信息如下: PACKAGE_NAME: Targeting S+ (version 10000 and above) requires that one of \ FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if \ some functionality depends on the PendingIntent being mutable, e.g. if \ it needs to be used with inline replies or bubbles.
示例:
// 安卓12之前 PendingIntent pendingActivityIntent = PendingIntent.getActivity(context, R.drawable.ic_launcher, intent, PendingIntent.FLAG_ONE_SHOT); // 安卓12及之后 PendingIntent pendingActivityIntent = PendingIntent.getActivity(context, R.drawable.ic_launcher, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
// 安卓12新增PendingIntent.FLAG_IMMUTABLE和FLAG_MUTABLE的说明 在Android开发中,`PendingIntent`是一个非常重要的类,用于构建一个应用程序可以稍后使用(通常是异步地)的意图(Intent)的封装。从Android 12(API级别31)开始,`PendingIntent`的创建和使用引入了两个关键的标志:`FLAG_IMMUTABLE`和`FLAG_MUTABLE`。这两个标志控制着`PendingIntent`的生命周期和安全性,以适应新的安全模型和性能优化。 ### FLAG_IMMUTABLE 这个标志表示创建的`PendingIntent`是不可变的,也就是说,一旦创建,它的意图和数据不能被改变。不可变的`PendingIntent`提供了更好的安全性和性能,因为它们可以跨进程共享,而不用担心被其他组件修改。它们也可以被缓存,从而减少系统资源的消耗。 当使用`FLAG_IMMUTABLE`时,`PendingIntent`将被序列化并存储在共享内存中,这样即使创建`PendingIntent`的应用程序崩溃或被杀死,`PendingIntent`仍然可以被其他应用程序使用。 ### FLAG_MUTABLE 相比之下,`FLAG_MUTABLE`允许`PendingIntent`在其生命周期内被修改。这意味着创建`PendingIntent`的应用程序可以在任何时候改变其行为,这可能带来安全风险,尤其是在与其他应用程序共享`PendingIntent`的情况下。此外,可变的`PendingIntent`不能被缓存,因此在性能上可能不如不可变的`PendingIntent`。 使用`FLAG_MUTABLE`创建的`PendingIntent`将不会被序列化到共享内存中,因此如果创建它的应用程序崩溃或终止,那么`PendingIntent`将变得无效。 ### 使用场景 - 如果你需要创建一个`PendingIntent`并将其传递给其他应用程序(例如,通过广播接收器或服务),并且你不想让其他应用程序能够修改你的意图,那么你应该使用`FLAG_IMMUTABLE`。 - 如果你创建了一个`PendingIntent`,并打算在后续时刻自己更新它(例如,更改Intent的数据或添加新的数据),则需要使用`FLAG_MUTABLE`。 ### 注意事项 从Android 12开始,当你创建`PendingIntent`时,**必须**明确指定`FLAG_IMMUTABLE`或`FLAG_MUTABLE`中的一个。如果不指定,系统将抛出异常。这是因为Android为了提高安全性和性能,强制要求开发者明确表明`PendingIntent`的意图是否可以被修改。 在实际开发中,你应该尽量使用`FLAG_IMMUTABLE`,除非有特定的需求要求`PendingIntent`必须是可变的。这样做不仅可以避免潜在的安全问题,还能提升应用的性能表现。
-
setPriority(int):设置优先级,参数选值范围为NotificationManager中的几个优先级:
PRIORITY_MIN
(-2): 最低优先级的通知,可能不会出现在状态栏,并且不会打断用户。PRIORITY_LOW
(-1): 低优先级的通知,可能只会在展开的状态栏中显示。PRIORITY_DEFAULT
(0): 默认优先级,这将是大多数通知的优先级。PRIORITY_HIGH
(1): 高优先级的通知将在状态栏中更显眼的位置显示,如弹出式通知。PRIORITY_MAX
(2): 最高优先级的通知,会立即显示在状态栏顶部,并可能伴有全屏意图。
-
setNumber(int num) :桌面角标通知数量
-
addAction() :通知上的操作,在Android10.0及以上,系统也会默认识别并添加一些操作,比如短信通知上的「复制验证码」。使用示例如下:
// 在通知上添加一个自定义操作,可以对这个titleText添加一个PendingIntent,这里效果跟点击通知栏一样 .addAction(R.mipmap.ic_big_iamge, "点击查看", pendingIntent)
-
setCategory(): 通知类别,"勿扰模式"时系统会决定要不要显示你的通知
-
setVisibility(int visible) :锁屏时,通知在屏幕上的可见性,可选值有下面三个:
// androidx/core/app/NotificationCompat.java // 在所有锁屏上完整显示此通知 public static final int VISIBILITY_PUBLIC = Notification.VISIBILITY_PUBLIC; // 在所有锁屏上显示此通知,但在安全锁屏上隐藏敏感或私人信息。 public static final int VISIBILITY_PRIVATE = Notification.VISIBILITY_PRIVATE; // 不要在安全锁屏上显示此通知的任何部分 public static final int VISIBILITY_SECRET = Notification.VISIBILITY_SECRET;
-
setStyle():设置各个类型的Notification,如大文本,支持的类型有:
BigPictureStyle、BigTextStyle、MessagingStyle、InboxStyle、DecoratedCustomViewStyle
-
setCustomContentView() 默认布局显示,即折叠状态下的布局
-
setCustomBigContentView() 展开状态下的布局
-
setTimeoutAfter() :定时取消,8.0新增的方法
-
setGroup(String groupName) :添加到通知组groupName中。如果您的应用发出 4 条或更多条通知且未指定通知组,则系统会在 Android 7.0 及更高版本上将这些通知自动分组。
常用的通知示例
下面展示下:普通通知、重要通知、进度条通知、大文本通知、大图片通知这几种通知的使用示例:
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
// 这里先定义一些Channel的name、description和ID,每种通知使用一个ID
private const val CHANNEL_NAME = "channel"
private const val CHANNEL_DESCRIPTION = "desc"
private const val NORMAL_CHANNEL_ID = "1"
private const val IMPORTANT_CHANNEL_ID = "2"
private const val PROGRESS_CHANNEL_ID = "3"
private const val TEXT_CHANNEL_ID = "4"
private const val IMAGE_CHANNEL_ID = "5"
private const val CUSTOMER_CHANNEL_ID = "6"
private const val REQUEST_CODE = 1000
}
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(activity_main)
val notificationManager = getSystemService(NotificationManager::class.java)
// 按钮的点击事件,触发通知事件
}
上面代码是定义了一些Channel的name、description和ID,每种通知使用一个ID,并在onCreate方法中创建了notificationManager对象,以方便在下面使用。下面就是添加各个按钮的点击事件,以触发通知事件:
// 普通通知
btn_normal.setOnClickListener {
Log.d(TAG, "onCreate: normal-")
// 安卓8.0及以上需要创建渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
NORMAL_CHANNEL_ID,
"$CHANNEL_NAME-normal",
// 优先级为“默认”
NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = "$CHANNEL_DESCRIPTION-normal"
// 设置是否在桌面显示角标
channel.setShowBadge(false)
notificationManager.createNotificationChannel(channel)
}
val intent = Intent(this, SecondActivity::class.java)
// pendingIntent必须声明可变性,最后一个参数必须为PendingIntent.FLAG_MUTABLE 或 PendingIntent.FLAG_IMMUTABLE
val pendingIntent =
PendingIntent.getActivity(this, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE)
// 构建Notification配置
val builder = NotificationCompat.Builder(this@MainActivity, NORMAL_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Normal")
.setContentText("Normal Message")
// 原本是通过getResources()方法获取Resources对象的,但在kotlin中可以直接使用resources
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_big_iamge))
// 设置优先级,如果在Channel中设置了,这里可以不设置,默认使用NotificationChannel中的配置
//.setPriority(NotificationManager.IMPORTANCE_DEFAULT)
// 配置 PendingIntent,即点击之后的意图(可以启动Activity等)
.setContentIntent(pendingIntent)
// 是否自动消失 触发消失的事件有以下来源:
// 点击 / 调用notificationManager.cancel()、cancelAll()方法/ setTimeoutAfter()方法计时
.setAutoCancel(true)
// 发起通知
notificationManager.notify(1, builder.build())
}
说明:在MainActivity中点击按钮时,通知栏会出现通知信息,点击通知信息时,会跳转到SecondActivity,同时通知栏的信息被取消。
效果如下:
// 重要通知
btn_important.setOnClickListener {
Log.d(TAG, "onCreate: important-")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
IMPORTANT_CHANNEL_ID,
"$CHANNEL_NAME-important",
// 设置优先级为“重要”等级
NotificationManager.IMPORTANCE_HIGH
)
channel.description = "$CHANNEL_DESCRIPTION-important"
channel.setShowBadge(true)
notificationManager.createNotificationChannel(channel)
}
val intent = Intent(this, SecondActivity::class.java)
val pendingIntent =
PendingIntent.getActivity(this, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE)
// 构建Notification配置
val builder = NotificationCompat.Builder(this@MainActivity, IMPORTANT_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Important")
.setContentText("Important Message")
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_big_iamge))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
// 设置桌面角标的未读消息数量
.setNumber(99)
// 在通知上添加一个自定义操作,可以对这个titleText添加一个PendingIntent,这里效果跟点击通知栏一样
.addAction(R.mipmap.ic_big_iamge, "查看~", pendingIntent)
// 设置通知类别,"勿扰模式"时系统会决定要不要显示你的通知
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
// 屏幕可见性。锁屏时:显示icon和标题、内容隐藏、解锁查看全部
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
notificationManager.notify(2, builder.build())
}
这里的示例与普通的通知类似,但是优先级设置为NotificationManager.IMPORTANCE_HIGH了,所以除了在通知栏显示之外,还会有一个悬浮窗在顶部弹出来,用来通知用户有新的消息。
注意:这里需要允许应用的悬浮窗权限,否则无法弹出来。
效果如下:
// 进度条通知
btn_progress.setOnClickListener {
Log.d(TAG, "onCreate: progress-")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
PROGRESS_CHANNEL_ID,
"$CHANNEL_NAME-progress",
NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = "$CHANNEL_DESCRIPTION-progress"
// 这里设置了false好像也没作用,progress流程走完之后,桌面app会出现角标
channel.setShowBadge(false)
notificationManager.createNotificationChannel(channel)
}
var currentProgress = 0
val maxProgress = 100
// 构建Notification配置
var builder = NotificationCompat.Builder(this@MainActivity, PROGRESS_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Progress")
.setContentText("下载中:$currentProgress%")
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_big_iamge))
// 设置一个进度条,三个参数分别是:进度条最大值、进度条当前值、
// 第三个参数indeterminate接收一个布尔值,false表示确定的进度,比如100;true表示不确定的进度,会一直显示进度动画,直到更新状态下载完成,或删除通知
.setProgress(maxProgress, currentProgress, false)
// 发起通知
notificationManager.notify(3, builder.build())
// 开启一个线程,模拟下载流程
Thread {
while (currentProgress < 100) {
Thread.sleep(300)
currentProgress += 1
// 1.更新进度
builder.setContentText("下载中:$currentProgress%")
.setProgress(maxProgress, currentProgress, false)
notificationManager.notify(3, builder.build())
}
// 2.下载完成,在这里可以加一个PendingIntent,下载完成后点击可以进入文件所在的位置
builder.setContentText("下载完成!").setProgress(0, 0, false)
notificationManager.notify(3, builder.build())
}.start()
}
上述的代码,设定了一个带有进度条的通知,开启了一个线程模拟下载的流程,进度+1时更新通知栏,在下载完成后也会更新。
注意:如果有多个进度通知,这个时候就需要通过NotificationId进行匹配,来更新到指定的通知,
效果如下:
// 大文本通知
btn_text.setOnClickListener {
Log.d(TAG, "onCreate: text-")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
TEXT_CHANNEL_ID,
"$CHANNEL_NAME-text",
NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = "$CHANNEL_DESCRIPTION-text"
channel.setShowBadge(false)
notificationManager.createNotificationChannel(channel)
}
val bigText =
"A message for TextNotification Test \n" + "Second message for TextNotification Test \n "
// 构建Notification配置
val builder = NotificationCompat.Builder(this@MainActivity, TEXT_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Text")
.setStyle(NotificationCompat.BigTextStyle().bigText(bigText))
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_big_iamge))
.setAutoCancel(true)
// 发起通知
notificationManager.notify(4, builder.build())
}
效果如下:
// 大图标
btn_image.setOnClickListener {
Log.d(TAG, "onCreate: image-")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
IMAGE_CHANNEL_ID,
"$CHANNEL_NAME-image",
NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = "$CHANNEL_DESCRIPTION-image"
channel.setShowBadge(false)
notificationManager.createNotificationChannel(channel)
}
val bigPic = BitmapFactory.decodeResource(resources, R.mipmap.ic_big_iamge)
// 构建Notification配置
val builder = NotificationCompat.Builder(this@MainActivity, IMAGE_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Image")
.setContentText("Image Message")
.setStyle(NotificationCompat.BigPictureStyle().bigPicture(bigPic))
.setAutoCancel(true)
// 发起通知
notificationManager.notify(5, builder.build())
}
效果如下:
自定义通知样式
使用自定义通知布局时,请特别注意确保您的自定义布局适用于不同的设备屏幕方向和分辨率。虽然对于所有界面布局,此建议都适用,但它对通知布局而言尤为重要,因为抽屉式通知栏中的空间非常有限。自定义通知布局的可用高度取决于通知视图。
自定义样式的关键是构建一个RemoteViews对象,这个对象会加载你自定义的布局文件,然后在NotificationCompat.Builder中调用**.setCustomContentView**(views)方法来构建通知,这里可以使用RemoteView.setOnClickPendingIntent()等方法,为自定义布局中的控件添加监听事件,PendingIntent可以是启动Activity、发送广播、启动Service等。具体用法可以查看下面的示例:
// 自定义通知
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
private const val CHANNEL_NAME = "channel"
private const val CHANNEL_DESCRIPTION = "desc"
private const val CUSTOMER_CHANNEL_ID = "6"
private const val REQUEST_CODE = 1000
private const val ACTION_PRE = "notification_pre_action"
private const val ACTION_NEXT = "notification_next_action"
}
private val musicReceiver: MusicReceiver = MusicReceiver();
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(activity_main)
val notificationManager = getSystemService(NotificationManager::class.java)
// 监听广播,这里是用于自定义的通知
val intentFilter = IntentFilter()
intentFilter.addAction(ACTION_PRE)
intentFilter.addAction(ACTION_NEXT)
registerReceiver(musicReceiver, intentFilter)
btn_customer.setOnClickListener {
Log.d(TAG, "onCreate: customer-")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CUSTOMER_CHANNEL_ID,
"$CHANNEL_NAME-customer",
NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = "$CHANNEL_DESCRIPTION-customer"
channel.setShowBadge(false)
notificationManager.createNotificationChannel(channel)
}
// 添加自定义的NotificationView
val views = RemoteViews(this.opPackageName, R.layout.layout_customer_notification)
// 为PreButton添加点击事件和绑定PendingIntent,这里的PendingIntent是发送广播事件
val intentPre = Intent(ACTION_PRE)
val prePendingIntent = PendingIntent.getBroadcast(
this@MainActivity, REQUEST_CODE,
intentPre, PendingIntent.FLAG_IMMUTABLE
)
// 在点击btn_pre控件时,会发送一个ACTION_PRE的广播
views.setOnClickPendingIntent(R.id.btn_pre, prePendingIntent)
// 为NextButton添加点击事件和绑定PendingIntent
val intentNext = Intent(ACTION_NEXT)
val nextPendingIntent = PendingIntent.getBroadcast(
this@MainActivity, REQUEST_CODE,
intentNext, PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.btn_next, nextPendingIntent)
// 调用setImageViewResource为btn_image控件设定Image资源,类似的还有设置TextView、设定控件Enable等
views.setImageViewResource(R.id.btn_image, R.mipmap.ic_big_iamge)
// 构建Notification
val builder = NotificationCompat.Builder(this@MainActivity, CUSTOMER_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Customer")
.setAutoCancel(false)
// 默认布局显示,即折叠状态下的布局
.setCustomContentView(views)
// 展开状态下的布局
.setCustomBigContentView(views)
// 发起通知
notificationManager.notify(6, builder.build())
}
}
// 定义一个广播接收器,用来接收通知栏上点击按钮之后触发的广播
open class MusicReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (context != null && intent != null) {
when (intent.action) {
ACTION_PRE -> {
Toast.makeText(context, "Pre onClick", Toast.LENGTH_SHORT).show()
}
ACTION_NEXT -> {
Toast.makeText(context, "Next onClick", Toast.LENGTH_SHORT).show()
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(musicReceiver)
}
}
上面例子自定义了一个Notification视图,通过RemoteViews(this.opPackageName, R.layout.layout_customer_notification)来获取自定义的视图,并通过Builder的setCustomContentView方法绑定到Notification中。RemoteViews并不是一个真正的view,它只是一个view的描述,所以事件处理上还是要借助PendingIntent。
这里为视图里的两个按钮绑定了点击事件,并添加了各自的PendingIntent,在点击的时候,会分别触发ACTION_PRE、ACTION_NEXT这两个广播,在Activity中监听这两个广播,就可以接收到通知栏的点击事件了。
实现效果如下:
(2)这里再展示下Java中实现的自定义通知栏布局、带有监听事件的通知栏:
public class MusicPlayService extends Service {
private RemoteViews remoteViews;
private static NotificationManager notificationManager;
private Notification notification;
private static final int NOTIFICATION_ID = 1;
public static final String PLAY = "play";
@Override
public void onCreate() {
super.onCreate();
// 创建通知栏通道
createNotificationChannel();
// 注册广播接收器
musicReceiver = new MusicReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PLAY);
registerReceiver(musicReceiver, intentFilter);
// 初始化之后再显示通知栏
showNotify();
}
@Override
public void onDestroy() {
super.onDestroy();
if (musicReceiver != null) {
// 解除动态注册的广播
unregisterReceiver(musicReceiver);
}
notificationManager.cancel(NOTIFICATION_ID);
}
/**
* @createTime 2023/2/8 14:33
* @description 创建通知栏通道
*/
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId = "9527";
CharSequence name = "PlayControl";
String description = "通知栏";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(channelId, name, importance);
channel.setDescription(description);
notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
/**
* @createTime 2023/2/8 14:32
* @description 显示通知栏
*/
@SuppressLint("UnspecifiedImmutableFlag")
private void showNotify() {
// 获取RemoteViews对象
remoteViews = getContentView();
// 设置PendingIntent
Intent it = new Intent(this, MainActivity.class);
it.putExtra("notify", true);
PendingIntent pi = PendingIntent.getActivity(this, 0, it, 0);
// 创建通知栏信息
notification = new NotificationCompat.Builder(this, "9527")
//设置图标
.setSmallIcon(R.mipmap.ic_notify_icon)
.setWhen(System.currentTimeMillis())
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
//设置点击启动的Intent
.setContentIntent(pi)
//设置自定义的layout
.setCustomContentView(remoteViews)
//点击后通知栏取消通知
//.setAutoCancel(true)
.setTicker("正在播放")
.setOngoing(true)
//优先级
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build();
notificationManager.notify(NOTIFICATION_ID, notification);
}
/**
* @createTime 2023/2/8 14:32
* @description 获取自定义的通知栏布局对象
*/
@SuppressLint("UnspecifiedImmutableFlag")
private RemoteViews getContentView() {
RemoteViews mRemoteViews = new RemoteViews(this.getPackageName(), R.layout.layout_notify_view);
// 通知栏控制器播放暂停按钮广播操作
Intent intentPlay = new Intent(PLAY);
PendingIntent playPendingIntent = PendingIntent.getBroadcast(this, 0, intentPlay, 0);
// 为btn_play控件注册事件
mRemoteViews.setOnClickPendingIntent(R.id.btn_play, playPendingIntent);
// 若音乐列表不为空,初始化通知栏的歌曲信息
setNotificationEnable(mRemoteViews, musicInfo.size()>0);
return mRemoteViews;
}
/**
* 设定通知栏的状态
* @author wm
* @createTime 2023/8/31 18:47
* @param enable: true 可用; false不可用
*/
private void setNotificationEnable(RemoteViews rv, boolean enable){
if (enable){
// 动态设置通知栏的TextView
rv.setTextViewText(R.id.tv_song_title, musicInfo.get(mPosition).getTitle());
if (isPlaying()) {
// 动态设置通知栏的ImageView
rv.setImageViewResource(R.id.btn_play, R.drawable.set_notify_pause_style);
} else {
rv.setImageViewResource(R.id.btn_play, R.drawable.set_notify_play_style);
}
} else {
// 当前播放列表为空的情况
rv.setTextViewText(R.id.tv_song_title,"");
rv.setImageViewResource(R.id.btn_play, R.drawable.set_notify_play_style);
}
// 设置状态栏的控件是否可用
rv.setBoolean(R.id.btn_play,"setEnabled",enable);
}
/**
* @createTime 2023/2/8 16:15
* @description 广播接收器 , 接收来自通知栏的广播
*/
public class MusicReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case PLAY:
play(musicInfo.get(mPosition), firstPlay, mPosition);
break;
default:
break;
}
}
}
}
这里就不放效果图了,效果与上面kotlin的示例类似,注释也写得比较清楚了,可以参考学习。
标签:val,Notification,安卓,FLAG,详解,CHANNEL,通知,PendingIntent From: https://blog.csdn.net/qq_44458837/article/details/140431275