Android應用添加日歷提醒功能

功能

在安卓應用里調用系統日歷,直接創建一個帶提醒的日歷事件,甚至不需要跳轉到日歷界面,只需要獲取系統日歷的讀取權限即可。

需要的權限

AndroidManifest.xml里添加

<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>

注意: 如果是Android 6.0(API 23)以上,需要動態申請權限。

代碼

創建一個CalendarHelper工具類,包含:

  • 獲取系統日歷賬戶
  • 自動寫入事件
  • 添加提醒
  • 自動處理沒有日歷賬戶的情況(可提示用戶手動創建)
  • 動態申請權限(當用戶拒絕權限時,我這里會彈出一個提示框,提示的內容可以從外部傳入,也可以使用默認的。或者你不是使用默認,直接打開系統的設置頁面也是可以的(下面屏蔽了這部分的代碼))
  • 判斷是否已存在相同時間的邏輯,避免重復添加
package com.cocos.calender;import android.app.Activity;
import android.Manifest;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.provider.CalendarContract;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import java.util.Calendar;
import java.util.TimeZone;
import org.json.JSONObject;
import org.json.JSONException;
import android.widget.Toast;public class CalendarHelper {private static final String TAG = "CalendarHelper";/** 用來存放拒絕權限時的提示語 */private static String denyPermissionMessage = "未獲得日歷權限,無法添加提醒事件";/** 日歷權限請求碼 */public static final int REQUEST_CALENDAR_PERMISSION = 1010;/** 臨時存儲待執行事件 */private static PendingEvent pendingEvent;private static class PendingEvent {String title;String description;String location;long beginTime;long endTime;int reminderMinutes;PendingEvent(String title, String description, String location,long beginTime, long endTime, int reminderMinutes) {this.title = title;this.description = description;this.location = location;this.beginTime = beginTime;this.endTime = endTime;this.reminderMinutes = reminderMinutes;}}/*** 檢查權限并添加事件(帶權限請求)*/public static void addEventWithPermission(Activity activity,String title,String description,String location,long beginTimeMillis,long endTimeMillis,int reminderMinutes) {// 檢查日歷讀寫權限if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_CALENDAR)!= PackageManager.PERMISSION_GRANTED|| ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_CALENDAR)!= PackageManager.PERMISSION_GRANTED) {// 保存事件等待用戶授權pendingEvent = new PendingEvent(title, description, location, beginTimeMillis, endTimeMillis, reminderMinutes);// 這里可以加解釋,但不強制if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.READ_CALENDAR)) {Log.i(TAG, "需要日歷權限來添加提醒事件");}// ? 直接請求權限(即使用戶上次拒絕,這里依舊會再彈一次)ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.READ_CALENDAR,Manifest.permission.WRITE_CALENDAR},REQUEST_CALENDAR_PERMISSION);} else {// 權限已授權,直接添加addEvent(activity, title, description, location, beginTimeMillis, endTimeMillis, reminderMinutes);}}/*** 在 Activity 的 onRequestPermissionsResult 中調用*/public static void onRequestPermissionsResultCalendar(Activity activity,int requestCode,@NonNull int[] grantResults) {if (requestCode == REQUEST_CALENDAR_PERMISSION) {if (grantResults.length >= 2&& grantResults[0] == PackageManager.PERMISSION_GRANTED&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {Log.i(TAG, "日歷權限申請成功");if (pendingEvent != null) {addEvent(activity,pendingEvent.title,pendingEvent.description,pendingEvent.location,pendingEvent.beginTime,pendingEvent.endTime,pendingEvent.reminderMinutes);pendingEvent = null;}} else {Log.e(TAG, "用戶拒絕了日歷權限");Toast.makeText(activity,denyPermissionMessage,Toast.LENGTH_SHORT).show();// 如果用戶永久拒絕,可跳轉設置
//                if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, android.Manifest.permission.READ_CALENDAR)) {
//                    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
//                    intent.setData(Uri.parse("package:" + activity.getPackageName()));
//                    activity.startActivity(intent);
//                }}}}/** 獲取系統日歷賬戶 ID */private static long getCalendarAccountId(Context context) {Cursor userCursor = context.getContentResolver().query(CalendarContract.Calendars.CONTENT_URI,new String[]{CalendarContract.Calendars._ID},null, null, null);if (userCursor != null) {try {if (userCursor.moveToFirst()) {return userCursor.getLong(0);}} finally {userCursor.close();}}return -1;}/** 判斷事件是否已存在(避免重復) */private static boolean isEventAlreadyExists(Context context, String title, long beginTimeMillis) {long oneMinuteBefore = beginTimeMillis - 60 * 1000;long oneMinuteAfter = beginTimeMillis + 60 * 1000;Cursor cursor = context.getContentResolver().query(CalendarContract.Events.CONTENT_URI,new String[]{CalendarContract.Events._ID},CalendarContract.Events.TITLE + "=? AND " +CalendarContract.Events.DTSTART + ">=? AND " +CalendarContract.Events.DTSTART + "<=?",new String[]{title, String.valueOf(oneMinuteBefore), String.valueOf(oneMinuteAfter)},null);if (cursor != null) {try {if (cursor.moveToFirst()) {return true; // 已存在}} finally {cursor.close();}}return false;}/** 插入日歷事件 + 提醒 */private static boolean addEvent(Context context,String title,String description,String location,long beginTimeMillis,long endTimeMillis,int reminderMinutes) {long calId = getCalendarAccountId(context);if (calId == -1) {Log.e(TAG, "沒有找到系統日歷賬戶,請先在系統日歷中添加一個賬戶");return false;}if (isEventAlreadyExists(context, title, beginTimeMillis)) {Log.w(TAG, "事件已存在,跳過添加: " + title);return false;}ContentValues eventValues = new ContentValues();eventValues.put(CalendarContract.Events.CALENDAR_ID, calId);eventValues.put(CalendarContract.Events.TITLE, TextUtils.isEmpty(title) ? "未命名事件" : title);eventValues.put(CalendarContract.Events.DESCRIPTION, description);eventValues.put(CalendarContract.Events.EVENT_LOCATION, location);eventValues.put(CalendarContract.Events.DTSTART, beginTimeMillis);eventValues.put(CalendarContract.Events.DTEND, endTimeMillis);eventValues.put(CalendarContract.Events.HAS_ALARM, 1);eventValues.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID());Uri newEvent = context.getContentResolver().insert(CalendarContract.Events.CONTENT_URI, eventValues);if (newEvent == null) {Log.e(TAG, "插入日歷事件失敗");return false;}long eventId = ContentUris.parseId(newEvent);ContentValues reminderValues = new ContentValues();reminderValues.put(CalendarContract.Reminders.EVENT_ID, eventId);reminderValues.put(CalendarContract.Reminders.MINUTES, reminderMinutes);reminderValues.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT);Uri reminderUri = context.getContentResolver().insert(CalendarContract.Reminders.CONTENT_URI, reminderValues);if (reminderUri == null) {Log.e(TAG, "插入提醒失敗");return false;}Log.i(TAG, "日歷事件添加成功,eventId=" + eventId);return true;}public static void creatroCalendarReminder(Context context,String data){try {// 將傳入的字符串轉成 JSON 對象JSONObject json = new JSONObject(data);// 從 JSON 中取字段,如果沒有就用默認值String title = json.optString("title", "測試");String description = json.optString("description", "測試");String location = json.optString("location", "測試");int startHour = json.optInt("startHour", 1);int startMinute = json.optInt("startMinute", 10);int endHour = json.optInt("endHour", startHour + 1);Calendar begin = Calendar.getInstance();begin.add(Calendar.DAY_OF_MONTH, 0);         // 哪天開始,Calendar.DAY_OF_MONTH當前時間 + 后面參數值,比如我這里為0,就是今天,如果為1就是明天begin.set(Calendar.HOUR_OF_DAY, startHour);  // 開始的小時,這里是24小時制 startHour的取值范圍為0~23begin.set(Calendar.MINUTE, startMinute);     // 開始的分鐘 Calendar end = (Calendar) begin.clone();end.set(Calendar.HOUR_OF_DAY, endHour);      // 結束的時間,參數和上面開始時間一樣,賦值方式為end.set()if (context == null) {Log.e("Calendar", "Context is null");return;}// 添加事件CalendarHelper.addEventWithPermission((Activity) context,title,description,location,begin.getTimeInMillis(),   //事件開始時間的毫秒值end.getTimeInMillis(),     //事件結束時間的毫秒值5   // 提前5分鐘提醒);} catch (JSONException e) {e.printStackTrace();Log.e("Calendar", "JSON解析失敗:" + data);}}/*** 從外部傳提示文本過來* @param message*/public static void setDenyPermissionMessage(String message) {if (!TextUtils.isEmpty(message)) {denyPermissionMessage = message;}}
}

Activity中的邏輯

先在Activity中引入CalendarHelper類,并調用CalendarHelper.creatroCalendarReminder()方法,傳入參數,實現日歷添加功能。

    @Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);// 處理日歷權限if (requestCode == CalendarHelper.REQUEST_CALENDAR_PERMISSION) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {CalendarHelper.onRequestPermissionsResultCalendar(this, requestCode, grantResults);}}}

添加創建日歷提醒事件和傳入提示文本

    /*** 創建日歷提醒事件* @param data*/public static void creatroCalendarReminder(String data){Context context = AppActivity.getInstance();CalendarHelper.creatroCalendarReminder(context,data);}/*** 獲取讀取日歷權限被拒絕時的提示文本* @param str*/public static void setDenyPermissionMessage(String str){CalendarHelper.setDenyPermissionMessage(str);}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/921944.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/921944.shtml
英文地址,請注明出處:http://en.pswp.cn/news/921944.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

?Git Bisect 二分查找定位錯誤總結

# Git Bisect 二分查找指南## 1. 基本原理&#xff08;ASCII示意圖&#xff09; 假設提交歷史是一條時間線&#xff0c;Ggood&#xff08;正常&#xff09;&#xff0c;Bbad&#xff08;異常&#xff09;&#xff1a;提交順序: G --- G --- G --- B --- B --- B | | | 初始正常…

ThingsKit物聯網平臺 v2.0.0 發布|前端UI重構、底層架構升級

v2.0.0 Release發布日期&#xff1a;2025/08/25 代碼標簽&#xff1a;v2.0.0_Release&#x1f947; 新增功能國標級聯&#xff08;支持上級、下級國標級聯&#xff09;視頻回放、錄像計劃&#xff08;用戶可以通過錄像計劃生成對應的視頻回放并查看&#xff09;Modbus_TCP協…

Lua > Mac Mini M4安裝openresty

Mac Mini M4安裝openresty 主要參考 https://www.cnblogs.com/helios-fz/p/15703260.html brew uninstall nginxbrew update brew install pcre openssl #brew install geoip# brew tap openresty/brew # brew install openresty # brew install openresty/brew/openresty# VER…

【多線程案例】:單例模式

多線程案例8.1 單例模式餓漢模式懶漢模式懶漢模式-單線程版懶漢模式-多線程版懶漢模式-多線程版(改進)8.1 單例模式 單個實例. 在一個 java 進程中, 要求指定的類,只能有唯–個實例。&#xff08;嘗試 new 多個實例的時候, 就會直接編譯報錯&#xff09; 單例模式是校招中最常…

【Python/Pytorch】-- 貝葉斯定理

文章目錄 文章目錄01 貝葉斯定理的理解02 在MRI重建領域應用01 貝葉斯定理的理解 貝葉斯定理的基本公式&#xff1a;P(A|B)P(B|A)*P(A) / P(B) 首先是如何理解這個公式&#xff1f; 在B事件發生的條件下&#xff0c;A發生的概率 P(A|B) 在B事件發生的條件下&#xff0c;A和B同…

子網掩碼的隱形陷阱:為何能ping通卻無法HTTPS訪問

問題現象深度解析在近期企業網絡維護中&#xff0c;運維團隊發現一個具有教學意義的典型案例&#xff1a;某臺部署在10.165.111.0/24網段的業務服務器&#xff08;10.165.111.71&#xff09;可以成功ping通目標中間件主機(10.165.110.11)&#xff0c;但通過HTTPS協議訪問https:…

【ArcGIS】如何編輯圖層的屬性表

GIS按屬性選擇后刪除所選項呈現灰色_arcgis刪除字段灰色-CSDN博客

大數據各組件flume,datax,presto,DolphinScheduler,findBI在大數據數倉架構中的作用和功能。

一、數據倉庫核心價值鋪墊在講具體技術前&#xff0c;先明確數據倉庫&#xff08;Data Warehouse&#xff0c;簡稱數倉&#xff09; 的核心作用&#xff1a; 數據倉庫是 “整合企業多源數據、按業務主題組織、支持決策分析” 的結構化數據存儲體系&#xff0c;核心價值是打破數…

React From表單使用Formik和yup進行校驗

一、Formik的使用 官方文檔地址&#xff1a;https://formik.org/docs/tutorial#validation 首先安裝依賴 yarn add formik2.導入并初始化 import { useFormik } from formik; initialValues&#xff1a;初始化 輸入框的密碼和賬號 onSubmit&#xff1a;當點擊提交按鈕時&am…

netty-scoket.io路徑配置

1、服務端代碼 package com.yh.service.socket;import com.corundumstudio.socketio.SocketIOServer; import com.corundumstudio.socketio.store.RedissonStoreFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory…

20250910榮品RD-RK3588-MID開發板在Android13系統下解決點卡迪的屏閃屏的問題

20250910榮品RD-RK3588-MID開發板在Android13系統下解決點卡迪的屏閃屏的問題 2025/9/5 15:44緣起&#xff1a;榮品RD-RK3588-MID開發板在Android13系統下解決點卡迪的屏。 按 POWER按鍵 關機之后&#xff0c;2s之內再次短按 POWER按鍵&#xff0c;開機之后屏會抖動。 2s后短按…

正態分布 - 計算 Z-Score 的 無偏估計

正態分布 - 計算 Z-Score 的 無偏估計 flyfish Z-Score公式與計算步驟 1 公式&#xff08;樣本Z-Score&#xff09; 實際應用中&#xff0c;我們幾乎不知道“總體均值/標準差”&#xff0c;所以常用樣本數據計算&#xff1a; zixi?xˉsz_i \frac{x_i - \bar{x}}{s}zi?sxi??…

ai生成文章,流式傳輸(uniapp,微信小程序)

1.環境nutui-uniappvue3tsunocss2.功能源碼包含ai生成邏輯&#xff0c;內容生成實時打字機功能&#xff0c;ai數據處理等<script setup lang"ts"> import {queryAIParams, } from /api/pagesA import { submitFn } from /api/aiimport Navbar from /component…

Linux設備內存不足如何處理

[rootlocalhost ~]# free -mtotal used free shared buff/cache available Mem: 31208 14317 1280 1551 15610 14657 Swap: 15927 2781 13146 [rootlocalhost ~]#從 free -m 輸出來看&…

中間件八股

文章目錄RedisRedis為什么快&#xff1f;Redis Redis為什么快&#xff1f; 首先它是內存數據庫&#xff0c;所有數據直接操作內存而非磁盤&#xff0c;避免了 I/O 瓶頸&#xff1b;其次采用單線程模型&#xff0c;消除了多線程切換的開銷&#xff0c;同時通過非阻塞 I/O 多路…

【參數詳解與使用指南】PyTorch MNIST數據集加載

# 加載MNIST數據集 train_dataset datasets.MNIST(root./data, trainTrue, downloadTrue, transformtransform) # 下載訓練集 test_dataset datasets.MNIST(root./data, trainFalse, downloadTrue, transformtransform) # 下載測試集在深度學習入門過程中&#xff0c;MNIST手…

閉包面試題

閉包&#xff08;Closure&#xff09; 是指一個函數能夠記住并訪問其詞法作用域&#xff08;定義時的作用域&#xff09;&#xff0c;即使該函數在其詞法作用域之外執行。一、通俗理解&#xff08;面試可這樣開頭&#xff09;&#xff1a;> 閉包就是一個函數“記住”了它出生…

WebSocket 雙向通信實戰:SCADA 移動端實時操控響應優化

引言&#xff1a;SCADA 移動端的 “延遲煩惱” 與破局之道在電力調度、水廠監控、智能制造等場景中&#xff0c;SCADA 系統&#xff08;數據采集與監視控制系統&#xff09;是當之無愧的 “工業指揮官”—— 它能實時采集設備運行數據&#xff08;如電網負荷、水泵壓力、機床轉…

SafeEar:浙大和清華聯合推出的AI音頻偽造檢測框架,錯誤率低至2.02%

本文轉載自&#xff1a;https://www.hello123.com/safeear ** 一、&#x1f512; SafeEar&#xff1a;你的聲音 “防火墻”&#xff0c;讓 AI 偽造音頻無所遁形 擔心自己的聲音被 AI 模仿甚至偽造&#xff1f;SafeEar就是來幫你解決這個難題的&#xff01;它是由浙江大學和清…

uni-app iOS 日志與崩潰分析全流程 多工具協作的實戰指南

在 uni-app 跨平臺開發中&#xff0c;iOS 應用的日志與崩潰分析往往是開發者最頭疼的問題。 日志分散&#xff1a;uni-app 的 JS 日志、原生插件日志、系統日志分布在不同位置&#xff1b;崩潰難復現&#xff1a;用戶反饋的崩潰往往無法在開發機還原&#xff1b;符號化復雜&…