原文:
zh.annas-archive.org/md5/37F309F5583A3BFE9D4DF14FC6F7D1A9
译者:飞龙
前言
本书是一本实用的、手把手指导开发 Android 应用程序的指南,使用 Android Ice Cream Sandwich(Android 4.0)的新特性,采用逐步讲解和清晰解释的示例代码。你将通过这些示例代码学习 Android 4.0 的新 API。
本书涵盖内容
第一章, 为所有人设计的操作栏, 介绍了操作栏,并展示如何使用和配置操作栏。
第二章, 新布局 - GridLayout, 介绍了 GridLayout,并展示如何使用和配置 GridLayout。GridLayout 是随 Android Ice Cream Sandwich 推出的一种新布局。这个布局是一个优化过的布局,可以替代 LinearLayout 和 RelativeLayout。
第三章, 社交 API, 讲解了随 Android Ice Cream Sandwich 推出的社交 API。这个 API 使得集成社交网络变得简单。此外,在 Ice Cream Sandwich 发布后,现在可以使用高分辨率照片作为联系人的照片。这一章通过示例展示了社交 API 的使用。
第四章, 日历 API, 讲解了与 Android Ice Cream Sandwich 一同推出的日历 API,用于管理日历。事件、参与者、提醒和备忘数据库可以通过这些 API 进行管理。这些 API 使我们能够轻松地将日历与 Android 应用程序集成。这一章展示了如何通过示例使用日历 API。
第五章, 碎片, 介绍了碎片的基础知识以及如何使用它们。
第六章, 支持不同的屏幕尺寸, 介绍了设计支持不同屏幕尺寸的用户界面的方法。
第七章, Android 兼容性包, 介绍了 Android 兼容性包,并展示如何使用它。Android 兼容性包是为了将新 API 移植到 Android 平台的旧版本。
第八章, 新的连接 API - Android Beam 和 Wi-Fi Direct, 介绍了 Android Beam,它使用设备的 NFC 硬件和无需使用无线接入点的 Wi-Fi Direct,允许设备之间相互连接。这一章将教我们如何使用 Android Beam 和 Wi-Fi Direct。
第九章, 多 APK 支持, 介绍了多 APK 支持,这是 Google Play(Android 市场)中的一个新选项,通过它可以为单一应用程序上传多个 APK 版本。
本章可以在以下链接下载:www.packtpub.com/sites/default/files/downloads/Multiple_APK_Support.pdf
。
第十章,Android Jelly Bean 的 API,涵盖了 Android Jelly Bean 及其内部的新 API。
本章可以在 www.packtpub.com/sites/default/files/downloads/Android_JellyBean.pdf
下载。
你需要这本书的内容
要跟随本书中的示例,需要设置并准备好 Android 开发工具。所需的软件列表如下:
-
带有 ADT 插件的 Eclipse
-
Android SDK 工具
-
Android 平台工具
-
最新的 Android 平台
可以使用以下操作系统:
-
Windows XP(32 位)、Vista(32 位或 64 位)或 Windows 7(32 位或 64 位)
-
Mac OS X 10.5.8 或更高版本(仅限 x86)
-
Linux(在 Ubuntu Linux,Lucid Lynx 上测试)
-
需要使用 GNU C 库(glibc)2.7 或更高版本
-
在 Ubuntu Linux 上,需要版本 8.04 或更高版本
-
64 位发行版必须能够运行 32 位应用程序
-
Eclipse IDE 的使用规范如下:
-
Eclipse 3.6.2(Helios)或更高版本(Eclipse 3.5(Galileo)不再支持最新版本的 ADT)
-
Eclipse JDT 插件(包含在大多数 Eclipse IDE 包中)
-
JDK 6(仅 JRE 不够)
-
Android Development Tools 插件(推荐)
本书适合的读者群体
本书适合那些对 Android 平台有经验,但可能不熟悉 Android 4.0 的新特性和 API 的开发者。
想要了解如何支持多种屏幕尺寸和多个 Android 版本的 Android 开发者;这本书也适合你。
约定
在这本书中,你会发现多种文本样式,用以区分不同类型的信息。以下是一些样式示例,以及它们含义的解释。
文中的代码字显示如下:“实现 onCreateOptionsMenu
和 onOptionsItemSelected
方法。”
代码块设置如下:
<?xml version="1.0" encoding="utf-8"?>
<menu >
<item android:id="@+id/settings" android:title="Settings">
</item>
<item android:id="@+id/about" android:title="About">
</item>
</menu>
当我们希望引起你注意代码块中的特定部分时,相关的行或项目会以粗体设置:
@Override
public void onPrepareSubMenu(SubMenu subMenu) {
//In order to add submenus, we should override this method we dynamically created submenus
subMenu.clear();
subMenu.add("SubItem1").setOnMenuItemClickListener(this);
subMenu.add("SubItem2").setOnMenuItemClickListener(this);
}
新术语和重要词汇以粗体显示。你在屏幕上看到的词,例如菜单或对话框中的,文本中会这样出现:“点击 插入 按钮然后点击 列表 按钮”。
注意
警告或重要提示会以这样的框显示。
提示
技巧和窍门会像这样出现。
读者反馈
我们始终欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢或可能不喜欢的地方。读者的反馈对我们开发能让你们充分利用的标题很重要。
要给我们发送一般反馈,只需发送电子邮件至 <feedback@packtpub.com>
,并在邮件的主题中提及书名。
如果你在一个主题上有专业知识,并且有兴趣撰写或参与书籍编写,请查看我们在 www.packtpub.com/authors 的作者指南。
客户支持
既然你现在拥有了 Packt 的一本书,我们有一些事情可以帮助你最大限度地利用你的购买。
下载示例代码
你可以从你在 www.PacktPub.com
的账户下载你购买的所有 Packt 图书的示例代码文件。如果你在其他地方购买了这本书,可以访问 www.PacktPub.com/support
注册,我们会直接将文件通过电子邮件发送给你。
源代码也可以在作者的网站 www.ottodroid.net 上获取。
错误更正
尽管我们已经竭尽全力确保内容的准确性,但错误仍然可能发生。如果你在我们的书中发现了一个错误——可能是文本或代码中的错误——我们非常感激你能向我们报告。这样做可以节省其他读者的时间,并帮助我们对本书的后续版本进行改进。如果你发现任何错误,请访问 www.packtpub.com/support
报告,选择你的书籍,点击 错误更正提交表单 链接,并输入错误的详细信息。一旦你的错误报告被验证,你的提交将被接受,并且错误更正将会被上传到我们的网站,或在相应标题的“错误更正”部分添加到现有的错误更正列表中。你可以通过访问 www.packtpub.com/support
选择你的标题来查看任何现有的错误更正。
盗版
互联网上版权材料的盗版问题在所有媒体中持续存在。在 Packt,我们非常重视保护我们的版权和许可。如果你在互联网上以任何形式遇到我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
如果你发现了疑似盗版材料,请通过 <copyright@packtpub.com>
联系我们,并提供链接。
我们感谢你帮助保护我们的作者,以及我们为你提供有价值内容的能力。
问题咨询
如果你在这本书的任何方面遇到问题,可以通过 <questions@packtpub.com>
联系我们,我们将尽力解决。
第一章:适用于所有人的操作栏
操作栏 API 最初是在 Android 3.0 中引入的。随着 Android 冰激凌三明治的发布,操作栏支持小屏幕尺寸。本章展示了如何使用和配置操作栏。
本章涵盖的主题如下:
-
操作栏类型
-
添加操作栏
-
添加 ActionProvider 和 ShareActionProvider
-
添加操作视图
-
使用操作栏进行导航
操作栏
操作栏是位于用户设备屏幕顶部的用户界面元素。它为用户提供操作和导航功能。操作栏自 API 级别 11(Android 3.0 Honeycomb)起提供,冰激凌三明治发布后,也支持小屏幕设备。以下截图显示了一个带有标签的操作栏示例:
如前图所示,在栏的左侧有一个应用程序图标和标题,然后是导航标签。最后,在标签之后放置操作按钮。那些不适合屏幕显示的操作按钮会以三个点的溢出菜单形式显示在栏的右侧。在前面的截图中,操作栏显示在大屏幕设备上。然而,在小屏幕设备上,操作栏会显示为堆叠的栏,如下截图所示:
如前图所示,可以看到没有足够的空间显示所有操作栏项目,操作栏以屏幕顶部的双栏形式显示。
另一种类型的操作栏是分割操作栏。在这种类型的操作栏中,在窄屏幕上,操作按钮显示在屏幕底部的栏中,如下截图所示:
添加操作栏
冰激凌三明治之后,Android 不再需要使用菜单按钮来访问选项菜单。最佳实践是使用操作栏而不是菜单按钮。从选项菜单迁移到操作栏非常容易。现在我们将创建一个菜单,然后将该菜单迁移到操作栏。
首先,创建一个 Android 项目,然后添加一个包含设置
和关于
作为菜单项的菜单。生成的菜单 XML 文件应如下代码块所示:
提示
下载示例代码
您可以从您的账户下载您购买的所有 Packt 图书的示例代码文件,网址为www.PacktPub.com
。如果您在别处购买了这本书,可以访问www.PacktPub.com/support
注册,我们会直接将文件通过电子邮件发送给您。
<?xml version="1.0" encoding="utf-8"?>
<menu >
<item android:id="@+id/settings" android:title="Settings"></item>
<item android:id="@+id/about" android:title="About"></item>
</menu>
本示例的布局 XML 是一个LinearLayout
布局,其中包含一个TextView
组件,如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
</LinearLayout>
实现onCreateOptionsMenu
和onOptionsItemSelected
方法,如下代码块所示,以显示菜单项:
package com.chapter1;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
public class Chapter1Activity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//Inflate the menu.xml of the android project
//in order to create menu
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
//According to selection, show the Toast message
//of the selected button
switch (item.getItemId()) {
case R.id.settings:
Toast.makeText(this, "Settings options menu button is pressed", Toast.LENGTH_LONG).show();
return true;
case R.id.about:
Toast.makeText(this, "About options menu button is pressed", Toast.LENGTH_LONG).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
为了显示操作栏,Android 应用程序应该在AndroidManifest.xml
文件中至少针对 API 级别 11,如下面的代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<!—set targetSDKversion to 11 because Action Bar is
available since API Level 11-->
<manifest
package="com.chapter1"
android:versionCode="1"
android:versionName="1.0" >
< uses-sdk android:minSdkVersion="5"
android:targetSdkVersion="11" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".Chapter1Activity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
使用这个配置,当应用程序在搭载 Android 3.0 或更高版本的设备上运行时,操作栏将会显示。
当我们在 API 级别 15 的模拟器上运行这个应用程序时,我们将在操作栏的右侧看到溢出菜单,并且当按下溢出菜单时,选项菜单按钮将会显示。为了在操作栏上显示选项菜单按钮(而不是作为溢出菜单),只需在菜单 XML 文件的item
标签内添加android:showAsAction="ifRoom|withText"
。修改后的菜单 XML 文件应该如下面的代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<menu >
<item android:id="@+id/settings" android:title="Settings" android:showAsAction="ifRoom|withText"></item>
<item android:id="@+id/about" android:title="About" android:showAsAction="ifRoom|withText"></item>
</menu>
如果没有足够的空间(ifRoom
)来显示选项菜单按钮,按钮将会作为溢出菜单显示。为了仅显示带有图标的选项菜单按钮(如果提供了图标),应该移除withText
。当你运行应用程序时,它将如下面的截图所示:
在某些情况下,你可能不希望显示操作栏。为了移除操作栏,你需要在AndroidManifest.xml
文件中的activity
标签内添加android:theme="@android:style/Theme.Holo.NoActionBar"
。修改后的AndroidManifest.xml
应该如下面的代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.chapter1"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="5"
android:targetSdkVersion="11" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".Chapter1Activity"
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.NoActionBar" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
为了将操作栏显示为分割操作栏,在AndroidManifest.xml
中的activity
标签内添加android:uiOptions="splitActionBarWhenNarrow"
属性。修改后的AndroidManifest.xml
应该如下面的代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.chapter1"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="5"
android:targetSdkVersion="11" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:uiOptions="splitActionBarWhenNarrow">
<activity
android:name=".Chapter1Activity"
android:label="@string/app_name"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
当你在这个模拟器上运行这个应用程序时,屏幕将如下面的截图所示:
添加 ActionProvider
为了在操作栏中使用自定义视图而不是简单的按钮,ActionProvider
类可能是一个解决方案。ActionProvider从 API 级别 14 开始可用。ActionProvider 可以在操作栏中生成自定义视图,可以生成子菜单,并且可以处理它生成的视图的事件。为了创建一个 ActionProvider,我们应该继承ActionProvider
类。下面的代码展示了一个扩展了ActionProvider
类的示例类,并在操作栏中显示自定义布局而不是简单按钮:
import android.content.Context;
import android.view.ActionProvider;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;
public class Chapter1ActionProvider extends ActionProvider {
Context mContext;
public Chapter1ActionProvider(Context context) {
super(context);
mContext = context;
}
@Override
public View onCreateActionView() {
//This method is the place where we generate a custom layout for the Action Bar menu item
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
View view = layoutInflater.inflate(R.layout.action_provider, null);
ImageButton button = (ImageButton) view.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, "Action Provider click", Toast.LENGTH_LONG).show();
}
});
return view;
}
@Override
public boolean onPerformDefaultAction() {
//This is the method which is called when the Action Bar menu item is in overflow menu and clicked from there
Toast.makeText(mContext, "Action Provider click", Toast.LENGTH_LONG).show();
return true;
}
}
我们必须添加一个构造函数并重写 onCreateActionView()
方法。在构造函数中,我们将 Context
赋值给一个变量,因为我们在后续实现中需要它。onCreateActionView()
方法是生成操作栏菜单项自定义布局的地方。onPerformDefaultAction()
是当操作栏菜单项在溢出菜单中并被点击时调用的方法。如果 ActionProvider 提供子菜单,则永远不会调用此方法。在 onCreateActionView()
方法中使用的自定义布局的布局 XML 如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="true"
android:addStatesFromChildren="true"
android:background="?android:attr/actionBarItemBackground"
style="?android:attr/actionButtonStyle">
<ImageButton android:id="@+id/button"
android:background="@drawable/ic_launcher"
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_gravity="center"
android:scaleType="fitCenter"
android:adjustViewBounds="true" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
如你在 XML 文件中所见,我们将一个 ImageButton
组件和一个 TextView
组件添加到了 LinearLayout
布局中。ImageButton
的 onClickListener()
事件在 Chapter1ActionProvider
类的 onCreateActionView()
方法中实现。在此事件中,将显示一个 Toast
消息。
显示操作栏的 Activity
类如下代码块所示:
public class Chapter1ActionProviderActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.about:
Toast.makeText(this, "About options menu button is pressed", Toast.LENGTH_LONG).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
为了为操作栏菜单项显示自定义布局,我们必须在 menu
XML 文件中分配一个 ActionProvider
类。我们分配了之前作为 ActionProvider
实现的 Chapter1ActionProvider
。我们示例中的 menu XML 文件如下所示:
<?xml version="1.0" encoding="utf-8"?>
<menu >
<item android:id="@+id/settings" android:title="Settings" android:showAsAction="ifRoom|withText"
android:actionProviderClass="com.chapter1.Chapter1ActionProvider"></item>
<item android:id="@+id/about" android:title="About" android:showAsAction="ifRoom|withText"></item>
</menu>
如你在 menu
XML 文件中所见,我们为 settings
菜单项提供了一个 ActionProvider
类。最后重要的一步是在 AndroidManifest.xml
文件中将最低 SDK 版本设置为 API 级别 14,因为 ActionProvider
是在 API 级别 14 中发布的新功能。AndroidManifest.xml
文件应如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.chapter1"
android:versionCode="1"
android:versionName="1.0" >
<!—set minSDKversion to 11 because ActionProvider is
available since API Level 11-->
<uses-sdk android:minSdkVersion="14"
android:targetSdkVersion="14" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".Chapter1ActionProviderActivity"
android:label="@string/app_name"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
当你在模拟器中运行此应用程序时,操作栏中将显示一个带有图像按钮和文本视图的用户界面组件。如果你按下图像按钮,将显示一个吐司消息。屏幕将如下所示:
向 ActionProvider 添加子菜单
可以通过 ActionProvider 显示子菜单。为了添加子菜单,我们应该在 Chapter1ActionProvider
类中重写 onPrepareSubMenu(SubMenu subMenu)
和 hasSubMenu()
方法。Chapter1ActionProvider
类的结果代码应如下代码块所示:
package com.chapter1;
import android.app.Activity;
import android.content.Context;
import android.view.ActionProvider;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.SubMenu;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;
public class Chapter1ActionProvider extends ActionProvider implements
OnMenuItemClickListener {
Context mContext;
public Chapter1ActionProvider(Context context) {
super(context);
mContext = context;
}
@Override
public View onCreateActionView() {
return null;
}
@Override
public boolean onPerformDefaultAction() {
Toast.makeText(mContext, "Action Provider click", Toast.LENGTH_LONG).show();
return true;
}
@Override
public void onPrepareSubMenu(SubMenu subMenu) {
//In order to add submenus, we should override this method
// we dynamically created submenus
subMenu.clear();
subMenu.add("SubItem1").setOnMenuItemClickListener(this);
subMenu.add("SubItem2").setOnMenuItemClickListener(this);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
Toast.makeText(mContext, "Sub Item click", Toast.LENGTH_LONG).show();
return true;
}
@Override
public boolean hasSubMenu() {
// we implemented it as returning true because we have menu
return true;
}
}
在 onPrepareSubMenu(SubMenu subMenu)
方法中,我们动态创建了子菜单并设置了它们的 onMenuItemClickListener
事件。如果 hasSubMenu()
方法返回 true,则会调用 onPrepareSubMenu(SubMenu subMenu)
方法,因此我们将其实现为返回 true。
也可以通过 menu
XML 文件创建子菜单。如果你想从 menu
XML 文件创建子菜单,onPrepareSubMenu(SubMenu subMenu)
应该如下代码块所示:
@Override
public void onPrepareSubMenu(SubMenu subMenu) {
MenuInflater inflater = ((Activity)mContext).getMenuInflater();
inflater.inflate(R.menu.menu2, subMenu);
}
此代码展示了如何使用 menu
XML 文件 menu2
将 XML 文件展开以创建子菜单。
ShareActionProvider
ShareActionProvider提供了一种一致的分享方式。它在操作栏上放置一个带有分享图标的动作按钮。当你点击该按钮时,它会列出可用于分享的应用程序。你需要在menu
项中声明ShareActionProvider
,如下面的代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<menu >
<item android:id="@+id/share" android:title="Share" android:showAsAction="ifRoom"
android:actionProviderClass="android.widget.ShareActionProvider"></item>
<item android:id="@+id/about" android:title="About" android:showAsAction="ifRoom"></item>
<item android:id="@+id/settings" android:title="Settings" android:showAsAction="ifRoom"></item>
</menu>
使用ShareActionProvider
的Activity
类应该如下代码块所示:
package com.chapter1;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ShareActionProvider;
public class Chapter1ShareActionProviderActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
ShareActionProvider myShareActionProvider;
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
MenuItem item = menu.findItem(R.id.share);
myShareActionProvider = (ShareActionProvider)item.getActionProvider();
myShareActionProvider.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME);
myShareActionProvider.setShareIntent(getShareIntent());
return true;
}
private Intent getShareIntent() {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, "www.somesite.com");
return shareIntent;
}
}
如代码所示,我们在onCreateOptionsMenu(Menu menu)
方法中获取了menu
项的ShareActionProvider
属性。然后我们使用ShareActionProvider
的setShareIntent
方法定义分享的意图。getShareIntent()
方法创建了一个用于分享文本的意图。我们使用此方法为ShareActionProvider
实例定义意图。
ShareActionProvider
会在一个文件中保存用于分享的应用程序历史记录。ShareActionProvider
默认使用的文件是ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME
。可以通过setShareHistoryFileName
方法更改此文件。你需要向此方法传递一个带有.xml 扩展名的 XML 文件名。ShareActionProvider
使用这个文件来查找最常用于分享的应用程序。然后它将最常使用的应用程序显示在分享动作按钮附近,作为默认分享目标。
使用ShareActionProvider
的应用程序界面如下所示:
由于ShareActionProvider
是在 API 级别 14 引入的,因此我们必须在AndroidManifest.xml
文件中将最小 SDK 设置为 14,如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.chapter1"
android:versionCode="1"
android:versionName="1.0" >
<!—set minSdkVersion to 14 because ShareActionProvider is available
since API Level 14-->
<uses-sdk android:minSdkVersion="14" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".Chapter1ShareActionProviderActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
添加一个动作视图
动作视图是出现在操作栏中的用户界面组件,而不是操作按钮。这个视图是可以折叠的,即如果它被配置为可折叠,意味着在按下动作按钮时会展开。如果没有配置为可折叠,默认会展开显示。在以下示例中,我们添加了一个动作视图,并展示了其事件以及如何处理这些事件。
首先,添加一个动作视图的布局,其中包含三个带有文本Large
、Medium
和Small
的按钮,如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<Button
android:id="@+id/buttonLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Large"
android:textSize="15dp" />
<Button
android:id="@+id/buttonMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Medium"
android:textSize="12dp" />
<Button
android:id="@+id/buttonSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Small"
android:textSize="9dp" />
</LinearLayout>
然后,我们需要将此动作视图绑定到操作栏的menu
项。menu
的 XML 代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<menu >
<item android:id="@+id/size" android:title="Size" android:showAsAction="ifRoom|collapseActionView"
android:actionLayout="@layout/actionview"></item>
<item android:id="@+id/about" android:title="About" android:showAsAction="ifRoom"></item>
<item android:id="@+id/settings" android:title="Settings" android:showAsAction="ifRoom|withText"></item>
</menu>
如你在menu
的 XML 代码中所见,我们通过设置actionLayout
属性将动作视图绑定到size
菜单项。我们还设置了showAsAction
属性为collapseActionView
。这样,动作视图就是可折叠的,当按下动作按钮项时展开。这个选项可以帮助我们在操作栏中节省空间。如果此属性未设置为collapseActionView
,则动作视图默认会展开显示。
处理动作视图事件的Activity
类如下代码块所示:
package com.chapter1;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MenuItem.OnActionExpandListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class Chapter1ActionViewActivity extends Activity implements
OnClickListener {
Button buttonLarge;
Button buttonMedium;
Button buttonSmall;
Menu menu;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//you can set on click listeners of the items in Action View in this method
this.menu = menu;
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
MenuItem item = menu.findItem(R.id.size);
item.setOnActionExpandListener(new Chapter1ActionListener(this));
buttonLarge = (Button)item.getActionView().findViewById(R.id.buttonLarge);
buttonLarge.setOnClickListener(this);
buttonMedium = (Button)item.getActionView().findViewById(R.id.buttonMedium);
buttonMedium.setOnClickListener(this);
buttonSmall = (Button)item.getActionView().findViewById(R.id.buttonSmall);
buttonSmall.setOnClickListener(this);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.size:
Toast.makeText(this, "Size options menu button is pressed", Toast.LENGTH_LONG).show();
return true;
case R.id.about:
Toast.makeText(this, "About options menu button is pressed", Toast.LENGTH_LONG).show();
return true;
case R.id.settings:
Toast.makeText(this, "Settings options menu button is pressed", Toast.LENGTH_LONG).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onClick(View v) {
if(v == buttonLarge )
{
Toast.makeText(this, "Large button is pressed", Toast.LENGTH_LONG).show();
//Collapse the action view
menu.findItem(R.id.size).collapseActionView();
}
else if(v == buttonMedium )
{
Toast.makeText(this, "Medium button is pressed", Toast.LENGTH_LONG).show();
//Collapse the action view
menu.findItem(R.id.size).collapseActionView();
}
else if(v == buttonSmall)
{
Toast.makeText(this, "Small button is pressed", Toast.LENGTH_LONG).show();
//Collapse the action view
menu.findItem(R.id.size).collapseActionView();
}
}
// This class returns a callback when Action View is expanded or collapsed
public static class Chapter1ActionListener implements OnActionExpandListener
{
Activity activity;
public Chapter1ActionListener(Activity activity)
{
this.activity = activity;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
Toast.makeText(activity, item.getTitle()+" button is collapsed", Toast.LENGTH_LONG).show();
return true;
}
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
Toast.makeText(activity, item.getTitle()+" button is expanded", Toast.LENGTH_LONG).show();
return true;
}
}
}
正如在Chapter1ActionViewActivity
中看到的,你可以在onCreateOptionsMenu(Menu menu)
方法中设置动作视图内各项目的事件监听器。我们在onCreateOptionsMenu(Menu menu)
方法中设置了动作视图内按钮的onClickListener
事件。
可以通过expandActionView()
和collapseActionView()
方法以编程方式展开和折叠动作视图。正如在Chapter1ActionViewActivity
的onClick(View v)
方法中看到的那样,我们使用collapseActionView()
方法手动折叠了动作视图。
当动作视图展开或折叠时,你可以使用OnActionExpandListener
类执行一个动作。正如代码中看到的,我们定义了实现OnActionExpandListener
的Chapter1ActionListener
类。我们重写了这个类的onMenuItemActionCollapse(MenuItem item)
和onMenuItemActionExpand(MenuItem item)
方法,以便显示一个Toast
消息。我们将Activity
作为参数传递给Chapter1ActionListener
的构造函数,因为显示Toast
消息时需要Activity
。为了处理展开和折叠事件,我们必须注册setOnActionExpandListener()
方法与OnActionExpandListener
类。正如代码中看到的,我们在onCreateOptionsMenu(Menu menu)
方法中注册了这个事件。当动作视图折叠和展开时,我们会显示一个Toast
消息。
由于动作视图是在 API 级别 14 中引入的,因此我们必须在AndroidManifest.xml
文件中将最小 SDK 属性设置为 14 或更高,如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.chapter1"
android:versionCode="1"
android:versionName="1.0" >
<!—set minSdkVersion to 14 because Action View is
available since API Level 14-->
<uses-sdk android:minSdkVersion="14" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".Chapter1ActionViewActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
当你在模拟器上运行这个应用时,它看起来会像下面的截图一样:
使用操作栏进行导航
标签导航也可以通过TabWidget
类实现。然而,操作栏有一些优点。操作栏会根据设备屏幕大小自动调整自身。例如,如果没有足够的空间显示标签,它会以堆叠条的方式显示标签。因此,在实现标签导航时,最好使用操作栏。
现在,我们将要了解如何使用操作栏进行标签导航。首先,创建一个 Android 项目并添加两个片段:一个显示Fragment A
,另一个显示Fragment B
。片段的布局 XML 应该如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment A"
android:textAppearance="?android:attr/textAppearanceLarge" android:layout_gravity="center_horizontal"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment B"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
为这两个片段扩展Fragment
类的类应该如下代码块所示:
package com.chapter1;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class FragmentA extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_a, container,false);
return view;
}
}
package com.chapter1;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class FragmentB extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_b, container, false);
return view;
}
}
为了使用操作栏进行标签导航,我们首先应该实现ActionBar.TabListener
类。实现TabListener
的类将在Activity
类中添加标签时使用。带有TabListener
实现的Activity
类应该如下代码块所示:
package com.chapter1;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.ActionBar.TabListener;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
public class Chapter1ActionBarTabActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
Tab tab = actionBar
.newTab()
.setText("First tab")
.setTabListener(
new Chapter1TabListener<FragmentA>(this, "fragmentA",FragmentA.class));
actionBar.addTab(tab);
tab = actionBar
.newTab()
.setText("Second Tab")
.setTabListener(
new Chapter1TabListener<FragmentB>(this, "fragmentB",FragmentB.class));
actionBar.addTab(tab);
}
public static class Chapter1TabListener<T extends Fragment> implements
TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
public Chapter1TabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// we initialize and add the fragment to our Activity if it doesn't exist
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
} else {
// If it exists, we simply attach it
ft.attach(mFragment);
}
}
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// in this method we detach the fragment because // it shouldn't be displayed
if (mFragment != null) {
ft.detach(mFragment);
}
}
@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// This method is called when the tab is reselected
}
}
}
在 Chapter1TabListener
类中有三个需要重写的方法:onTabReselected(Tab tab, FragmentTransaction ft)
、onTabUnselected(Tab tab, FragmentTransaction ft)
和 onTabSelected(Tab tab, FragmentTransaction ft)
。在 onTabSelected(Tab tab, FragmentTransaction ft)
方法中,如果片段不存在,我们会初始化并将片段添加到我们的活动中。如果它已存在,我们只需连接到它。当标签被取消选择时,会调用 onTabUnselected(Tab tab, FragmentTransaction ft)
方法。在这个方法中,我们分离片段,因为它不应该被显示。当标签被重新选择时,会调用 onTabReselected(Tab tab, FragmentTransaction ft)
方法。在这个方法中我们不执行任何操作。在 Chapter1ActionBarTabActivity
类中,我们创建并设置操作栏。我们活动的布局只有一个 LinearLayout
布局,我们使用片段作为用户界面。首先,我们将操作栏的导航模式设置为 ActionBar.NAVIGATION_MODE_TABS
,因为我们需要标签导航。然后我们创建两个标签,设置它们的 TabListener
事件,并将它们添加到 操作栏
实例中。当你运行应用程序时,你会看到两个标签,名为 FIRST TAB 和 SECOND TAB。第一个标签将显示 Fragment A,第二个标签将显示 Fragment B。屏幕将如下所示:
重要的是不要忘记将最低 SDK 级别设置为 API 级别 11 或更高,因为操作栏是在 API 级别 11 中引入的。
总结
在本章中,你学习了如何使用操作栏,因为这种方法比使用选项菜单更为一致。你还了解了如何使用 ActionProvider 在操作栏中创建自定义布局。你学习了如何使用 ShareActionProvider,以及它如何在你的应用中实现分享的有效方式。你学习了如何使用操作视图以及如何使其可折叠。最后,你学习了如何使用操作栏进行标签导航。它具有适应设备屏幕尺寸的优点,因此使用操作栏比使用较旧的 API 更好。在下一章中,我们将学习一个名为 GridLayout 的 Android 布局,并了解如何添加和配置它。
第二章:新布局——GridLayout
随着 Android Ice Cream Sandwich 的推出,引入了一种新的布局,称为GridLayout。这个布局是一个优化的布局,可以代替LinearLayout和RelativeLayout。本章展示了如何使用和配置 GridLayout。
本章涵盖的主题包括:
-
为什么使用 GridLayout
-
添加一个 GridLayout
-
配置 GridLayout
GridLayout
GridLayout是一种将其视图空间划分为行、列和单元格的布局。GridLayout 自动在其中放置视图,但也可以定义列和行索引来在 GridLayout 中放置视图。通过单元格的跨度属性,可以使一个视图跨越多行或多列。下面的代码块展示了一个使用GridLayout
布局的示例布局文件:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
android:id="@+id/GridLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnCount="2"
android:orientation="horizontal" android:rowCount="2">
<TextView
android:id="@+id/textView1"
android:text="Cell 1,1"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/textView2"
android:text="Cell 1,2"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/textView3"
android:text="Cell 2,1"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/textView4"
android:text="Cell 2,2"
android:textAppearance="?android:attr/textAppearanceLarge" />
</GridLayout>
当这个布局 XML 在模拟器中查看时,它将如下截图所示:
在这个布局 XML 文件中,我们放置了四个TextView
组件,文本分别为Cell 1,1
、Cell 1,2
、Cell 2,1
和Cell 2,2
。将 GridLayout 的orientation
设置为horizontal
,并且columnCount
和rowCount
属性设置为2
,GridLayout 首先自动将项目放置在第一行,当项目数量达到columnCount
时,它开始将项目放置在第二行。
你在这个布局中首先会注意到的就是TextView
组件没有layout_width
和layout_height
属性。这些属性没有被使用,因为GridLayout
使用layout_gravity
属性来确定单元格的大小,而不是layout_width
和layout_height
属性。通常gravity
属性用于对齐视图的内容,但在GridLayout
中它被用于不同的目的。可用的重力常数包括left
、top
、right
、bottom
、center_horizontal
、center_vertical
、center
、fill_horizontal
、fill_vertical
和fill
。
在GridLayout
中,你可以通过指定列和行的索引明确地定义一个视图将被放置的单元格。如果没有指定索引,GridLayout
会根据GridLayout
布局的取向自动将视图放置在第一个可用的位置。
为什么使用 GridLayout
LinearLayout和RelativeLayout是 Android 用户界面设计中使用最普遍的布局。对于简单的用户界面,它们是一个不错的选择,但是当用户界面变得复杂时,嵌套 LinearLayout 的使用往往会增加。嵌套布局(任何类型)可能会影响性能,而且嵌套超过 10 层的 LinearLayout 可能会导致应用程序崩溃。因此,你应该避免使用过多的嵌套 LinearLayout 块,或者使用 RelativeLayout 以减少嵌套 LinearLayout 块。这些布局对于复杂用户界面的另一个缺点是可读性差。维护具有许多视图的嵌套 LinearLayout 或 RelativeLayout 布局很困难。在这些情况下,使用GridLayout是一个不错的选择。通过使用 GridLayout,可以避免使用过多的嵌套 LinearLayout。此外,维护 GridLayout 要容易得多。许多使用 LinearLayout、RelativeLayout 或TableLayout的用户界面可以转换为 GridLayout,其中 GridLayout 将提供性能提升。与其他布局相比,GridLayout 的主要优势之一是你可以控制视图在水平和垂直轴上的对齐方式。
添加一个 GridLayout
在本节中,我们将把一个 Android 应用程序从LinearLayout迁移到GridLayout。使用LinearLayout
的应用程序的布局 XML 代码如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" android:background="#ffffff">
<!-- we used 3 nested LinearLayout-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<!—LinearLayout that contains labels-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username:"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#000000" android:layout_gravity="right"/>
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Password:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="#000000" />
</LinearLayout>
<!—Linearlayout that contains fields-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/borders_bottom_right"
android:ems="10" >
</EditText>
<EditText
android:id="@+id/editText2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/borders_bottom_right"
android:ems="10" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OK" android:layout_gravity="center_horizontal"/>
</LinearLayout>
在前一个布局文件中使用的 drawable borders_bottom_right
背景如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<layer-list >
<item>
<shape android:shape="rectangle">
<stroke android:width="1dp" android:color="#FFFFFF" />
<solid android:color="#000000" />
</shape>
</item>
</layer-list>
屏幕将如下所示:
正如你在布局 XML 代码中看到的,我们使用了三个嵌套的LinearLayout
实例来实现一个简单的登录屏幕。如果这个屏幕是用GridLayout
设计的,布局的 XML 代码将如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff"
android:columnCount="2"
android:orientation="horizontal" >
<TextView
android:id="@+id/textView1"
android:layout_gravity="right"
android:text="Username:" android:textColor="#000000"/>
<EditText
android:id="@+id/editText1"
android:ems="10" android:background="@drawable/borders"/>
<TextView
android:id="@+id/textView2"
android:text="Password:"
android:textAppearance="?android:attr/textAppearanceLarge" android:textColor="#000000"/>
<EditText
android:id="@+id/editText2"
android:ems="10" android:background="@drawable/borders">
</EditText>
<Button
android:id="@+id/button1"
android:layout_columnSpan="2"
android:text="Button" android:layout_gravity="center_horizontal"/>
</GridLayout>
我们将columnCount
属性设置为2
,因为我们在一行中有一个TextView
组件和一个EditText
组件。之后我们放置了视图,并没有指定行或列的索引。GridLayout会根据orientation
和columnCount
自动放置它们。我们将layout_columnSpan
属性设置为2
,以使按钮跨越两列。通过layout_gravity
属性,我们使按钮在行的中心显示。正如你在布局的 XML 代码中看到的,使用 GridLayout 设计相同的屏幕非常简单和容易。此外,GridLayout 的对其方式更加简单,并且代码的可读性更好。
GridLayout
从 API 级别 14 开始可用,因此在AndroidManifest.xml
文件中,最低 SDK 属性应设置为14
或更高,如下代码行所示:
<uses-sdkandroid:minSdkVersion="14" />
配置 GridLayout
首先,我们将编写一个示例GridLayout
XML 代码,然后我们将使用此代码作为其他示例的基础。示例布局的 XML 代码将如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:columnCount="3"
android:rowCount="3" >
<TextView
android:text="[1,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[1,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[1,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[2,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[2,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[2,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</GridLayout>
使用前面的代码块,屏幕将如下所示:
如你在布局 XML 代码中所见,TextView
组件没有索引地放置,并根据orientation
、columnCount
和rowCount
在GridLayout
中自动定位。现在我们将[1, 3]
的索引编号设置为[2, 1]
。布局 XML 代码应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:columnCount="3"
android:rowCount="3" >
<TextView
android:text="[1,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[1,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<!-- set the row and column index with layout_row and layout_column
properties-->
<TextView
android:text="[1,3]"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_row="1" android:layout_column="1"/>
<TextView
android:text="[2,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[2,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[2,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</GridLayout>
屏幕应如下所示:
如你在布局 XML 代码(高亮部分)中所见,我们使用layout_row
和layout_column
属性设置行和列索引。索引是基于零的,因此带有文本[1, 3]
的TextView
组件被放置在第二行和第二列。这里有趣的部分是,带有文本[2, 1]
的TextView
组件被放置在[1, 3]
之后。这是因为[2, 1]
没有索引,GridLayout在[1, 3]
之后继续定位。这就是GridLayout在最后一个放置视图之后寻找第一个可用位置的方式。另一个值得注意的是,在移动索引后,行数增加到了4
,尽管我们设置了行数为3
。GridLayout在这种情况下不会抛出异常。
在以下示例中,我们将交换[1, 2]
和[2, 2]
。布局 XML 代码应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:columnCount="3"
android:rowCount="3" >
<TextView
android:text="[1,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<!-- set layout_row of [1, 2] to 1-->
<TextView
android:text="[1,2]"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_row="1"/>
<!-- set layout_row of [1, 2] to 1-->
<TextView
android:text="[1,3]"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_row="0"/>
<TextView
android:text="[2,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<!-- set the layout_row of [2, 2] to 0 and layout_column to 1-->
<TextView
android:text="[2,2]"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_row="0" android:layout_column="1"/>
<!-- set layout_row of [2, 3] to 1 in order to make it appear after [1,2]'s
new position-->
<TextView
android:text="[2,3]"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_row="1"/>
<TextView
android:text="[3,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</GridLayout>
屏幕应如下所示:
如你在布局 XML 代码中所见,我们首先将[1, 2]
的layout_row
设置为1
。这样,它将出现在[2, 2]
的位置。然后,我们必须将[1, 3]
的layout_row
设置为0
,将layout_column
设置为2
,因为GridLayout
的游标位置通过设置[1, 2]
的索引而改变了。如果我们不改变[1, 3]
的索引,它将被放置在[1, 2]
索引新位置的后面。之后,为了使[2, 2]
出现在[1, 2]
的位置,我们将[2, 2]
的layout_row
设置为0
,将layout_column
设置为1
。最后,我们必须将[2, 3]
的layout_row
设置为1
,以使其出现在[1, 2]
索引新位置的后面。在GridLayout中配置视图看起来有点复杂,但如果你在模拟器中尝试,你会发现这并不那么困难。
在以下示例代码中,我们将删除[2, 2]
并将[1, 2]
设置为跨越两行。布局 XML 代码应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:columnCount="3"
android:rowCount="3" >
<TextView
android:text="[1,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<!-- set layout_rowSpan property of [1,2] to 2\. By this way [1,2] will
cover 2 rows.-->
<TextView
android:text="[1,2]"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_rowSpan="2" android:layout_gravity="fill"
android:gravity="center"/>
<TextView
android:text="[1,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[2,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[2,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</GridLayout>
使用前面的代码块,我们得到以下屏幕:
如你在布局 XML 代码中所见,我们删除了单元格[2,2]
并将[1,2]
的layout_rowSpan
属性设置为2
。这样,[1,2]
将覆盖两行。我们将layout_gravity
属性设置为fill
,以使其填充两行的空间。然后我们将gravity
属性设置为center
,以使TextView
组件的内容对其覆盖的空间中心对齐。`
`# 一个新的视图 - 空间
Space是 Android Ice Cream Sandwich 中引入的一个新视图。它用于在视图之间放置空间。在GridLayout中它非常有用。在以下示例布局 XML 代码中,我们移除了带有文本[2, 2]
和[2, 3]
的TextView
组件,然后用Space替换它们,如下面的代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:columnCount="3"
android:rowCount="3" >
<TextView
android:text="[1,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[1,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[1,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[2,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<Space
android:layout_row="1"
android:layout_column="1"
android:layout_columnSpan="2"
android:layout_gravity="fill"/>
<TextView
android:text="[3,1]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,2]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:text="[3,3]"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</GridLayout>
如你在布局 XML 代码中所见,我们移除了带有文本[2,2]
和[2,3]
的TextView
组件。我们在第1
行和第1
列放置了一个Space
视图。我们将layout_columnSpan
设置为2
,以使其跨越两列。屏幕将看起来像下面这样:
总结
LinearLayout和RelativeLayout是 Android 应用开发中最常见的布局。然而,在设计复杂的用户界面时,你可能需要使用嵌套的 LinearLayout 或 RelativeLayout。这对你的代码性能和可读性是一个缺点,因为这些布局增加了视图层次结构,导致在视图刷新时进行不必要的迭代。GridLayout是 Android Ice Cream Sandwich 中引入的一种新布局,它克服了这些问题。你可以设计用户界面,而无需嵌套布局。如果你正在为 API 级别 14 及以上的版本开发应用,使用 GridLayout 会更好。在下一章中,我们将学习到 Android Ice Cream Sandwich 引入的新社交 APIs。`
第三章:社交 API
随着 Android Ice Cream Sandwich 的推出,引入了新的社交 API,这个 API 使得集成社交网络变得简单。此外,在 Android Ice Cream Sandwich 发布后,可以使用高分辨率照片作为联系人照片。本章通过示例展示了社交 API 的使用。
本章涵盖的主题如下:
-
Android 中的联系人基础
-
使用社交 API
Android 中的联系人基础
一个人可能有多个联系人信息详情。在 Android 中,这些多个联系人信息详情被合并并显示为一个联系人详情。例如;一个人可能有一个 Google+联系人,一个 Skype 联系人,以及一个电话联系人,Android 将这些联系人全部合并为一个联系人。这些联系人的每个来源都是RawContact。每个 RawContact 有一个或多个数据行,它们保存有关联系人的某些数据,如电话号码、电子邮件等。参考以下框图可以更好地理解它们之间的关系:
每个 RawContact 在 Android Ice Cream Sandwich 中都支持存储社交网络流——文本和照片。每个 RawContact 都与StreamItems相关联,其中包含来自社交媒体更新的文本、时间戳和评论,如 Google+,每个 StreamItem 都与包含照片的StreamItemPhotos相关联(如 Google+帖子中的照片)。然而,存储在 RawContact 中的 StreamItems 数量是有限制的。这个数字可以通过查询StreamItems.CONTENT_LIMIT_URI URI
获取。当数量超过限制时,将移除时间戳最旧的流项目。以下框图描述了这些块之间的关系:
使用社交 API
在以下示例中,我们将展示如何添加StreamItem,以及如何显示已添加的 StreamItems。首先,我们在用户界面中插入了两个按钮,一个用于触发插入操作,另一个用于列出 StreamItems。为了显示 StreamItems,我们在布局中放置了三个TextView
组件。布局 XML 应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<!-- we put two buttons to the user interface, one for triggering insert and one for listing stream items-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/buttonInsert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Insert" />
<Button
android:id="@+id/buttonList"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="List" />
</LinearLayout>
<!-- In order to display stream items, we put three TextViews to the layout-->
<TextView
android:id="@+id/txt1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:id="@+id/txt2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/txt3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
我们将逐步实现Activity
类,首先添加一个联系人,然后添加StreamItems
并显示它们。带有onCreate()
方法的Activity
类如下代码块所示:
package com.chapter3;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.StreamItemPhotos;
import android.provider.ContactsContract.StreamItems;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class Chapter3_1Activity extends Activity implements OnClickListener {
Button insertButton;
Button listButton;
Button chooseButton;
TextView txt1;
TextView txt2;
TextView txt3;
long rawContactId;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//initialize UI components
insertButton = (Button) this.findViewById(R.id.buttonInsert);
insertButton.setOnClickListener(this);
listButton = (Button) this.findViewById(R.id.buttonList);
listButton.setOnClickListener(this);
txt1 = (TextView) this.findViewById(R.id.txt1);
txt2 = (TextView) this.findViewById(R.id.txt2);
txt3 = (TextView) this.findViewById(R.id.txt3);
}
@Override
public void onClick(View v) {
// when the insert button is clicked, addContact method // is called
if (v == insertButton)
this.rawContactId = addContact("Murat Aydın", "9999999",
"maydin@gmail.com", "Murat", "com.google");
else if (v == listButton) {
getStreams(this.rawContactId);
}
}
}
如您在此代码中看到的,我们首先在onCreate(Bundle savedInstanceState)
方法中获取布局中的Button
和TextView
实例。Chapter3_1Activity
类为按钮实现了OnClickListener
。如您在onClick(View v)
方法中看到的,当点击Insert按钮时,会调用addContact()
方法。addContact()
方法定义如下:
public long addContact(String name, String phone, String email,
String accountName, String accountType) {
// firstly a raw contact is created with the // addRawContact method
Uri rawContactUri = addRawContact(accountName, accountType);
if (rawContactUri != null) {
long rawContactId = ContentUris.parseId(rawContactUri);
// we use the ID of the created raw contact in // creating name, email, phone number and stream // items
addName(name, rawContactId);
addPhoneNumber(phone, rawContactId);
addEmail(email, rawContactId);
addContactStreamItem(rawContactId, accountName,
accountType, "Social Media Update 1");
addContactStreamItem(rawContactId, accountName,
accountType, "Social Media Update 2");
addContactStreamItem(rawContactId, accountName,
accountType, "Social Media Update 3");
return rawContactId;
}
return 0;
}
在addContact()
方法中,首先使用addRawContact()
方法创建一个 RawContact。在addRawContact()
方法中,我们使用accountName
和accountType
来创建原始联系人。addRawContact()
方法的定义如下:
public Uri addRawContact(String accountName, String accountType) {
// we use account name and type to create a raw contact
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(RawContacts.ACCOUNT_TYPE, accountType);
values.put(RawContacts.ACCOUNT_NAME, accountName);
Uri rawContactUri = cr.insert(RawContacts.CONTENT_URI, values);
return rawContactUri;
}
创建原始联系人后,我们使用创建的原始联系人 ID 来创建姓名、电子邮件、电话号码和 StreamItems。addName()
,addEmail()
和addPhoneNumber()
方法使用ContentValues
类来创建姓名、电子邮件和电话号码数据,如下代码块所示:
// This method is for creating email data
private void addEmail(String email, long rawContactId) {
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(Email.ADDRESS, email);
values.put(Email.TYPE, Email.TYPE_OTHER);
values.put(Email.MIMETYPE, Email.CONTENT_ITEM_TYPE);
values.put(Data.RAW_CONTACT_ID, rawContactId);
cr.insert(Data.CONTENT_URI, values);
}
//This method is for creating phone number data
private void addPhoneNumber(String phone, long rawContactId) {
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(Phone.NUMBER, phone);
values.put(Phone.TYPE, Phone.TYPE_OTHER);
values.put(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
values.put(Data.RAW_CONTACT_ID, rawContactId);
cr.insert(Data.CONTENT_URI, values);
}
//This method is for adding name data
private void addName(String name, long rawContactId) {
ContentValues values = new ContentValues();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
values.put(StructuredName.DISPLAY_NAME, name);
getContentResolver().insert(Data.CONTENT_URI, values);
}
在addContactStreamItem()
方法中,我们创建 StreamItems。我们提供了原始联系人 ID、StreamItem 的文本、StreamItem 创建时的时间戳(毫秒)、账户名称和类型来创建 StreamItems。原始联系人 ID、账户名称和类型是创建 StreamItem 所需的必填字段。addContactStreamItem()
方法的定义如下:
//StreamItems are created in this method
private long addContactStreamItem(long rawContactId, String accountName,
String accountType, String text) {
// Raw contact ID, account name and type are required // fields for creating a stream item.
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(StreamItems.RAW_CONTACT_ID, rawContactId);
values.put(StreamItems.TEXT, text);
values.put(StreamItems.TIMESTAMP, Calendar.getInstance().getTime()
.getTime());
Uri.Builder builder = StreamItems.CONTENT_URI.buildUpon();
builder.appendQueryParameter(RawContacts.ACCOUNT_NAME,
accountName);
builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE,
accountType);
Uri streamItemUri = cr.insert(builder.build(), values);
long streamItemId = ContentUris.parseId(streamItemUri);
addContactStreamPhoto(streamItemId, accountName, accountType);
return streamItemId;
}
addContactStreamPhoto()
方法用于为 StreamItem 创建 StreamItemPhotos。我们必须提供二进制格式的照片,或者PHOTO_FILE_ID
,或者PHOTO_URI
。正如以下代码块所示,我们使用了loadPhotoFromResource
和readInputStream
方法从资源中加载并创建二进制照片。我们还提供了 StreamItem ID、排序索引、账户名称和类型以创建流照片。如果我们不提供排序索引,将使用 ID 列进行排序。addContactStreamPhoto()
方法的定义如下:
//This method is used for creating a stream photo for a stream item
private long addContactStreamPhoto(long streamItemId,String accountName,
String accountType) {
// provide stream item ID, sort index, account name and type for creating a stream photo
ContentValues values = new ContentValues();
values.put(StreamItemPhotos.STREAM_ITEM_ID, streamItemId);
values.put(StreamItemPhotos.SORT_INDEX, 1);
values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(R.drawable.ic_launcher));
Uri.Builder builder = StreamItems.CONTENT_PHOTO_URI.buildUpon();
builder.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName);
builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType);
Uri photoUri = getContentResolver().insert(builder.build(), values);
long photoId = ContentUris.parseId(photoUri);
return photoId;
}
//This method is used for creating a photo in binary
private byte[] loadPhotoFromResource(int resourceId) {
InputStream is = getResources().openRawResource(resourceId);
return readInputStream(is);
}
private byte[] readInputStream(InputStream is) {
try {
byte[] buffer = new byte[is.available()];
is.read(buffer);
is.close();
return buffer;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
当点击列表按钮时,会调用getStreams()
方法。正如以下代码所示,在getStream()
方法中,我们首先使用getContactId()
方法获取原始联系人的contactId
详情。然后我们使用这个联系人 ID 作为查询参数来查询 StreamItems。由于我们查询的是 StreamItems,因此使用ContactsContract.StreamItems.CONTENT_URI
作为 URI。最后,使用游标检索 StreamItems,并在 TextViews 中显示 StreamItems 的文本。getStreams()
方法和getContactId()
方法的定义如下:
public void getStreams(long rawContactId) {
long contactId = getContactId(rawContactId);
ContentResolver cr = getContentResolver();
Cursor pCur = cr.query(ContactsContract.StreamItems.CONTENT_URI,
null,
ContactsContract.StreamItems.CONTACT_ID + " = ?",
new String[] { String.valueOf(contactId) }, null);
int i = 0;
if (pCur.getCount() > 0) {
while (pCur.moveToNext()) {
String text = pCur.getString(pCur
.getColumnIndex(ContactsContract.StreamItems.TEXT));
if (i == 0)
this.txt1.setText(text);
else if (i == 1)
this.txt2.setText(text);
else if (i == 2)
this.txt3.setText(text);
i++;
}
}
pCur.close();
}
public long getContactId(long rawContactId) {
Cursor cur = null;
try {
cur = this.getContentResolver().query(
ContactsContract.RawContacts.CONTENT_URI,
new String[] {
ContactsContract.RawContacts.CONTACT_ID },
ContactsContract.RawContacts._ID + "=" + rawContactId, null, null);
if (cur.moveToFirst()) {
return cur
.getLong(cur
.getColumnIndex(ContactsContract.RawContacts.CONTACT_ID));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cur != null) {
cur.close();
}
}
return -1l;
}
最后,我们需要一些权限来读取和写入社交流和联系人:READ_SOCI
AL_STREAM
,WRITE_SOCIAL_STREAM, READ_CONTACTS
和WRITE_CONTACTS
。此外,为了使用社交 API,我们必须将最小 SDK 设置为 API Level 15。AndroidManifest.xml
文件应如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.chapter3"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="15" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_SOCIAL_STREAM" />
<uses-permission android:name="android.permission.WRITE_SOCIAL_STREAM" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".Chapter3_1Activity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
当我们在模拟器中执行应用程序时,点击插入按钮,然后点击列表按钮;屏幕将如下所示:
当您在模拟器中执行人员
应用时,您会看到名为Murat Aydın的联系人已创建,如下截图所示:
您还将看到我们以编程方式创建的包含照片的最新社交网络更新:
设备用户资料
从 API 级别 14 开始,Android 将在联系人列表顶部显示设备用户资料,标记为我,如下屏幕所示:
可以使用 ContactsContract.Profile.CONTENT_URI
和 ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI
URI 来读取和写入设备用户资料。操作与读取和写入联系人相似,但需要在 AndroidManifest.xml
文件中拥有 READ_PROFILE
和 WRITE_PROFILE
权限。
总结
新的社交 API 随 Android Ice Cream Sandwich 一同引入,使得联系人和社会网络的整合变得更加简单。StreamItems
和 StreamItemPhotos
类用于存储要保存在联系人中的社交网络更新。在本章中,我们学习了如何使用这些类。此外,我们还学习了设备用户资料,它显示设备用户联系信息。
Android Ice Cream Sandwich 引入了新的 API 来管理日历。在下一章中,我们将学习新的日历 API 及其使用方法。
第四章:日历 API
Android Ice Cream Sandwich 引入了新的日历 API,用于管理日历。这些 API 可以管理事件、参与者、提醒和重复事件的数据库。这些 API 允许我们轻松地将日历与我们的 Android 应用程序集成。本章展示了如何使用示例使用日历 API。
本章涵盖的主题如下:
-
使用日历 API
-
创建一个事件
-
添加参与者
-
添加一个提醒
使用日历 API
管理日历数据的主要类是CalendarContract
类。值得注意的存储日历信息的表格如下:
-
CalendarContract.Calendar
:这个表格为每个日历存储特定的日历数据 -
CalendarContract.Event
:这个表格为每个事件存储特定的事件数据 -
CalendarContract.Attendee
:这个表格存储了事件参与者的数据 -
CalendarContract.Reminder
:这个表格存储了事件提醒的数据
备注
在以下示例中,我们将在 Android 设备上执行应用程序,因为在模拟器中测试日历 API 需要一个账户。如果你想在模拟器中测试示例,确保在创建AVD时选择Google API的 API 级别 14 或更高。Google API 允许你向模拟器添加一个 Google 账户,这对于日历 API 是必需的。你还需要设置日历与 Gmail 同步。添加账户时,可以使用[m.google.com](http://m.google.com)
作为服务器和<your_email@gmail.com>
作为域/用户名。创建并同步账户后,你可以在模拟器中运行以下示例。
创建一个事件
为了创建一个日历事件,我们需要创建一个ContentValues
实例并将事件信息放入这个实例中。然后,使用ContentResolver
类,我们可以将事件信息插入到日历中。插入日历中的事件需要一些必填字段。这些字段如下:
-
事件的开始时间
-
如果事件不是重复的,其结束时间
-
如果事件是重复的,其重复规则或重复日期
-
如果事件是重复的,其持续时间
-
事件时区和日历 ID
插入事件的Activity
类定义如下:
package com.chapter4;
import java.util.Calendar;
import java.util.TimeZone;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Bundle;
import android.content.ContentUris;
import android.net.Uri;
import android.provider.CalendarContract;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class Chapter4_1Activity extends Activity implements OnClickListener {
Button insertButton;
long calendarID;
long eventID;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
insertButton = (Button)this.findViewById(R.id.buttonInsertEvent);
insertButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
addEvent();
}
private void addEvent() {
calendarID = getCalendarID();
ContentValues eventValues = new ContentValues ();
// provide the required fields for creating an event to
// ContentValues instance and insert event using // ContentResolver
eventValues.put (CalendarContract.Events.CALENDAR_ID,calendarID);
eventValues.put (CalendarContract.Events.TITLE,"Event 1");
eventValues.put (CalendarContract.Events.DESCRIPTION,
"Testing Calendar API");
eventValues.put
(CalendarContract.Events.DTSTART,Calendar.getInstance().getTimeInMillis());
eventValues.put
(CalendarContract.Events.DTEND,Calendar.getInstance().getTimeInMillis());
eventValues.put(CalendarContract.Events.EVENT_TIMEZONE,
TimeZone.getDefault().toString());
Uri eventUri = this.getContentResolver().insert
(CalendarContract.Events.CONTENT_URI, eventValues);
eventID = ContentUris.parseId(eventUri);
}
// we use this method in order to get the ID of the calendar because
// calendar ID is a required field in creating an event
public long getCalendarID() {
Cursor cur = null;
try {
// provide CalendarContract.Calendars.CONTENT_URI to
// ContentResolver to query calendars
cur = this.getContentResolver().query(
CalendarContract.Calendars.CONTENT_URI,
null,null,null, null);
if (cur.moveToFirst()) {
return cur
.getLong(cur
.getColumnIndex(CalendarContract.Calendars._ID));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cur != null) {
cur.close();
}
}
return -1L;
}
}
如你所见,在这段代码中,我们使用getCalendarID()
方法来获取日历的 ID,因为calendarID
在创建事件时是一个必填字段。我们向ContentResolver
提供了CalendarContract.Calendars.CONTENT_URI
以查询日历。
我们使用了按钮点击事件来添加一个事件。点击这个按钮时,我们调用addEvent()
方法。在addEvent()
方法中,我们为创建事件的必要字段提供给ContentValues
实例,并使用ContentResolver
插入事件。我们向ContentResolver
提供CalendarContract.Events.CONTENT_URI
以添加一个事件。
本应用程序布局的 XML 代码是LinearLayout
,包含一个Button
组件,如下代码块所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="@+id/buttonInsertEvent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="insert event" />
</LinearLayout>
执行此代码时,屏幕将如下所示:
为了使用新的日历 API,AndroidManifest.xml
文件中的最低 SDK 版本应设置为 API 级别 14 或更高。此外,还需要WRITE_CALENDAR
和READ_CALENDAR
权限才能读写日历。AndroidManifest.xml
文件应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.chapter4"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="14" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".Chapter4_1Activity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
创建事件后,日历将如下所示:
使用意图创建事件
同样可以使用Intent
对象创建事件。以下方法展示了如何使用Intent
对象添加事件:
private void addEventUsingIntent() {
calendarID = getCalendarID();
Intent intent = new Intent(Intent.ACTION_INSERT)
.setData(CalendarContract.Events.CONTENT_URI)
.putExtra(CalendarContract.Events.DTSTART,
Calendar.getInstance().getTimeInMillis())
.putExtra(CalendarContract.Events.DTEND,
Calendar.getInstance().getTimeInMillis())
.putExtra(CalendarContract.Events.TITLE,"Event 1")
.putExtra(CalendarContract.Events.DESCRIPTION,"Testing Calendar API");
startActivity(intent);
}
我们可以调用这个方法,而不是addEvent()
方法,以使用Intent
对象创建事件。通过使用Intent
对象,我们无需创建视图即可创建事件。使用Intent
对象是修改和显示日历的最佳实践。
添加参与者
添加参与者的过程与创建事件类似。我们使用CalendarContract.Attendees.CONTENT_URI
作为插入参与者的 URI。插入参与者所需的字段包括事件 ID、参与者电子邮件、参与者关系、参与者状态和参与者类型。我们在应用程序的 XML 布局中放置了一个Button
组件。结果布局如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="@+id/buttonInsertEvent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="insert event" />
<Button
android:id="@+id/buttonInsertAttendee"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="insert attendee" />
</LinearLayout>
然后我们在点击添加参与者按钮时调用以下方法:
private void addAttendee()
{
ContentValues cv = new ContentValues();
cv.put(Attendees.ATTENDEE_NAME, "Murat AYDIN");
cv.put(Attendees.ATTENDEE_EMAIL, "maydin@gmail.com");
cv.put(Attendees.EVENT_ID, eventID);
cv.put(Attendees.ATTENDEE_RELATIONSHIP,
Attendees.RELATIONSHIP_ATTENDEE);
cv.put(Attendees.ATTENDEE_STATUS,
Attendees.ATTENDEE_STATUS_INVITED);
cv.put(Attendees.ATTENDEE_TYPE,Attendees.TYPE_OPTIONAL);
this.getContentResolver().insert(CalendarContract.Attendees.CONTENT_URI,
cv);
}
在点击添加参与者按钮之前,应该先创建一个事件,因为我们在插入参与者时需要使用事件 ID。
添加提醒
我们使用CalendarContract.Reminder.CONTENT_URI
作为插入事件提醒的 URI。插入提醒所需的字段包括事件 ID、提醒需要在事件前触发的分钟数以及方法。我们在应用程序的 XML 布局中放置了一个Button
组件。结果布局如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="@+id/buttonInsertEvent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="insert event" />
<Button
android:id="@+id/buttonInsertAttendee"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="insert attendee" />
<Button
android:id="@+id/buttonInsertReminder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="insert reminder" />
</LinearLayout>
然后我们在点击添加提醒按钮时调用以下方法:
private void addReminder()
{
ContentValues values = new ContentValues();
values.put(Reminders.MINUTES, 15);
values.put(Reminders.EVENT_ID, eventID);
values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
this.getContentResolver().insert(CalendarContract.Reminders.CONTENT_URI,
values);
}
如你所见,在这段代码中,这个提醒会在事件发生前 15 分钟触发。在按下添加提醒按钮之前,应该先创建一个事件,因为我们在插入提醒时需要使用事件 ID。
添加提醒后,日历将如下所示:
概要
使用新的日历 API,将日历集成到 Android 应用程序变得更加容易。在本章中,我们学习了如何创建一个事件以及创建事件所需的字段。然后我们了解了如何向事件中添加参与者和提醒。我们需要设置修改日历所需的权限。
尽管片段(Fragments)是在 Android 3.0 中引入的,但现在它们也适用于配备 Android Ice Cream Sandwich 的小屏幕设备。在下一章中,我们将介绍片段的基础知识以及如何使用它们。