用于应用开发的安卓 4 新特性(一)

原文:zh.annas-archive.org/md5/37F309F5583A3BFE9D4DF14FC6F7D1A9

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

本书是一本实用的、手把手指导开发 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 开发者;这本书也适合你。

约定

在这本书中,你会发现多种文本样式,用以区分不同类型的信息。以下是一些样式示例,以及它们含义的解释。

文中的代码字显示如下:“实现 onCreateOptionsMenuonOptionsItemSelected 方法。”

代码块设置如下:

<?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>

实现onCreateOptionsMenuonOptionsItemSelected方法,如下代码块所示,以显示菜单项:

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 布局中。ImageButtononClickListener() 事件在 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>

使用ShareActionProviderActivity类应该如下代码块所示:

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属性。然后我们使用ShareActionProvidersetShareIntent方法定义分享的意图。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>

添加一个动作视图

动作视图是出现在操作栏中的用户界面组件,而不是操作按钮。这个视图是可以折叠的,即如果它被配置为可折叠,意味着在按下动作按钮时会展开。如果没有配置为可折叠,默认会展开显示。在以下示例中,我们添加了一个动作视图,并展示了其事件以及如何处理这些事件。

首先,添加一个动作视图的布局,其中包含三个带有文本LargeMediumSmall的按钮,如下代码块所示:

<?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()方法以编程方式展开和折叠动作视图。正如在Chapter1ActionViewActivityonClick(View v)方法中看到的那样,我们使用collapseActionView()方法手动折叠了动作视图。

当动作视图展开或折叠时,你可以使用OnActionExpandListener类执行一个动作。正如代码中看到的,我们定义了实现OnActionExpandListenerChapter1ActionListener类。我们重写了这个类的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 TABSECOND TAB。第一个标签将显示 Fragment A,第二个标签将显示 Fragment B。屏幕将如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

重要的是不要忘记将最低 SDK 级别设置为 API 级别 11 或更高,因为操作栏是在 API 级别 11 中引入的。

总结

在本章中,你学习了如何使用操作栏,因为这种方法比使用选项菜单更为一致。你还了解了如何使用 ActionProvider 在操作栏中创建自定义布局。你学习了如何使用 ShareActionProvider,以及它如何在你的应用中实现分享的有效方式。你学习了如何使用操作视图以及如何使其可折叠。最后,你学习了如何使用操作栏进行标签导航。它具有适应设备屏幕尺寸的优点,因此使用操作栏比使用较旧的 API 更好。在下一章中,我们将学习一个名为 GridLayout 的 Android 布局,并了解如何添加和配置它。

第二章:新布局——GridLayout

随着 Android Ice Cream Sandwich 的推出,引入了一种新的布局,称为GridLayout。这个布局是一个优化的布局,可以代替LinearLayoutRelativeLayout。本章展示了如何使用和配置 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,1Cell 1,2Cell 2,1Cell 2,2。将 GridLayout 的orientation设置为horizontal,并且columnCountrowCount属性设置为2,GridLayout 首先自动将项目放置在第一行,当项目数量达到columnCount时,它开始将项目放置在第二行。

你在这个布局中首先会注意到的就是TextView组件没有layout_widthlayout_height属性。这些属性没有被使用,因为GridLayout使用layout_gravity属性来确定单元格的大小,而不是layout_widthlayout_height属性。通常gravity属性用于对齐视图的内容,但在GridLayout中它被用于不同的目的。可用的重力常数包括lefttoprightbottomcenter_horizontalcenter_verticalcenterfill_horizontalfill_verticalfill

GridLayout中,你可以通过指定列和行的索引明确地定义一个视图将被放置的单元格。如果没有指定索引,GridLayout会根据GridLayout布局的取向自动将视图放置在第一个可用的位置。

为什么使用 GridLayout

LinearLayoutRelativeLayout是 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会根据orientationcolumnCount自动放置它们。我们将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组件没有索引地放置,并根据orientationcolumnCountrowCountGridLayout中自动定位。现在我们将[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_rowlayout_column属性设置行和列索引。索引是基于零的,因此带有文本[1, 3]TextView组件被放置在第二行和第二列。这里有趣的部分是,带有文本[2, 1]TextView组件被放置在[1, 3]之后。这是因为[2, 1]没有索引,GridLayout[1, 3]之后继续定位。这就是GridLayout在最后一个放置视图之后寻找第一个可用位置的方式。另一个值得注意的是,在移动索引后,行数增加到了4,尽管我们设置了行数为3GridLayout在这种情况下不会抛出异常。

在以下示例中,我们将交换[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,以使其跨越两列。屏幕将看起来像下面这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

总结

LinearLayoutRelativeLayout是 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)方法中获取布局中的ButtonTextView实例。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()方法中,我们使用accountNameaccountType来创建原始联系人。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。正如以下代码块所示,我们使用了loadPhotoFromResourcereadInputStream方法从资源中加载并创建二进制照片。我们还提供了 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_STREAMWRITE_SOCIAL_STREAM, READ_CONTACTSWRITE_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_URIContactsContract.Profile.CONTENT_RAW_CONTACTS_URI URI 来读取和写入设备用户资料。操作与读取和写入联系人相似,但需要在 AndroidManifest.xml 文件中拥有 READ_PROFILEWRITE_PROFILE 权限。

总结

新的社交 API 随 Android Ice Cream Sandwich 一同引入,使得联系人和社会网络的整合变得更加简单。StreamItemsStreamItemPhotos 类用于存储要保存在联系人中的社交网络更新。在本章中,我们学习了如何使用这些类。此外,我们还学习了设备用户资料,它显示设备用户联系信息。

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_CALENDARREAD_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 的小屏幕设备。在下一章中,我们将介绍片段的基础知识以及如何使用它们。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值