如何为RecyclerView每项设置指定日期时间的闹钟提醒
为RecyclerView中每个待办项设置独立闹钟的正确实现方式
现有代码的核心问题
- 重复设置闹钟:适配器
onBindViewHolder中每次绑定都会调用onAlarmAlert,导致列表刷新时重复设置同一闹钟。 - 闹钟互相覆盖:
PendingIntent的requestCode固定为0,多个待办项的闹钟会互相替换,无法独立触发。 - 时间处理错误:
- Calendar的月份是0索引(0=1月,11=12月),现有代码直接用分割得到的1-12数值,会导致月份偏移。
- 未处理12小时制的AM/PM,直接用
HOUR_OF_DAY会导致时间错误(比如下午3点会被当成凌晨3点)。
- 触发时机不合理:当前仅通过菜单按钮触发设置,应该在添加/更新待办项时自动设置闹钟,删除时自动取消。
- 接收器功能不完整:
showNotification方法未实现,且未创建Android 8.0+要求的通知渠道。
分步修正方案
1. 调整闹钟触发时机,避免重复设置
移除适配器onBindViewHolder中的todoInterface?.onAlarmAlert(todoListPos),改为在添加/更新待办项成功后自动设置闹钟,同时在删除待办项时取消对应闹钟。
在AddUpdateActivity的btnDone点击事件中补充逻辑:
// AddUpdateActivity.kt btnDone.setOnClickListener { // ... 原有获取输入逻辑 ... if (type.equals("update")) { if (title.isNotEmpty() && description.isNotEmpty()) { val updatedTodo = ToDoPojo(title, description, time, date, selectedTodoNotify) updatedTodo.id = noteID todoViewModel.updateToDoExistingItem(updatedTodo) // 更新后重新设置闹钟(先取消旧的,再设置新的) setTodoAlarm(updatedTodo) Toast.makeText(this, "$title updated!", Toast.LENGTH_LONG).show() } } else { if (title.isNotEmpty() && description.isNotEmpty()) { val newTodo = ToDoPojo(title, description, time, date, selectedTodoNotify) todoViewModel.insertToDoItem(newTodo) // 新增待办项后设置闹钟 setTodoAlarm(newTodo) Toast.makeText(this, "$title added!", Toast.LENGTH_LONG).show() } } // ... 跳转逻辑 ... } // 新增设置闹钟的方法 private fun setTodoAlarm(todoItem: ToDoPojo) { val calendar = Calendar.getInstance() val itemDate = todoItem.date.split("-") val day = itemDate[0].toInt() val month = itemDate[1].toInt() - 1 // 转换为0索引月份 val year = itemDate[2].toInt() val timeParts = todoItem.time.split(":", " ") var hour = timeParts[0].toInt() val min = timeParts[1].toInt() val amPm = timeParts[2].uppercase() // 12小时制转24小时制 if (amPm == "PM" && hour != 12) hour += 12 if (amPm == "AM" && hour == 12) hour = 0 calendar.apply { set(Calendar.YEAR, year) set(Calendar.MONTH, month) set(Calendar.DAY_OF_MONTH, day) set(Calendar.HOUR_OF_DAY, hour) set(Calendar.MINUTE, min) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) } // 处理已过期的时间,默认设为第二天 val alarmTime = if (calendar.timeInMillis < System.currentTimeMillis()) { calendar.add(Calendar.DAY_OF_MONTH, 1) calendar.timeInMillis } else { calendar.timeInMillis } val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(this, TodoReceiverClass::class.java).apply { putExtra("todo_title", todoItem.title) putExtra("todo_desc", todoItem.description) putExtra("todo_id", todoItem.id) } val pendingIntent = PendingIntent.getBroadcast( this, todoItem.id, // 用待办项唯一ID作为requestCode,避免冲突 intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE // 适配Android 12+ ) alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pendingIntent) }
2. 添加闹钟取消逻辑
删除待办项时取消对应闹钟,在Fragment的onDeleteClick中补充:
// TodoFragment.kt override fun onDeleteClick(todoItem: ToDoPojo) { val alarmManager = requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(requireContext(), TodoReceiverClass::class.java) val pendingIntent = PendingIntent.getBroadcast( requireContext(), todoItem.id, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) alarmManager.cancel(pendingIntent) todoViewModel.deleteToDoItem(todoItem) }
3. 补全接收器的通知功能
完善TodoReceiverClass,添加通知渠道和完整通知逻辑:
class TodoReceiverClass : BroadcastReceiver() { private val CHANNEL_ID = "todo_alarm_channel" private val CHANNEL_NAME = "待办事项闹钟" override fun onReceive(context: Context, intent: Intent) { val title = intent.getStringExtra("todo_title") ?: "待办提醒" val content = intent.getStringExtra("todo_desc") ?: "该处理你的待办事项了!" val todoId = intent.getIntExtra("todo_id", 1000) vibrationAndRingtone(context) showNotification(context, title, content, todoId) } private fun vibrationAndRingtone(context: Context) { Toast.makeText(context, "待办闹钟触发!", Toast.LENGTH_LONG).show() val alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM) ?: RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) val ringtone = RingtoneManager.getRingtone(context, alarmUri) ringtone.play() // 震动逻辑(需添加权限) val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { vibrator.vibrate(VibrationEffect.createOneShot(1000, VibrationEffect.DEFAULT_AMPLITUDE)) } else { vibrator.vibrate(1000) } } private fun showNotification(context: Context, title: String, content: String, todoId: Int) { // 创建Android 8.0+所需的通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH ).apply { description = "待办事项闹钟通知" enableVibration(true) } val nm = context.getSystemService(NotificationManager::class.java) nm.createNotificationChannel(channel) } val notificationIntent = Intent(context, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } val pendingIntent = PendingIntent.getActivity( context, todoId, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(title) .setContentText(content) .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent(pendingIntent) .setAutoCancel(true) .build() NotificationManagerCompat.from(context).notify(todoId, notification) } }
4. 权限补充
在清单文件中添加震动权限:
<uses-permission android:name="android.permission.VIBRATE" />
优化后的核心流程
- 用户添加/更新待办项时,自动触发闹钟设置,用待办项ID作为唯一标识,避免闹钟冲突。
- 删除待办项时,自动取消对应闹钟,防止无效触发。
- 闹钟触发时,接收器播放铃声、震动,并显示包含待办信息的通知。
内容的提问来源于stack exchange,提问作者Sita Soni
相关产品推荐
相关产品推荐

