news 2026/7/6 3:11:33

UniAppx 实现安卓日历事件添加功能

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UniAppx 实现安卓日历事件添加功能

UniAppx 实现安卓日历事件添加功能

在移动应用开发中,与系统日历集成是一项常见需求。本文将详细介绍如何在 UniAppx + Vue3 + uts uvue环境下,通过 UTS 语言调用安卓原生 API,实现日历事件的添加功能。

功能概述

实现的核心功能包括:

  • 日历权限管理:检查和请求安卓日历读写权限
  • 日历账户管理:获取或创建日历账户 ID
  • 事件创建:向系统日历中添加事件,包含标题、描述、地点、时间等信息
  • 提醒设置:为事件设置提前提醒
  • 操作日志:记录所有操作步骤,便于调试和用户反馈

项目结构

packageB/ └── addCalendar/ └── index.uvue # 主组件逻辑与样式

模板结构设计

模板结构简洁明了,主要包含三个部分:

  1. 标题区域:显示页面标题"添加日历事件"
  2. 按钮区域:包含一个"添加日历事件"按钮,点击触发添加流程
  3. 日志区域:使用滚动视图显示操作日志,用户可以查看详细的操作记录

日期时间格式化

组件包含两个核心的格式化函数:

  • formatDate:将 Date 对象格式化为"YYYY-MM-DD"格式的字符串
  • formatTime:将 Date 对象格式化为"HH:MM"格式的字符串

同时包含一个 padZero 辅助函数,用于将单个数字补零。开始时间设为当前时间,结束时间设为当前时间加一小时。

时间戳转换

getTimestamp 函数将日期字符串和时间字符串转换为毫秒级时间戳,便于后续存储到日历数据库中。

权限管理

权限检查

checkCalendarPermission 函数用于检查日历权限状态,通过 UTSAndroid.getUniActivity() 获取当前 Activity,然后调用 checkSystemPermissionGranted 检查 WRITE_CALENDAR 和 READ_CALENDAR 权限是否已授权。

权限请求

requestCalendarPermission 函数用于向用户请求日历权限,使用 UTSAndroid.requestSystemPermission 发起权限请求,包含成功和失败的回调处理。

日历账户管理

getCalendarId 函数负责获取或创建日历账户 ID:

  1. 首先查询本地日历账户(account_type=LOCAL)
  2. 如果没有找到本地账户,查找任何可见的日历账户(visible=1)
  3. 如果仍然没有找到,返回默认账户 ID 1

该函数使用安卓的 ContentResolver 和 ContentProvider 查询日历数据库。

提醒设置

addReminder 函数为创建的事件添加提醒:

  1. 创建 ContentValues 对象,设置 event_id、minutes 和 method
  2. 使用 ContentResolver 插入到 reminders 表中
  3. method=1 表示使用通知提醒

事件创建

doAddCalendarEvent 函数是实际执行添加日历事件的核心逻辑:

  1. 获取 ContentResolver 对象
  2. 获取日历账户 ID
  3. 计算开始和结束时间的时间戳
  4. 验证结束时间必须晚于开始时间
  5. 创建 ContentValues 对象,设置事件的各种属性(日历ID、标题、描述、地点、开始时间、结束时间、时区、是否有闹钟)
  6. 使用 ContentResolver 插入事件到 events 表中
  7. 如果创建成功,获取事件 ID 并添加提醒
  8. 使用 showToast 向用户反馈操作结果

添加流程

addCalendarEvent 函数是添加日历事件的入口函数:

  1. 记录操作日志
  2. 检查日历权限
  3. 如果没有权限,先请求权限,权限获取成功后执行添加
  4. 如果有权限,直接执行添加

日志记录

组件使用 ref 维护一个日志数组,addLog 函数将日志消息添加到数组头部,并在控制台打印。页面显示时会记录初始化日志。

样式设计

样式采用简洁的移动端设计风格:

  • 页面背景色为浅灰色
  • 标题居中显示,使用加粗字体
  • 按钮使用绿色背景,白色文字
  • 日志区域使用白色背景,圆角边框
  • 日志内容使用较小字体,每条日志之间有分隔线

安卓兼容性注意事项

在 UniApp 安卓端开发时,需要注意以下几点:

  1. 权限声明:需要在 manifest.json 中声明日历相关权限
  2. API 兼容性:不同安卓版本的日历 ContentProvider URI 可能不同
  3. 类型转换:UTS 语言中需要使用 .toInt()/.toLong() 进行类型转换
  4. 异常处理:操作日历可能抛出异常,需要进行 try-catch 处理

优化建议

1. 权限设置引导

如果用户拒绝权限后,可以引导用户到系统设置页面开启权限。

2. 事件重复功能

可以增加事件重复设置,支持每天、每周、每月等重复模式。

3. 事件编辑和删除

可以扩展功能,支持对已添加的事件进行编辑和删除操作。

4. 多账户支持

可以让用户选择添加到哪个日历账户。

总结

通过以上实现,我们完成了一个完整的安卓日历事件添加功能。核心要点包括:

  1. 使用 UTS 语言调用安卓原生 API
  2. 正确处理权限请求和检查
  3. 使用 ContentResolver 操作日历数据库
  4. 完整的错误处理和日志记录
  5. 良好的用户反馈机制

这种实现方式具有良好的兼容性,可以轻松应用于各种需要日历集成的场景。


完整代码

index.uvue

<template><viewclass="container"><textclass="title">添加日历事件</text><viewclass="btn-group"><buttonclass="btn btn-success"@click="addCalendarEvent">添加日历事件</button></view><viewclass="log-area"><textclass="log-title">操作日志:</text><scroll-viewclass="log-content"scroll-y><textv-for="(log, index) in logs":key="index"class="log-item">{{ log }}</text></scroll-view></view></view></template><scriptsetuplang="uts">import{ref}from'vue';consteventTitle='测试日历事件';consteventDescription='这是由uni-app-x自动创建的测试事件';consteventLocation='北京市朝阳区';constnow=newDate();constpadZero=(num:number):string=>{returnnum<10?'0'+num:''+num;};constformatDate=(date:Date):string=>{constyear=date.getFullYear();constmonth=padZero(date.getMonth()+1);constday=padZero(date.getDate());returnyear+'-'+month+'-'+day;};constformatTime=(date:Date):string=>{consthours=padZero(date.getHours());constminutes=padZero(date.getMinutes());returnhours+':'+minutes;};conststartDate=formatDate(now);conststartTime=formatTime(now);constendDateTime=newDate(now.getTime()+60*60*1000);constendDate=formatDate(endDateTime);constendTime=formatTime(endDateTime);constlogs=ref<string[]>([]);constaddLog=(msg:string)=>{consttime=formatTime(newDate());logs.value.unshift('['+time+'] '+msg);console.log(msg);};constgetTimestamp=(dateStr:string,timeStr:string):number=>{constdateParts=dateStr.split('-');consttimeParts=timeStr.split(':');constyear=parseInt(dateParts[0]);constmonth=parseInt(dateParts[1])-1;constday=parseInt(dateParts[2]);consthour=parseInt(timeParts[0]);constminute=parseInt(timeParts[1]);returnnewDate(year,month,day,hour,minute).getTime();};constcheckCalendarPermission=():boolean=>{constactivity=UTSAndroid.getUniActivity();if(activity==null){addLog('获取Activity失败');returnfalse;}consthasPermission=UTSAndroid.checkSystemPermissionGranted(activity,['android.permission.WRITE_CALENDAR','android.permission.READ_CALENDAR']);addLog('日历权限状态: '+(hasPermission?'已授权':'未授权'));returnhasPermission;};constrequestCalendarPermission=(callback:()=>void)=>{constactivity=UTSAndroid.getUniActivity();if(activity==null){addLog('获取Activity失败');return;}constpermissions=['android.permission.WRITE_CALENDAR','android.permission.READ_CALENDAR'];UTSAndroid.requestSystemPermission(activity,permissions,(allRight:boolean,grantedPermissions:string[])=>{if(allRight){addLog('日历权限请求成功');callback();}else{addLog('用户拒绝了部分权限');uni.showToast({title:'权限被拒绝',icon:'none'});}},(permissionDenied:boolean,deniedPermissions:string[])=>{addLog('用户拒绝了权限申请');uni.showToast({title:'权限被拒绝',icon:'none'});});};constgetCalendarId=():number=>{constactivity=UTSAndroid.getUniActivity();if(activity==null){addLog('获取Activity失败');return-1;}try{constcontentResolver=activity.getContentResolver();if(contentResolver==null){addLog('获取ContentResolver失败');return-1;}constCALENDAR_URI=android.net.Uri.parse('content://com.android.calendar/calendars');constprojection=arrayOf('_id','account_name','account_type','visible');constcursor=contentResolver.query(CALENDAR_URI,projection,'account_type=?',arrayOf('LOCAL'),null);if(cursor!=null){if(cursor.moveToFirst()){constidIndex=cursor.getColumnIndex('_id');constcalendarId=cursor.getInt(idIndex);cursor.close();addLog('找到本地日历账户,ID: '+calendarId);returncalendarId;}cursor.close();}constcursor2=contentResolver.query(CALENDAR_URI,projection,'visible=?',arrayOf('1'),null);if(cursor2!=null){if(cursor2.moveToFirst()){constidIndex=cursor2.getColumnIndex('_id');constcalendarId=cursor2.getInt(idIndex);cursor2.close();addLog('找到可见日历账户,ID: '+calendarId);returncalendarId;}cursor2.close();}addLog('未找到日历账户,使用默认ID: 1');return1;}catch(e){addLog('获取日历ID失败: '+e);return1;}};constaddReminder=(contentResolver:android.content.ContentResolver,eventId:string|null,minutesBefore:number)=>{if(eventId==null)return;try{constreminderValues=newandroid.content.ContentValues();reminderValues.put('event_id',eventId);reminderValues.put('minutes',minutesBefore.toInt());reminderValues.put('method',1);constREMINDERS_URI=android.net.Uri.parse('content://com.android.calendar/reminders');contentResolver.insert(REMINDERS_URI,reminderValues);addLog('已设置提前'+minutesBefore+'分钟提醒');}catch(e){addLog('添加提醒失败: '+e);}};constdoAddCalendarEvent=()=>{constactivity=UTSAndroid.getUniActivity();if(activity==null){addLog('获取Activity失败');return;}try{constcontentResolver=activity.getContentResolver();if(contentResolver==null){addLog('获取ContentResolver失败');return;}constcalendarId=getCalendarId();if(calendarId<0){addLog('无法获取有效的日历ID');return;}conststartMillis=getTimestamp(startDate,startTime);constendMillis=getTimestamp(endDate,endTime);if(endMillis<=startMillis){uni.showToast({title:'结束时间必须晚于开始时间',icon:'none'});return;}constvalues=newandroid.content.ContentValues();values.put('calendar_id',calendarId.toInt());values.put('title',eventTitle);values.put('description',eventDescription);values.put('eventLocation',eventLocation);values.put('dtstart',startMillis.toLong());values.put('dtend',endMillis.toLong());values.put('eventTimezone',java.util.TimeZone.getDefault().getID());values.put('hasAlarm',1);constEVENTS_URI=android.net.Uri.parse('content://com.android.calendar/events');consteventUri=contentResolver.insert(EVENTS_URI,values);if(eventUri!=null){consteventId=eventUri.getLastPathSegment();addLog('事件创建成功,ID: '+eventId);addReminder(contentResolver,eventId,10);uni.showToast({title:'添加成功',icon:'success'});}else{addLog('事件创建失败');uni.showToast({title:'添加失败',icon:'none'});}}catch(e){addLog('添加事件失败: '+e);uni.showToast({title:'添加失败',icon:'none'});}};constaddCalendarEvent=()=>{addLog('开始添加日历事件...');if(!checkCalendarPermission()){addLog('没有日历权限,先请求权限');requestCalendarPermission(()=>{doAddCalendarEvent();});return;}doAddCalendarEvent();};onShow(()=>{addLog('页面加载,点击按钮添加日历事件');});</script><style>.container{padding:20rpx;background-color:#f5f5f5;flex:1;}.title{font-size:36rpx;font-weight:bold;color:#333;text-align:center;margin-bottom:30rpx;}.btn-group{margin-top:30rpx;}.btn{margin-bottom:20rpx;font-size:32rpx;}.btn-success{background-color:#4cd964;color:#fff;}.log-area{margin-top:30rpx;background-color:#fff;padding:20rpx;border-radius:12rpx;}.log-title{font-size:28rpx;font-weight:bold;color:#333;margin-bottom:15rpx;}.log-content{max-height:300rpx;}.log-item{font-size:24rpx;color:#666;padding:8rpx 0;border-bottom:1rpx solid #f0f0f0;}</style>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/6 3:09:07

第五次shell理解练习

课后作业《理解Shell》 一、Shell的类型 默认Shell配置位置 用户登录后启动的默认Shell记录在 /etc/passwd 文件第7个字段&#xff0c;登录终端/图形终端仿真器都会自动加载该Shell&#xff0c;绝大多数Linux系统默认使用bash。 查看指定用户配置&#xff1a;cat /etc/passwd |…

作者头像 李华
网站建设 2026/7/6 3:08:20

从404链接到开源知识库:Galgame Wiki 如何为小众文化“存档”

前几天整理浏览器收藏夹&#xff0c;点开一个名为“Galgame攻略”的文件夹。十几个链接&#xff0c;超过一半显示“404 Not Found”。剩下的几个&#xff0c;有的页面停留在2018年&#xff0c;有的跳转到了完全陌生的域名。我一个个点开&#xff0c;又一个个关掉。这些链接曾经…

作者头像 李华
网站建设 2026/7/6 3:08:16

《怪物猎人:荒野》 豪华中文版 全DLC VBS一键启狩猎

获取地址&#xff1a;《怪物猎人&#xff1a;荒野》 《怪物猎人&#xff1a;荒野》豪华中文全DLC整合&#xff0c;个人修复加入原生手柄直连支持并封装VBS一键启动脚本&#xff0c;解压双击自动配置运行环境进入游戏。 完整收录全部武器派生、怪物生态与剧情扩展&#xff0c;…

作者头像 李华
网站建设 2026/7/6 3:07:40

C++26 constexpr placement new 详解:编译期管理对象生命周期

C26 constexpr placement new 详解&#xff1a;编译期管理对象生命周期 本文是「C26 新特性单篇精讲」系列第 14 篇。阅读约需 5 分钟&#xff0c;文末可跳转完整合订本。 一、是什么 C26 允许在 constexpr 函数中使用 placement new&#xff0c;即在已分配内存上构造对象&…

作者头像 李华
网站建设 2026/7/6 3:05:15

实验室搬迁科普专业流程防护标准与合规核心要点

实验室搬迁不同于普通办公、家居搬迁&#xff0c;是一项高精密、高安全、高合规的专项技术工程&#xff0c;也是高校、科研院所、企业研发机构场地迁移中最容易出现操作疏漏的核心环节。实验室涉及精密分析仪器、理化实验设备、专用台柜、化学试剂耗材、生物留样标本、核心科研…

作者头像 李华
网站建设 2026/7/6 3:04:13

AI Agent开发实战:从零理解Agent、RAG与LangChain核心原理

&#x1f680; 30款热门AI模型一站整合&#xff0c;DeepSeek/GLM/Qwen 随心用&#xff0c;限时 5 折。 &#x1f449; 点击领海量免费额度 你是不是也刷到过那些“付费9880元”、“一周学完Agent”、“超越所有人”的AI Agent课程广告&#xff1f;看着很诱人&#xff0c;但冷…

作者头像 李华