6、通知Action与交互:添加Action按钮、内联回复(RemoteInput)、进度条通知、媒体风格通知。
通知如果只是展示信息,那它就是个「哑巴」。真正让通知活起来的,是交互。用户不用打开App,直接在通知栏就能完成操作——这才是Android通知设计的精髓。
我个人习惯把通知交互分成四个层次:点击跳转(最基础)、Action按钮(快速操作)、内联回复(输入交互)、进度/媒体(实时反馈)。今天咱们就把后面三个讲透。
核心知识点一览
- Action按钮:最多3个,用PendingIntent绑定操作
- RemoteInput:在通知栏直接输入文字回复
- 进度条通知:确定进度 vs 不确定进度
- 媒体风格通知:MediaStyle + 播放控制
6.1 Action按钮:让通知不止于「看」
Action按钮说白了就是在通知底部加几个可点击的按钮。比如微信消息的「回复」和「标记已读」,就是典型的Action。
实现起来其实不复杂,核心就三步:
- 创建PendingIntent(指向你要执行的操作)
- 调用
addAction()方法 - 在BroadcastReceiver或Service里处理回调
// 创建Action按钮
Intent acceptIntent = new Intent(this, ActionReceiver.class);
acceptIntent.setAction("ACTION_ACCEPT");
PendingIntent acceptPendingIntent = PendingIntent.getBroadcast(
this, 0, acceptIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Action action = new NotificationCompat.Action.Builder(
R.drawable.ic_check, "接受", acceptPendingIntent
).build();
// 构建通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("好友请求")
.setContentText("张三请求添加你为好友")
.addAction(action) // 添加Action
.setAutoCancel(true);
NotificationManagerCompat.from(this).notify(1001, builder.build());
💡 我的经验
Action按钮最多只能加3个,超过会被系统忽略。我有个项目曾经加了4个,结果最后一个死活不显示,查了半天文档才发现这个限制。另外,Android 12开始,PendingIntent必须指定FLAG_IMMUTABLE或FLAG_MUTABLE,否则会抛异常。
6.2 内联回复:在通知栏直接打字
你想想看,收到一条消息,下拉通知栏直接打字回复,不用打开App——这个体验多流畅。这就是RemoteInput的功劳。
RemoteInput是Android Wear(手表)时代引入的,后来在手机上也普及了。它的工作流程是这样的:
- 在Action里嵌入一个RemoteInput对象
- 用户点击回复按钮后,弹出输入框
- 输入内容通过BroadcastReceiver回传给App
// 1. 定义RemoteInput的Key
private static final String KEY_REPLY = "key_reply";
// 2. 创建RemoteInput
RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
.setLabel("输入回复内容")
.build();
// 3. 创建回复Action
Intent replyIntent = new Intent(this, ReplyReceiver.class);
replyIntent.setAction("ACTION_REPLY");
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(
this, 1, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
R.drawable.ic_reply, "回复", replyPendingIntent
)
.addRemoteInput(remoteInput) // 关键:绑定RemoteInput
.build();
// 4. 构建通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_message)
.setContentTitle("新消息")
.setContentText("你好,在吗?")
.addAction(replyAction);
NotificationManagerCompat.from(this).notify(1002, builder.build());
然后在BroadcastReceiver里取出用户输入:
public class ReplyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 从RemoteInput中提取回复内容
Bundle results = RemoteInput.getResultsFromIntent(intent);
if (results != null) {
CharSequence replyText = results.getCharSequence(KEY_REPLY);
// 处理回复逻辑...
Toast.makeText(context, "回复: " + replyText, Toast.LENGTH_SHORT).show();
}
}
}
⚠️ 避坑指南
我曾经踩过一个坑:RemoteInput的Key必须和Builder时传入的Key完全一致,大小写都不能错。另外,如果用户没有输入直接发送,getCharSequence返回的是空字符串,不是null,记得判空处理。
6.3 进度条通知:给用户一个「进度感」
下载文件、上传图片、处理数据……这些耗时操作如果没有任何反馈,用户会以为App卡死了。进度条通知就是用来解决这个问题的。
Android支持两种进度条:
| 类型 | 方法 | 适用场景 |
|---|---|---|
| 确定进度 | setProgress(max, progress, false) |
下载文件、批量处理(知道总量) |
| 不确定进度 | setProgress(0, 0, true) |
连接服务器、解压文件(不知道总量) |
// 确定进度示例:文件下载
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_download)
.setContentTitle("正在下载")
.setContentText("下载中...")
.setOngoing(true); // 不可滑动清除
// 模拟下载进度更新
new Thread(() -> {
for (int i = 0; i <= 100; i += 10) {
builder.setProgress(100, i, false);
builder.setContentText("已下载 " + i + "%");
NotificationManagerCompat.from(this).notify(1003, builder.build());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 下载完成
builder.setContentText("下载完成")
.setProgress(0, 0, false) // 移除进度条
.setOngoing(false);
NotificationManagerCompat.from(this).notify(1003, builder.build());
}).start();
💡 我的习惯
不确定进度时,我一般会配合一个「正在加载」的动画图标,让用户知道程序还在跑。另外,进度更新不要太频繁,每200ms更新一次就够,否则会浪费CPU和电量。
6.4 媒体风格通知:播放控制一步到位
音乐播放器、播客App、视频App……这些场景下,用户希望在下拉通知栏就能控制播放/暂停、切歌。MediaStyle就是为此设计的。
MediaStyle和普通通知的区别在于:
- 使用
MediaStyle装饰器,让通知样式更紧凑 - 可以显示专辑封面(大图标)
- 支持最多5个Action按钮(普通通知只能3个)
- 配合
MediaSession实现更完善的媒体控制
// 创建媒体通知
NotificationCompat.MediaStyle mediaStyle = new NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.getSessionToken()) // 绑定MediaSession
.setShowActionsInCompactView(0, 1, 2); // 在紧凑模式下显示哪些按钮
// 创建播放控制Action
Intent playIntent = new Intent(this, MediaControlReceiver.class);
playIntent.setAction("ACTION_PLAY_PAUSE");
PendingIntent playPendingIntent = PendingIntent.getBroadcast(
this, 0, playIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Action playAction = new NotificationCompat.Action.Builder(
R.drawable.ic_pause, "暂停", playPendingIntent
).build();
// 上一首、下一首类似...
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_music_note)
.setContentTitle("歌曲名称")
.setContentText("歌手名")
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.album_art))
.setStyle(mediaStyle) // 应用MediaStyle
.addAction(prevAction)
.addAction(playAction)
.addAction(nextAction)
.setOngoing(true); // 播放中不可清除
NotificationManagerCompat.from(this).notify(1004, builder.build());
关键点
媒体通知必须配合前台Service使用,否则App被杀死后通知也会消失。Android 13开始,媒体通知有专门的「媒体播放」通道,优先级更高,用户无法关闭。
嗯,这里要注意一点:setShowActionsInCompactView()这个方法决定了在通知栏只显示一行时,哪些按钮可见。我一般把播放/暂停放在索引0,这样用户一眼就能看到核心控制。
其实媒体通知还有一个隐藏技巧:如果你用MediaSession,系统会自动帮你同步锁屏界面的控制。用户锁屏后也能切歌,体验非常棒。我在做音乐App时,就靠这个功能拿到了产品经理的表扬(笑)。
好了,通知交互这块就讲到这里。Action按钮让通知可操作,RemoteInput让通知可输入,进度条让通知可感知,MediaStyle让通知可控制——这四个组合起来,基本覆盖了90%的交互场景。实际项目中,你可能会把它们混着用,比如「下载进度条+取消按钮」,或者「消息通知+回复Action」。灵活组合才是王道。