Xamarin Android 日历Xamarin.Android Calendar

日历 APICalendar API

Android 4 中引入的一组新日历 Api 支持设计为向日历提供程序读取数据或向其写入数据的应用程序。A new set of calendar APIs introduced in Android 4 supports applications that are designed to read or write data to the calendar provider. 这些 Api 支持丰富的日历数据交互选项,包括读取和写入事件、与会者和提醒的能力。These APIs support a wealth of interaction options with calendar data, including the ability to read and write events, attendees, and reminders. 通过在你的应用程序中使用日历提供程序,你通过 API 添加的数据将出现在 Android 4 随附的内置日历应用中。By using the calendar provider in your application, data you add through the API will appear in the built-in calendar app that comes with Android 4.

添加权限Adding Permissions

在您的应用程序中使用新的日历 Api 时,您需要做的第一件事就是向 Android 清单添加适当的权限。When working with the new calendar APIs in your application, the first thing you need to do is add the appropriate permissions to the Android manifest. 你需要添加的权限是 android.permisson.READ_CALENDARandroid.permission.WRITE_CALENDAR,具体取决于你是要读取还是写入日历数据。The permissions you need to add are android.permisson.READ_CALENDAR and android.permission.WRITE_CALENDAR, depending on whether you are reading and/or writing calendar data.

使用日历约定Using the Calendar Contract

设置权限后,可以使用 CalendarContract 类与日历数据进行交互。Once you set the permissions, you can interact with calendar data by using the CalendarContract class. 此类提供了一个数据模型,应用程序在与日历提供程序交互时可以使用该模型。This class provides a data model that applications can use when they interact with the calendar provider. CalendarContract 允许应用程序将 Uri 解析为日历实体,如日历和事件。The CalendarContract allows applications to resolve the Uris to calendar entities, such as calendars and events. 它还提供了一种方法,用于与每个实体中的各个字段(例如日历的名称和 ID)或事件的开始和结束日期交互。It also provides a way to interact with various fields in each entity, such as a calendar's name and ID, or an event's start and end date.

让我们看看使用日历 API 的示例。Let's look at an example that uses the Calendar API. 在此示例中,我们将检查如何枚举日历及其事件,以及如何将新事件添加到日历。In this example, we'll examine how to enumerate calendars and their events, as well as how to add a new event to a calendar.

列出日历Listing Calendars

首先,让我们看一下如何枚举已在日历应用中注册的日历。First, let's examine how to enumerate the calendars that have been registered in the calendar app. 为此,我们可以实例化一个 CursorLoaderTo do this, we can instantiate a CursorLoader. 在 Android 3.0 (API 11)中引入,CursorLoader 是使用 ContentProvider的首选方式。Introduced in Android 3.0 (API 11), CursorLoader is the preferred way to consume a ContentProvider. 至少需要为日历和要返回的列指定内容 Uri;此列规范称为_投影_。At a minimum, we'll need to specify the content Uri for calendars and the columns we want to return; this column specification is known as a projection.

通过调用 CursorLoader.LoadInBackground 方法,我们可以查询数据的内容提供程序,如日历提供程序。Calling the CursorLoader.LoadInBackground method allows us to query a content provider for data, such as the calendar provider. LoadInBackground 执行实际的加载操作并返回一个包含查询结果的 CursorLoadInBackground performs the actual load operation and returns a Cursor with the results of the query.

CalendarContract 有助于我们同时指定内容 Uri 和投影。The CalendarContract assists us in specifying both the content Uri and the projection. 若要获取内容 Uri 以查询日历,只需按如下所示使用 CalendarContract.Calendars.ContentUri 属性:To get the content Uri for querying calendars, we can simply use the CalendarContract.Calendars.ContentUri property like this:

var calendarsUri = CalendarContract.Calendars.ContentUri;

使用 CalendarContract 指定所需的日历列同样简单。Using the CalendarContract to specify which calendar columns we want is equally simple. 只需将 CalendarContract.Calendars.InterfaceConsts 类中的字段添加到数组中。We just add fields in the CalendarContract.Calendars.InterfaceConsts class to an array. 例如,以下代码包括日历的 ID、显示名称和帐户名:For example, the following code includes the calendar's ID, display name, and account name:

string[] calendarsProjection = {
    CalendarContract.Calendars.InterfaceConsts.Id,
    CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName,
    CalendarContract.Calendars.InterfaceConsts.AccountName
};

如果使用 SimpleCursorAdapter 将数据绑定到 UI,则必须包含 Id,如我们将很快看到。The Id is important to include if you are using a SimpleCursorAdapter to bind the data to the UI, as we will see shortly. 使用内容 Uri 和投影后,将实例化 CursorLoader 并调用 CursorLoader.LoadInBackground 方法以返回带有日历数据的光标,如下所示:With the content Uri and projection in place, we instantiate the CursorLoader and call the CursorLoader.LoadInBackground method to return a cursor with the calendar data as shown below:

var loader = new CursorLoader(this, calendarsUri, calendarsProjection, null, null, null);
var cursor = (ICursor)loader.LoadInBackground();

此示例的 UI 包含一个 ListView,列表中的每一项都表示一个日历。The UI for this example contains a ListView, with each item in the list representing a single calendar. 下面的 XML 演示包含 ListView的标记:The following XML shows the markup that includes the 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 文件中,如下所示:Also, we need to specify the UI for each list item, which we place in a separate XML file as follows:

<?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 代码。From this point on, it's just normal Android code to bind the data from the cursor to the UI. 我们将使用 SimpleCursorAdapter,如下所示:We'll use a SimpleCursorAdapter as follows:

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 数组中的用户界面元素。In the above code, the adapter takes the columns specified in the sourceColumns array and writes them to the user interface elements in the targetResources array for each calendar entry in the cursor. 此处使用的活动是 ListActivity的子类;它包含我们将适配器设置为的 ListAdapter 属性。The Activity used here is a subclass of ListActivity; it includes the ListAdapter property to which we set the adapter.

下面是显示最终结果的屏幕截图,其中显示了日历信息 ListViewHere's a screenshot showing the end result, with the calendar info displayed in the ListView:

在模拟器中运行的 CalendarDemo,其中显示了两个日历条目CalendarDemo running in emulator, displaying two calendar entries

列出日历事件Listing Calendar Events

接下来,让我们看看如何枚举给定日历的事件。Next let's look at how to enumerate the events for a given calendar. 根据上面的示例,我们将在用户选择一个日历时显示事件列表。Building upon the example above, we'll present a list of events when the user selects one of the calendars. 因此,我们需要处理前面代码中的项选择:Therefore, we'll need to handle the item selection in the previous code:

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。In this code, we're creating an Intent to open an Activity of type EventListActivity, passing the calendar's ID in the Intent. 我们将需要 ID 来了解要查询事件的日历。We will need the ID to know which calendar to query for events. EventListActivityOnCreate 方法中,可以从 Intent 检索 ID,如下所示:In the EventListActivity's OnCreate method, we can retrieve the ID from the Intent as shown below:

_calId = Intent.GetIntExtra ("calId", -1);

现在,让我们查询此日历 ID 的事件。Now let's query events for this calendar ID. 用于查询事件的过程与我们之前查询的日历列表的方式类似,只是在这种情况下,我们将使用 CalendarContract.Events 类。The process to query for events is similar to the way we queried for a list of calendars earlier, only this time we'll work with the CalendarContract.Events class. 下面的代码创建查询以检索事件:The following code creates a query to retrieve 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 的内容。In this code, we first get the content Uri for events from the CalendarContract.Events.ContentUri property. 然后,在 eventsProjection 数组中指定要检索的事件列。Then we specify the event columns we want to retrieve in the eventsProjection array. 最后,使用此信息来实例化 CursorLoader 并调用加载程序的 LoadInBackground 方法,以返回包含事件数据的 CursorFinally, we instantiate a CursorLoader with this information and call the loader's LoadInBackground method to return a Cursor with the event data.

若要在 UI 中显示事件数据,可以使用标记和代码,就像在显示日历列表之前所做的那样。To display the event data in the UI, we can use markup and code just like we did before to display the list of calendars. 同样,我们使用 SimpleCursorAdapter 将数据绑定到 ListView,如以下代码所示:Again, we use SimpleCursorAdapter to bind the data to a ListView as shown in the following code:

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;

此代码与之前用于显示日历列表的代码之间的主要区别在于,使用的是在行上设置的 ViewBinderThe main difference between this code and the code that we used earlier to show the calendar list is the use of a ViewBinder, which is set on the line:

adapter.ViewBinder = new ViewBinder ();

通过 ViewBinder 类,我们可以进一步控制如何将值绑定到视图。The ViewBinder class allows us to further control how we bind values to views. 在这种情况下,我们使用它将事件开始时间从毫秒转换为日期字符串,如以下实现所示:In this case, we use it to convert the event start time from milliseconds to a date string, as shown in the following implementation:

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;
    }    
}

这会显示事件列表,如下所示:This displays a list of events as shown below:

显示三个日历事件的示例应用的屏幕截图Screenshot of example app displaying three calendar events

添加日历事件Adding a Calendar Event

我们已了解如何读取日历数据。We've seen how to read calendar data. 现在,让我们看看如何向日历添加事件。Now let's see how to add an event to a calendar. 为此,请确保包含之前提到的 android.permission.WRITE_CALENDAR 权限。For this to work, be sure to include the android.permission.WRITE_CALENDAR permission we mentioned earlier. 若要将事件添加到日历,我们将:To add an event to a calendar, we will:

  1. 创建 ContentValues 实例。Create a ContentValues instance.
  2. 使用 CalendarContract.Events.InterfaceConsts 类中的键来填充 ContentValues 实例。Use keys from the CalendarContract.Events.InterfaceConsts class to populate the ContentValues instance.
  3. 设置事件开始和结束时间的时区。Set the time zones for the event start and end times.
  4. 使用 ContentResolver 将事件数据插入日历中。Use a ContentResolver to insert the event data into the calendar.

下面的代码演示了这些步骤:The code below illustrates these steps:

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 类型的异常。Note that if we do not set the time zone, an exception of type Java.Lang.IllegalArgumentException will be thrown. 由于事件时间值必须以毫秒表示,自 epoch 起,我们将创建一个 GetDateTimeMS 方法(在 EventListActivity中),以将我们的日期规范转换为毫秒格式:Because event time values must be expressed in milliseconds since epoch, we create a GetDateTimeMS method (in EventListActivity) to convert our date specifications into millisecond format:

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,并在按钮的单击事件处理程序中运行上述代码,则会将该事件添加到日历并在列表中更新,如下所示:If we add a button to the event list UI and run the above code in the button's click event handler, the event is added to the calendar and updated in our list as shown below:

带日历事件的示例应用程序的屏幕截图,后跟 "添加示例事件" 按钮Screenshot of example app with calendar events followed by Add Sample Event button

如果我们打开日历应用程序,我们就会看到该事件也会写入其中:If we open the calendar app, then we will see that the event is written there as well:

显示选定日历事件日历应用程序的屏幕截图Screenshot of calendar app displaying the selected calendar event

正如您所看到的那样,Android 允许使用功能强大且易于访问的来检索和保存日历数据,从而使应用程序无缝集成日历功能。As you can see, Android allows powerful and easy access to retrieve and persist calendar data, allowing applications to seamlessly integrate calendar capabilities.