You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为RecyclerView每项设置指定日期时间的闹钟提醒

为RecyclerView中每个待办项设置独立闹钟的正确实现方式

现有代码的核心问题

  1. 重复设置闹钟:适配器onBindViewHolder中每次绑定都会调用onAlarmAlert,导致列表刷新时重复设置同一闹钟。
  2. 闹钟互相覆盖:PendingIntent的requestCode固定为0,多个待办项的闹钟会互相替换,无法独立触发。
  3. 时间处理错误:
    • Calendar的月份是0索引(0=1月,11=12月),现有代码直接用分割得到的1-12数值,会导致月份偏移。
    • 未处理12小时制的AM/PM,直接用HOUR_OF_DAY会导致时间错误(比如下午3点会被当成凌晨3点)。
  4. 触发时机不合理:当前仅通过菜单按钮触发设置,应该在添加/更新待办项时自动设置闹钟,删除时自动取消。
  5. 接收器功能不完整: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" />

优化后的核心流程

  1. 用户添加/更新待办项时,自动触发闹钟设置,用待办项ID作为唯一标识,避免闹钟冲突。
  2. 删除待办项时,自动取消对应闹钟,防止无效触发。
  3. 闹钟触发时,接收器播放铃声、震动,并显示包含待办信息的通知。

内容的提问来源于stack exchange,提问作者Sita Soni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 21:56:59