Android实战项目——任务达人(todolist) (二)

添加了时间显示,以及提醒功能。


前言

       接着上篇文章的进度,继续完善项目,添加了时间显示,以及提醒功能。

       这是适合初学者学习的安卓APP项目,对你有帮助的话,麻烦点赞、收藏、加评论,谢谢大家。


一、添加创建时间和结束时间

       众所周知,对应每个要完成的任务,我们特别关注其截止时间,因此我们需要让每个任务显示时间,来帮助我们更好把握任务的完成。

       实现效果如图:

      (1.1) 添加组件

          我们先在item_task.xml上加上显示创建、结束时间以及设置结束时间按钮的组件

item_task.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp">

    <TextView
        android:id="@+id/tvTask"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvStartTime"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:textColor="@color/black"
            android:textSize="12sp" />

        <TextView
            android:id="@+id/tvEndTime"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:textColor="@color/black"
            android:textSize="12sp" />

        <Button
            android:id="@+id/btnSetEndTime"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="设置结束时间"
            android:textColor="@color/white"
            android:textSize="12sp" />
    </LinearLayout>


</LinearLayout>

       (1.2)在添加任务模型类里添加元素与方法

        然后,我们需要在任务模型类Task.java里,加上创建时间、结束时间这两个变量,以及它们对应的get和set方法,其中创建时间startTime为直接获取系统当前时间,而结束时间endTime需要手动设置。

Task.java:

package com.example.todolist.modle;

import java.util.Date;

public class Task {
    private String content;  //任务内容
    private boolean isCompleted; //是否完成

    private Date endTime;  //结束时间

    private Date startTime; //创建时间

    public Task(String content) {  //构造函数
        this.content = content;
        this.isCompleted = false;
        this.startTime = new Date();
    }

    public void setContent(String content) {  //设置content
        this.content = content;
    }

    public String getContent() { //获取content
        return content;
    }

    public boolean isCompleted() { //获取isCompleted
        return isCompleted;
    }

    public void setCompleted(boolean isCompleted) { //设置isCompleted
        this.isCompleted = isCompleted;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public Date getStartTime() {
        return startTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    public Date getEndTime() {
        return endTime;
    }

}

      (1.3)实现设置结束时间按钮

       为了实现手动设置结束时间,我们的思路是,当用户点击了“设置结束时间”后,应该跳出一个时间选择器,在用户选择完时间后,根据选择的时间显示结束时间。

      所以,我们需要修改适配器TaskAdapter.java,第一步,我们先在单个任务项的视图组件TaskViewHolder方法中将新增元素添加进去,并将其与item_task中组件绑定

    public static class TaskViewHolder extends RecyclerView.ViewHolder {
        TextView tvTask; //显示任务内容

        TextView tvStartTime; //创建时间

        TextView tvEndTime; //结束时间

        Button btnSetEndTime; //设置结束时间按钮

        /**
         * 构造函数,初始化视图组件
         */
        public TaskViewHolder(@NonNull View itemView) {
            super(itemView);
            tvTask = itemView.findViewById(R.id.tvTask); //获取TextView组件
            tvStartTime = itemView.findViewById(R.id.tvStartTime);
            tvEndTime = itemView.findViewById(R.id.tvEndTime);
            btnSetEndTime = itemView.findViewById(R.id.btnSetEndTime);
        }
    }

         第二步,我们在onBindViewHolder方法中,将创建、结束时间的数据绑定到视图上,并将点击“设置结束时间”的触发方法写入

//时间的格式 
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
       //显示任务的创建时间
        if (task.getStartTime() != null) {
            //格式化日期并显示
            String satrtString = simpleDateFormat.format(task.getStartTime());
            holder.tvStartTime.setText("创建时间:" + satrtString);
        }
        //显示任务结束时间
        if (task.getEndTime() != null) {
            //格式化日期并显示
            String endString = simpleDateFormat.format(task.getEndTime());
            holder.tvEndTime.setText("结束时间:" + endString);
        } else {
            //如果结束时间未设置,显示默认提示
            holder.tvEndTime.setText("结束时间:未设置");
        }
        //设置结束时间的按钮点击事件
        holder.btnSetEndTime.setOnClickListener(v -> {
            //获取当前时间
            Calendar calendar = Calendar.getInstance();
            //创建日期选择器,用于选择年、月、日
            DatePickerDialog datePickerDialog = new DatePickerDialog(context, (view, year, month, dayOfMonth) -> {
                //创建时间选择器,用于选择小时和分钟
                TimePickerDialog timePickerDialog = new TimePickerDialog(context, (view1, hourOfDay, minute) -> {
                    // 设置选择的时间到 Calendar 对象
                    calendar.set(year, month, dayOfMonth, hourOfDay, minute);
                    //更新任务结束时间
                    task.setEndTime(calendar.getTime());
                    String endString = simpleDateFormat.format(task.getEndTime());
                    holder.tvEndTime.setText("结束时间:" + endString);
                    //刷新数据并保存
                    notifyDataSetChanged();
                    saveTasks();
                },
                        calendar.get(Calendar.HOUR_OF_DAY),
                        calendar.get(Calendar.MINUTE),
                        true
                );
                timePickerDialog.show(); //显示时间选择器
            },
                    calendar.get(Calendar.YEAR),
                    calendar.get(Calendar.MONTH),
                    calendar.get(Calendar.DAY_OF_MONTH)
            );
            datePickerDialog.show(); //显示日期选择器
        });

       整个文件如下

TaskAdapter.java:

package com.example.todolist.adapter;

import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.todolist.R;
import com.example.todolist.modle.Task;
import com.google.gson.Gson;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;


/**
 * RecyclerView适配器,用于管理任务列表的显示和交互
 */

public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder> {
    private List<Task> tasks; //任务列表
    private Context context; //上下文,用于获取资源和操作SharedPreferences

    /**
     * 构造函数,初始化任务列表和上下文
     *
     * @param tasks   任务列表
     * @param context 上下文
     */
    public TaskAdapter(List<Task> tasks, Context context) {
        this.tasks = tasks;
        this.context = context;
    }

    /**
     * 创建并返回一个新的ViewHolder,负责显示单个任务项的视图
     */
    @NonNull
    @Override
    public TaskAdapter.TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        //加载item_task布局
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_task, parent, false);
        return new TaskViewHolder(view);
    }

    /**
     * 将任务数据绑定到对应的ViewHolder
     */
    @Override
    public void onBindViewHolder(@NonNull TaskAdapter.TaskViewHolder holder, int position) {
        Task task = tasks.get(position); //获取指定位置任务
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //时间的格式
        holder.tvTask.setText(task.getContent()); //设置任务内容
        //根据任务是否完成,设置文本删除线
        holder.tvTask.setPaintFlags(task.isCompleted() ? Paint.STRIKE_THRU_TEXT_FLAG : 0);
        //显示任务的创建时间
        if (task.getStartTime() != null) {
            //格式化日期并显示
            String satrtString = simpleDateFormat.format(task.getStartTime());
            holder.tvStartTime.setText("创建时间:" + satrtString);
        }
        //显示任务结束时间
        if (task.getEndTime() != null) {
            //格式化日期并显示
            String endString = simpleDateFormat.format(task.getEndTime());
            holder.tvEndTime.setText("结束时间:" + endString);
        } else {
            //如果结束时间未设置,显示默认提示
            holder.tvEndTime.setText("结束时间:未设置");
        }
        //设置结束时间的按钮点击事件
        holder.btnSetEndTime.setOnClickListener(v -> {
            //获取当前时间
            Calendar calendar = Calendar.getInstance();
            //创建日期选择器,用于选择年、月、日
            DatePickerDialog datePickerDialog = new DatePickerDialog(context, (view, year, month, dayOfMonth) -> {
                //创建时间选择器,用于选择小时和分钟
                TimePickerDialog timePickerDialog = new TimePickerDialog(context, (view1, hourOfDay, minute) -> {
                    // 设置选择的时间到 Calendar 对象
                    calendar.set(year, month, dayOfMonth, hourOfDay, minute);
                    //更新任务结束时间
                    task.setEndTime(calendar.getTime());
                    String endString = simpleDateFormat.format(task.getEndTime());
                    holder.tvEndTime.setText("结束时间:" + endString);
                    //刷新数据并保存
                    notifyDataSetChanged();
                    saveTasks();
                },
                        calendar.get(Calendar.HOUR_OF_DAY),
                        calendar.get(Calendar.MINUTE),
                        true
                );
                timePickerDialog.show(); //显示时间选择器
            },
                    calendar.get(Calendar.YEAR),
                    calendar.get(Calendar.MONTH),
                    calendar.get(Calendar.DAY_OF_MONTH)
            );
            datePickerDialog.show(); //显示日期选择器
        });

        //设置点击监听器,单击任务时,切换其完成状态
        holder.itemView.setOnClickListener(v -> {
            task.setCompleted(!task.isCompleted());
            holder.tvTask.setPaintFlags(task.isCompleted() ? Paint.STRIKE_THRU_TEXT_FLAG : 0);
        });
        //设置长按监听器,长按任务时,显示删除确认对话框
        holder.itemView.setOnLongClickListener(v -> {
            AlertDialog alertDialog;
            AlertDialog.Builder builder = new AlertDialog.Builder(context)
                    .setTitle("是否删除")  //设置对话框标题
                    .setMessage("是否删除该任务") //设置对话框内容
                    //“确定”按钮
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            tasks.remove(position); //从任务列表移除任务
                            notifyDataSetChanged(); //通知数据变化,更新RecyclerView
                            saveTasks(); //调用保存函数
                            dialog.dismiss(); //关闭对话框
                        }
                    })
                    //“取消”按钮,setNegativeButton点击后自动关闭对话框
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {

                        }
                    });
            alertDialog = builder.create();
            alertDialog.show();
            return true;
        });
    }

    /**
     * 单个任务项的视图组件ViewHolder
     */
    public static class TaskViewHolder extends RecyclerView.ViewHolder {
        TextView tvTask; //显示任务内容

        TextView tvStartTime; //创建时间

        TextView tvEndTime; //结束时间

        Button btnSetEndTime; //设置结束时间按钮

        /**
         * 构造函数,初始化视图组件
         */
        public TaskViewHolder(@NonNull View itemView) {
            super(itemView);
            tvTask = itemView.findViewById(R.id.tvTask); //获取TextView组件
            tvStartTime = itemView.findViewById(R.id.tvStartTime);
            tvEndTime = itemView.findViewById(R.id.tvEndTime);
            btnSetEndTime = itemView.findViewById(R.id.btnSetEndTime);
        }
    }

    /**
     * 保存函数,保存任务列表到sharedPreferences
     */
    public void saveTasks() {
        SharedPreferences sharedPreferences = context.getSharedPreferences("todo_list", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit(); //获取Editor对象,修改sharedPreferences
        Gson gson = new Gson();
        String json = gson.toJson(tasks); //将任务列表转换成JSON字符串
        editor.putString("tasks", json); //将JSON字符串保存到sharedPreferences
        editor.apply(); //提交修改
    }

    /**
     * 加载函数,从SharedPreferences中加载任务列表
     */
    public void loadTasks(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences("todo_list", Context.MODE_PRIVATE);
        String json = sharedPreferences.getString("tasks", null); //获取保存的JSON字符串
        if (json != null) {
            Gson gson = new Gson();
            Task[] tasksArray = gson.fromJson(json, Task[].class); //将JSON字符串转换为任务数组
            tasks.clear();  //清空当前任务列表
            tasks.addAll(Arrays.asList(tasksArray)); //将转换为后的任务数组添加到任务列表
            notifyDataSetChanged();  //通知数据变化,更新RecyclerView
        }
    }

    /**
     * 获取RecyclerView中显示的项数,即任务列表的大小
     */
    @Override
    public int getItemCount() {
        return tasks.size();
    }
}

         自此,我们就能实现文章开头的时间显示效果。

二、提醒功能

(2.1)创建通知渠道

       在Android 8.0以上的版本,需要创建通知渠道,才能发送通知,我们在主函数MainActivity,中编写创建通知渠道函数,并在生命周期onCreate()中调用。

/**
     * 创建通知渠道
     */
    private void createNotificationChannel() {
        // 检查设备的 Android 版本是否支持通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // 定义通知渠道的名称
            String name = "任务结束提醒";
            // 定义通知渠道的描述
            String description = "任务结束";
            // 定义通知渠道的重要性(默认级别)
            int importance = NotificationManager.IMPORTANCE_DEFAULT;

            // 创建通知渠道对象
            NotificationChannel channel = new NotificationChannel(
                    "task_reminder_channel", // 渠道 ID
                    name, // 渠道名称
                    importance // 渠道重要性
            );

            // 设置通知渠道的描述
            channel.setDescription(description);

            // 获取系统的 NotificationManager 服务
            NotificationManager notificationManager = getSystemService(NotificationManager.class);

            // 创建通知渠道
            // 如果渠道 ID 已存在,此方法不会重复创建
            notificationManager.createNotificationChannel(channel);
        }
    }

(2.2)新建广播接收器ReminderReceiver

     创建ReminderReceiver,用于当提醒时间到达时,调用此方法,用于发送通知提醒用户

ReminderReceiver.java

package com.example.todolist.receiver;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.widget.Toast;

import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import com.example.todolist.R;

public class ReminderReceiver extends BroadcastReceiver {

    /**
     * 广播接收器的回调方法
     * 当提醒时间到达时,此方法会被调用,用于发送通知提醒用户
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        // 从 Intent 中提取任务内容
        String taskContent = intent.getStringExtra("taskContent");
        // 从 Intent 中提取任务 ID
        int taskId = intent.getIntExtra("taskId", 0);

        // 创建通知构建器
        // 参数:
        // 1. 应用上下文
        // 2. 通知渠道 ID(必须与创建通知渠道时的 ID 一致)
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "task_reminder_channel")
                .setSmallIcon(R.drawable.ic_launcher_foreground) // 设置通知图标
                .setContentTitle("任务提醒") // 设置通知标题
                .setContentText(taskContent) // 设置通知内容
                .setPriority(NotificationCompat.PRIORITY_DEFAULT); // 设置通知优先级

        // 获取系统的 NotificationManagerCompat 服务
        NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);

        // 检查是否拥有发送通知的权限
        // Android 13 及以上版本需要显式请求 POST_NOTIFICATIONS 权限
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
            // 如果没有权限,直接返回,不发送通知
            return;
        }

        // 发送通知
        // 参数:
        // 1. 通知 ID(用于区分不同的通知)
        // 2. 构建好的通知对象
        notificationManagerCompat.notify(taskId, builder.build());
    }
}

(3.3)在TaskAdapter,编写设置提醒、取消提醒方法,并在合适的地方调用

TaskAdapter.java

package com.example.todolist.adapter;

import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.PendingIntent;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Build;
import android.widget.Toast;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.todolist.R;
import com.example.todolist.modle.Task;
import com.example.todolist.receiver.ReminderReceiver;
import com.google.gson.Gson;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

/**
 * RecyclerView适配器,用于管理任务列表的显示和交互
 */

public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder> {
    private List<Task> tasks; // 任务列表
    private Context context; // 上下文,用于获取资源和操作SharedPreferences

    /**
     * 构造函数,初始化任务列表和上下文
     *
     * @param tasks   任务列表
     * @param context 上下文
     */
    public TaskAdapter(List<Task> tasks, Context context) {
        this.tasks = tasks;
        this.context = context;
    }

    /**
     * 创建并返回一个新的ViewHolder,负责显示单个任务项的视图
     */
    @NonNull
    @Override
    public TaskAdapter.TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载item_task布局
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_task, parent, false);
        return new TaskViewHolder(view);
    }

    /**
     * 将任务数据绑定到对应的ViewHolder
     */
    @Override
    public void onBindViewHolder(@NonNull TaskAdapter.TaskViewHolder holder, int position) {
        Task task = tasks.get(position); // 获取指定位置任务
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 时间的格式
        holder.tvTask.setText(task.getContent()); // 设置任务内容
        // 根据任务是否完成,设置文本删除线
        holder.tvTask.setPaintFlags(task.isCompleted() ? Paint.STRIKE_THRU_TEXT_FLAG : 0);
        // 显示任务的创建时间
        if (task.getStartTime() != null) {
            // 格式化日期并显示
            String satrtString = simpleDateFormat.format(task.getStartTime());
            holder.tvStartTime.setText("创建时间:" + satrtString);
        }
        // 显示任务结束时间
        if (task.getEndTime() != null) {
            // 格式化日期并显示
            String endString = simpleDateFormat.format(task.getEndTime());
            holder.tvEndTime.setText("结束时间:" + endString);
        } else {
            // 如果结束时间未设置,显示默认提示
            holder.tvEndTime.setText("结束时间:未设置");
        }
        // 设置结束时间的按钮点击事件
        holder.btnSetEndTime.setOnClickListener(v -> {
            // 获取当前时间
            Calendar calendar = Calendar.getInstance();
            // 创建日期选择器,用于选择年、月、日
            DatePickerDialog datePickerDialog = new DatePickerDialog(context, (view, year, month, dayOfMonth) -> {
                // 创建时间选择器,用于选择小时和分钟
                TimePickerDialog timePickerDialog = new TimePickerDialog(context, (view1, hourOfDay, minute) -> {
                    // 设置选择的时间到 Calendar 对象
                    calendar.set(year, month, dayOfMonth, hourOfDay, minute);
                    // 检查时间是否早于当前时间
                    if (calendar.getTimeInMillis() < System.currentTimeMillis()) {
                        Toast.makeText(context, "设置的时间早于当前时间,请重新选择", Toast.LENGTH_SHORT).show();
                        return; // 取消设置提醒
                    }
                    // 更新任务结束时间
                    task.setEndTime(calendar.getTime());
                    String endString = simpleDateFormat.format(task.getEndTime());
                    holder.tvEndTime.setText("结束时间:" + endString);
                    scheduleReminder(task, context);
                    // 刷新数据并保存
                    notifyDataSetChanged();
                    saveTasks();
                },
                        calendar.get(Calendar.HOUR_OF_DAY),
                        calendar.get(Calendar.MINUTE),
                        true);
                timePickerDialog.show(); // 显示时间选择器
            },
                    calendar.get(Calendar.YEAR),
                    calendar.get(Calendar.MONTH),
                    calendar.get(Calendar.DAY_OF_MONTH));
            datePickerDialog.show(); // 显示日期选择器
        });

        // 设置点击监听器,单击任务时,切换其完成状态
        holder.itemView.setOnClickListener(v -> {
            task.setCompleted(!task.isCompleted());
            holder.tvTask.setPaintFlags(task.isCompleted() ? Paint.STRIKE_THRU_TEXT_FLAG : 0);
            if (task.isCompleted()) {
                cancelReminder(task, context); // 完成任务时取消提醒
            }else {
                scheduleReminder(task, context);
            }

        });
        // 设置长按监听器,长按任务时,显示删除确认对话框
        holder.itemView.setOnLongClickListener(v -> {
            AlertDialog alertDialog;
            AlertDialog.Builder builder = new AlertDialog.Builder(context)
                    .setTitle("是否删除") // 设置对话框标题
                    .setMessage("是否删除该任务") // 设置对话框内容
                    // "确定"按钮
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            tasks.remove(position); // 从任务列表移除任务
                            notifyDataSetChanged(); // 通知数据变化,更新RecyclerView
                            cancelReminder(task,context);
                            saveTasks(); // 调用保存函数
                            dialog.dismiss(); // 关闭对话框
                        }
                    })
                    // "取消"按钮,setNegativeButton点击后自动关闭对话框
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {

                        }
                    });
            alertDialog = builder.create();
            alertDialog.show();
            return true;
        });
    }

    /**
     * 单个任务项的视图组件ViewHolder
     */
    public static class TaskViewHolder extends RecyclerView.ViewHolder {
        TextView tvTask; // 显示任务内容

        TextView tvStartTime; // 创建时间

        TextView tvEndTime; // 结束时间

        Button btnSetEndTime; // 设置结束时间按钮

        /**
         * 构造函数,初始化视图组件
         */
        public TaskViewHolder(@NonNull View itemView) {
            super(itemView);
            tvTask = itemView.findViewById(R.id.tvTask); // 获取TextView组件
            tvStartTime = itemView.findViewById(R.id.tvStartTime);
            tvEndTime = itemView.findViewById(R.id.tvEndTime);
            btnSetEndTime = itemView.findViewById(R.id.btnSetEndTime);
        }
    }

    /**
     * 保存函数,保存任务列表到sharedPreferences
     */
    public void saveTasks() {
        SharedPreferences sharedPreferences = context.getSharedPreferences("todo_list", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit(); // 获取Editor对象,修改sharedPreferences
        Gson gson = new Gson();
        String json = gson.toJson(tasks); // 将任务列表转换成JSON字符串
        editor.putString("tasks", json); // 将JSON字符串保存到sharedPreferences
        editor.apply(); // 提交修改
    }

    /**
     * 加载函数,从SharedPreferences中加载任务列表
     */
    public void loadTasks(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences("todo_list", Context.MODE_PRIVATE);
        String json = sharedPreferences.getString("tasks", null); // 获取保存的JSON字符串
        if (json != null) {
            Gson gson = new Gson();
            Task[] tasksArray = gson.fromJson(json, Task[].class); // 将JSON字符串转换为任务数组
            tasks.clear(); // 清空当前任务列表
            tasks.addAll(Arrays.asList(tasksArray)); // 将转换为后的任务数组添加到任务列表
            notifyDataSetChanged(); // 通知数据变化,更新RecyclerView
        }
    }

    /**
     * 设置提醒
     *
     * @param task 任务对象,包含任务内容和结束时间
     * @param context 应用上下文
     */
    private void scheduleReminder(Task task, Context context) {
        // 如果任务没有设置结束时间,则无法设置提醒,直接返回
        if (task.getEndTime() == null) {
            return;
        }

        // 创建一个日历对象,用于处理时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(task.getEndTime());

        // 使用任务内容和结束时间的哈希值作为唯一请求码
        // 这样可以确保每个任务的请求码是唯一的
        int requestCode = (task.getContent() + task.getEndTime().getTime()).hashCode();

        // 创建一个 Intent,指向 ReminderReceiver(广播接收器)
        // ReminderReceiver 会在提醒时间到达时触发
        Intent intent = new Intent(context, ReminderReceiver.class);
        intent.putExtra("taskContent", task.getContent()); // 传递任务内容
        intent.putExtra("taskId", requestCode); // 传递请求码

        // 创建 PendingIntent,用于在指定时间触发广播
        PendingIntent pendingIntent = PendingIntent.getBroadcast(
                context, // 应用上下文
                requestCode, // 唯一请求码
                intent, // 广播接收器的 Intent
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE // 标志位
        );

        // 获取系统的 AlarmManager 服务
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (alarmManager != null) {
            // 根据 Android 版本选择不同的设置方式
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                // Android 12 及以上版本需要检查精确闹钟权限
                if (alarmManager.canScheduleExactAlarms()) {
                    // 如果有精确闹钟权限,使用精确闹钟
                    alarmManager.setExactAndAllowWhileIdle(
                            AlarmManager.RTC_WAKEUP, // 使用系统时间(即使设备休眠也会唤醒)
                            calendar.getTimeInMillis(), // 提醒时间
                            pendingIntent // 待触发的 PendingIntent
                    );
                    // 显示成功提示
                    Toast.makeText(context, "已设置提醒:" + task.getContent(), Toast.LENGTH_SHORT).show();
                } else {
                    // 如果没有精确闹钟权限,使用不精确的闹钟
                    alarmManager.set(
                            AlarmManager.RTC_WAKEUP, // 使用系统时间(即使设备休眠也会唤醒)
                            calendar.getTimeInMillis(), // 提醒时间
                            pendingIntent // 待触发的 PendingIntent
                    );
                    // 显示权限不足的提示
                    Toast.makeText(context, "提醒可能不准确,请授予精确闹钟权限", Toast.LENGTH_SHORT).show();
                }
            } else {
                // Android 12 以下版本可以直接使用精确闹钟
                alarmManager.setExactAndAllowWhileIdle(
                        AlarmManager.RTC_WAKEUP, // 使用系统时间(即使设备休眠也会唤醒)
                        calendar.getTimeInMillis(), // 提醒时间
                        pendingIntent // 待触发的 PendingIntent
                );
                // 显示成功提示
                Toast.makeText(context, "已设置提醒:" + task.getContent(), Toast.LENGTH_SHORT).show();
            }
        }
    }

    /**
     * 取消提醒
     *
     * @param task 任务对象,包含任务内容和结束时间
     * @param context 应用上下文
     */
    private void cancelReminder(Task task, Context context) {
        // 如果任务没有设置结束时间,则无法取消提醒,直接返回
        if (task.getEndTime() == null) {
            return;
        }

        // 使用任务内容和结束时间的哈希值作为唯一请求码
        // 这样可以确保每个任务的请求码是唯一的
        int requestCode = (task.getContent() + task.getEndTime().getTime()).hashCode();

        // 创建一个 Intent,指向 ReminderReceiver(广播接收器)
        Intent intent = new Intent(context, ReminderReceiver.class);
        intent.putExtra("taskId", requestCode); // 传递请求码

        // 创建 PendingIntent,用于取消提醒
        PendingIntent pendingIntent = PendingIntent.getBroadcast(
                context, // 应用上下文
                requestCode, // 唯一请求码
                intent, // 广播接收器的 Intent
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE // 标志位
        );

        // 获取系统的 AlarmManager 服务
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (alarmManager != null) {
            // 取消提醒
            alarmManager.cancel(pendingIntent);
        }

        // 释放 PendingIntent 资源
        pendingIntent.cancel();
    }

    /**
     * 获取RecyclerView中显示的项数,即任务列表的大小
     */
    @Override
    public int getItemCount() {
        return tasks.size();
    }
}

三、运行结果

(1)设置结束时间

(2)通知提醒


四、总结

       在这篇文章中,我们实现了设置结束时间、提醒功能。

       我会持续更新此项目,如果需要源码的,可以在评论区回我,或者是私信我,感谢大家的阅读,下篇文章见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值