Xamarin.Android 日历
日历 API
Android 4 中引入的新日历 API 集支持旨在读取或写入日历提供程序的数据的应用程序。 这些 API 支持大量与日历数据的交互选项,包括读取和写入事件、与会者和提醒的功能。 通过使用应用程序中的日历提供程序,通过 API 添加的数据将显示在 Android 4 附带的内置日历应用中。
添加权限
在应用程序中使用新的日历 API 时,首先需要向 Android 清单添加适当的权限。 添加的权限为 android.permisson.READ_CALENDAR
和 android.permission.WRITE_CALENDAR
,具体取决于是读取和/还是写入日历数据。
使用日历协定
设置权限后,可以使用 CalendarContract
类与日历数据进行交互。 此类提供一个数据模型,应用程序在与日历提供程序交互时可以使用这些数据模型。 CalendarContract
允许应用程序将 URI 解析为日历实体,例如日历和事件。 它还提供了一种方法来与每个实体中的各种字段进行交互,例如日历的名称和 ID,或事件的开始日期和结束日期。
让我们看一个使用日历 API 的示例。 在此示例中,我们将检查如何枚举日历及其事件,以及如何向日历添加新事件。
列出日历
首先,让我们来看看如何枚举已在日历应用中注册的日历。 为此,我们可以实例化 CursorLoader
。 在 Android 3.0 (API 11) 中引入,CursorLoader
是使用 ContentProvider
的首选方法。 至少需要为日历和要返回的列指定内容 URI;此列规范称为投影。
通过调用 CursorLoader.LoadInBackground
方法,我们可以查询内容提供程序以获取数据,例如日历提供程序。
LoadInBackground
执行实际加载操作,并返回包含查询结果的 Cursor
。
CalendarContract
帮助我们指定内容 Uri
和投影。 若要获取用于查询日历的内容 Uri
,我们只需使用以下 CalendarContract.Calendars.ContentUri
属性:
var calendarsUri = CalendarContract.Calendars.ContentUri;
使用 CalendarContract
指定所需的日历列同样简单。 只需将 CalendarContract.Calendars.InterfaceConsts
类中的字段添加到数组中。 例如,以下代码包括日历的 ID、显示名称和帐户名称:
string[] calendarsProjection = {
CalendarContract.Calendars.InterfaceConsts.Id,
CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName,
CalendarContract.Calendars.InterfaceConsts.AccountName
};
如果使用 SimpleCursorAdapter
将数据绑定到 UI,则 Id
很重要,我们很快就会看到。 在内容 URI 和投影到位后,我们实例化 CursorLoader
并调用 CursorLoader.LoadInBackground
方法以返回带有日历数据的游标,如下所示:
var loader = new CursorLoader(this, calendarsUri, calendarsProjection, null, null, null);
var cursor = (ICursor)loader.LoadInBackground();
此示例的 UI 包含一个 ListView
,列表中每个项目都表示单个日历。 以下 XML 显示包含 ListView
的标记:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@android:id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
此外,我们需要为每个列表项指定 UI,我们将其放置在单独的 XML 文件中,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:id="@+id/calDisplayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dip" />
<TextView android:id="@+id/calAccountName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12dip" />
</LinearLayout>
从这一点开始,将光标中的数据绑定到 UI 只是正常的 Android 代码。 我们将按如下所示使用 SimpleCursorAdapter
:
string[] sourceColumns = {
CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName,
CalendarContract.Calendars.InterfaceConsts.AccountName };
int[] targetResources = {
Resource.Id.calDisplayName, Resource.Id.calAccountName };
SimpleCursorAdapter adapter = new SimpleCursorAdapter (this,
Resource.Layout.CalListItem, cursor, sourceColumns, targetResources);
ListAdapter = adapter;
在上面的代码中,适配器接受 sourceColumns
数组中指定的列,并将其写入游标中每个日历项的 targetResources
数组中的用户界面元素。 此处使用的活动是 ListActivity
的子类;它包括我们为其设置适配器的 ListAdapter
属性。
下面是显示最终结果的屏幕截图,其中日历信息显示在 ListView
中:
列出日历事件
接下来,让我们看看如何枚举给定日历的事件。 基于上面的示例,当用户选择其中一个日历时,我们将提供事件列表。 因此,我们需要在前面的代码中处理项选择:
ListView.ItemClick += (sender, e) => {
int i = (e as ItemEventArgs).Position;
cursor.MoveToPosition(i);
int calId =
cursor.GetInt (cursor.GetColumnIndex (calendarsProjection [0]));
var showEvents = new Intent(this, typeof(EventListActivity));
showEvents.PutExtra("calId", calId);
StartActivity(showEvents);
};
在此代码中,我们将创建一个意向以打开 EventListActivity
类型的活动,并在意向中传递日历的 ID。 我们需要 ID 才能知道要查询事件的日历。 在 EventListActivity
的 OnCreate
方法中,我们可以从 Intent
检索 ID,如下所示:
_calId = Intent.GetIntExtra ("calId", -1);
现在,让我们查询此日历 ID 的事件。 查询事件的过程类似于我们之前查询日历列表的方式,但这次我们将使用 CalendarContract.Events
类。 以下代码创建用于检索事件的查询:
var eventsUri = CalendarContract.Events.ContentUri;
string[] eventsProjection = {
CalendarContract.Events.InterfaceConsts.Id,
CalendarContract.Events.InterfaceConsts.Title,
CalendarContract.Events.InterfaceConsts.Dtstart
};
var loader = new CursorLoader(this, eventsUri, eventsProjection,
String.Format ("calendar_id={0}", _calId), null, "dtstart ASC");
var cursor = (ICursor)loader.LoadInBackground();
在此代码中,我们首先从 CalendarContract.Events.ContentUri
属性获取事件的内容 Uri
。 然后,指定要在 eventsProjection 数组中检索的事件列。
最后,我们使用此信息实例化 CursorLoader
,并调用加载程序 LoadInBackground
方法以返回事件数据的 Cursor
。
若要在 UI 中显示事件数据,可以使用标记和代码,就像之前一样显示日历列表。 同样,我们使用 SimpleCursorAdapter
将数据绑定到 ListView
,如以下代码所示:
string[] sourceColumns = {
CalendarContract.Events.InterfaceConsts.Title,
CalendarContract.Events.InterfaceConsts.Dtstart };
int[] targetResources = {
Resource.Id.eventTitle,
Resource.Id.eventStartDate };
var adapter = new SimpleCursorAdapter (this, Resource.Layout.EventListItem,
cursor, sourceColumns, targetResources);
adapter.ViewBinder = new ViewBinder ();
ListAdapter = adapter;
此代码和前面用于显示日历列表的代码之间的主要区别是使用 ViewBinder
,该代码在行中设置:
adapter.ViewBinder = new ViewBinder ();
ViewBinder
类允许我们进一步控制如何将值绑定到视图。 在这种情况下,我们使用它将事件开始时间从毫秒转换为日期字符串,如以下实现中所示:
class ViewBinder : Java.Lang.Object, SimpleCursorAdapter.IViewBinder
{
public bool SetViewValue (View view, Android.Database.ICursor cursor,
int columnIndex)
{
if (columnIndex == 2) {
long ms = cursor.GetLong (columnIndex);
DateTime date = new DateTime (1970, 1, 1, 0, 0, 0,
DateTimeKind.Utc).AddMilliseconds (ms).ToLocalTime ();
TextView textView = (TextView)view;
textView.Text = date.ToLongDateString ();
return true;
}
return false;
}
}
这会显示事件列表,如下所示:
添加日历事件
我们已了解如何读取日历数据。 现在,让我们了解如何向日历添加事件。 为此,请务必包括前面提到的 android.permission.WRITE_CALENDAR
权限。 若要向日历添加事件,我们将:
- 创建一个
ContentValues
实例。 - 使用
CalendarContract.Events.InterfaceConsts
类中的键填充ContentValues
实例。 - 设置事件开始和结束时间的时区。
- 使用
ContentResolver
将事件数据插入日历中。
下面的代码演示了以下步骤:
ContentValues eventValues = new ContentValues ();
eventValues.Put (CalendarContract.Events.InterfaceConsts.CalendarId,
_calId);
eventValues.Put (CalendarContract.Events.InterfaceConsts.Title,
"Test Event from M4A");
eventValues.Put (CalendarContract.Events.InterfaceConsts.Description,
"This is an event created from Xamarin.Android");
eventValues.Put (CalendarContract.Events.InterfaceConsts.Dtstart,
GetDateTimeMS (2011, 12, 15, 10, 0));
eventValues.Put (CalendarContract.Events.InterfaceConsts.Dtend,
GetDateTimeMS (2011, 12, 15, 11, 0));
eventValues.Put(CalendarContract.Events.InterfaceConsts.EventTimezone,
"UTC");
eventValues.Put(CalendarContract.Events.InterfaceConsts.EventEndTimezone,
"UTC");
var uri = ContentResolver.Insert (CalendarContract.Events.ContentUri,
eventValues);
请注意,如果未设置时区,则会引发 Java.Lang.IllegalArgumentException
类型的异常。 由于事件时间值必须以毫秒为单位表示,因为自纪元以来,我们将创建一个 GetDateTimeMS
方法 (EventListActivity
),以将日期规范转换为毫秒格式:
long GetDateTimeMS (int yr, int month, int day, int hr, int min)
{
Calendar c = Calendar.GetInstance (Java.Util.TimeZone.Default);
c.Set (Java.Util.CalendarField.DayOfMonth, 15);
c.Set (Java.Util.CalendarField.HourOfDay, hr);
c.Set (Java.Util.CalendarField.Minute, min);
c.Set (Java.Util.CalendarField.Month, Calendar.December);
c.Set (Java.Util.CalendarField.Year, 2011);
return c.TimeInMillis;
}
如果将按钮添加到事件列表 UI 并在按钮的单击事件处理程序中运行上述代码,该事件将添加到日历并更新在我们的列表中,如下所示:
如果打开日历应用,我们会看到该事件也写入其中:
正如你所看到的,Android 允许强大且易于访问来检索和保存日历数据,从而允许应用程序无缝集成日历功能。