pAdTy_4 构建有联系人和签署的应用程序

2015.12.15 - 12.23
个人英文阅读练习笔记。原文地址:http://developer.android.com/training/building-userinfo.html

12.15
此节描述如何在应用程序中包含联系人信息和用户认证(跟用户使用的Google账户的凭证相同)。这些特性能够让应用程序将用户和他们所关心的人结合起来且提供了人性化的用户体验(不用创建新的账户)。

12.16

1. 访问联系人数据

此节描述如何使用Android的中央通讯录、联系人提供器,如何展示联系人及他们的细节信息并修改联系人信息。

Contacts Provider是用户联系人信息的中央仓库,包括来自联系人应用程序和社交网络应用程序的数据。在应用程序中,可以直接通过调用ContentResolver方法或通过发送意图到联系人应用程序访问Contacts Provider信息。

此节集中描述检索联系人列表、展示指定联系人的详细信息以及用意图修改联系人。在本节中所描述的技术能够被扩展去执行更复杂的任务。另外,此节还会帮助理解Contacts Provider整体的框架和操作。

1.1 检索联系人清单

描述如何用以下技术检索与数据相匹配或与字符串搜索部分匹配的联系人清单。
- 匹配联系人名称。
- 匹配联系人数据的任何类型。
- 匹配联系人数据的指定类型,诸如电话号码。

匹配联系人名字
根据匹配搜索字符串来匹配联系人所有的信息或匹配联系人的部分信息从而检索出联系人列表。Contacts Provider允许相同名字有多个实例,所以此技术能够返回匹配的列表。

匹配指定的数据类型,如电话号码
根据诸如匹配邮件地址这样指定的数据类型的搜索字符串来检索联系人列表。例如,此技术允许检索到所有匹配搜索字符串的邮件地址。

匹配任何类型
根据可匹配任何数据的搜索字符串来检索联系人列表,包括姓名、电话号码、街道地址、邮件地址等等。例如,此技术允许接受任意类型的搜索字符串然后列表出所有匹配此字符串的数据。

注:本小节中的所有例子都是使用CursorLoader来从Contacts Provider中检索数据。CursorLoader在不同于用户界面线程的另外一个线程中运行它的查询工作。这就确保了此查询不会减慢用户界面的响应时间也不会导致差的用户体验。更多信息见安卓的培训课“ Loading Data in the Background”。

(1) 请求读提供器(Provider)的权限

不管对Contacts Provider做任何类型的搜索,都必须在应用程序的清单文件中请求READ_CONTACTS权限。欲请求此权限,将uses-permission元素增加到清单文件中作为manifest的子元素:

<uses-permission android:name="android.permission.READ_CONTACTS" />

(2) 根据姓名匹配联系人并列表结果

此技术尝试用搜索字符串去匹配联系人姓名或在Contacts Provider的ContactsContract.Contacts表中的联系人。通常需要将匹配结果展示到ListView中,以让用户选择。

[1] 定义ListView和条目布局

欲将搜索结果呈现在ListView中,需要一个主布局文件来定义包括ListView和定义一行ListView的条目布局文件。如可以创建一个主布局文件res/layout/contacts_list_view.xml来包含以下XML内容:

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@android:id/list"
          android:layout_width="match_parent"
          android:layout_height="match_parent"/>

此XML文件使用Android内建的ListView部件android:id/list。

定义条目布局文件contacts_list_item.xml包含以下XML内容:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@android:id/text1"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:clickable="true"/>

此XML文件使用Android内建的TextView部件android:text1。

注:此小节没有描述获取用户搜索字符串的用户界面,因为此字符串的获取可能是间接的。如可给用户一个这样的选择:根据匹配输入的文本信息来搜索联系人。

此两个布局文件定义了显示ListView的用户界面。下一步就应该写代码来使用此用户界面展示联系人列表。

[2] 定义展示联系人列表的碎片

欲展示联系人列表,首先定义一个Activity可载入的Fragment。使用Fragment会更加的灵活,因为可以使用一个Fragment来展示列表,使用第二个Fragment来展示用户从此列表中选择联系人的详细信息。通过使用此方法,可以结合“检索联系人详细信息”一节中呈现的技术。

欲知如何在一个Activity中使用一个Fragment或多个Fragment对象,阅读培训课程“用Fragment构建动态的用户界面”。

欲帮助编写查询Contacts Provider的代码,安卓框架提供了名为ContactsContract的类,此类定义了有用的常量和方法来访问Contacts Provider。当使用此类时,不必为内容URIs自定义常量、表名以及列名。欲使用此类,包含以下语句:

import android.provider.ContactsContract;

因为代码使用CursorLoader从提供器中检索数据,必须指定所实现的载入器接口LoaderManager.LoaderCallbacks。同时,欲帮助检测用户从列表中选择了哪一个联系人,实现适配器接口AdapterView.OnItemClickListener。如下例:

...
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.widget.AdapterView;
...
public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {
[3] 定义全局变量

定义会在其他代码中用到的全部变量:

    ...
    /*
     * Defines an array that contains column names to move from
     * the Cursor to the ListView.
     */
    @SuppressLint("InlinedApi")
    private final static String[] FROM_COLUMNS = {
            Build.VERSION.SDK_INT
                    >= Build.VERSION_CODES.HONEYCOMB ?
                    Contacts.DISPLAY_NAME_PRIMARY :
                    Contacts.DISPLAY_NAME
    };
    /*
     * Defines an array that contains resource ids for the layout views
     * that get the Cursor column contents. The id is pre-defined in
     * the Android framework, so it is prefaced with "android.R.id"
     */
    private final static int[] TO_IDS = {
           android.R.id.text1
    };
    // Define global mutable variables
    // Define a ListView object
    ListView mContactsList;
    // Define variables for the contact the user selects
    // The contact's _ID value
    long mContactId;
    // The contact's LOOKUP_KEY
    String mContactKey;
    // A content URI for the selected contact
    Uri mContactUri;
    // An adapter that binds the result Cursor to the ListView
    private SimpleCursorAdapter mCursorAdapter;
    ...

注:因为Contacts.DISPLAY_NAME_PRIMARY需要Android 3.0(API version 11)或更高的版本,设置应用程序的minSdkVersion为10或更低以在这种情况下产生Android中产生警告信息。欲关掉此警告,在FROM_COLUMNS定义前添加@SuppressLint(“InlineApi”)标注。

[4] 初始化碎片

初始化Fragment:增加Android系统所需的空的、公有的构造函数,并将Fragment对象的用户界面填充到onCreateView()方法中。如下例:

    // Empty public constructor, required by the system
    public ContactsFragment() {}

    // A UI Fragment must inflate its View
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the fragment layout
        return inflater.inflate(R.layout.contact_list_fragment,
            container, false);
    }
[5] 为ListView设置CursorAdapter

设置SimpleCursorAdapter来将搜索结果绑定到ListView。欲获取展示联系人的ListView对象,需要在Fragment的父Activity中调用Activity.findViewById()。当在父activity中调用setAdapter()时使用Context。如下例:

    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        // Gets the ListView from the View list of the parent activity
        mContactsList =
            (ListView) getActivity().findViewById(R.layout.contact_list_view);
        // Gets a CursorAdapter
        mCursorAdapter = new SimpleCursorAdapter(
                getActivity(),
                R.layout.contact_list_item,
                null,
                FROM_COLUMNS, TO_IDS,
                0);
        // Sets the adapter for the ListView
        mContactsList.setAdapter(mCursorAdapter);
    }
[6] 设置选定联系人的监听器

当展示搜索结果后,往往允许用户选择某个联系人来做进一步处理。如当用户点击一个联系人时就将此联系人的地址标注在地图上。欲实现此功能,首先需要通过指定实现的AdapterView.OnItemClickListener类定义一个Fragment来作为点击监听器(如”定义Fragment展示联系人雷彪“中的描述)。

欲继续设置监听器,在onActiCreate()方法中调用setOnItemClickListener()方法来将监听器绑定到ListView上。如下例:

    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // Set the item click listener to be the current fragment.
        mContactsList.setOnItemClickListener(this);
        ...
    }

因为为ListView的OnItemClickListener指定了当前的Fragment,就需要实现所需的onItemClick()方法,此方法处理点击事件。此在后续小节中描述。

[7] 定义投影(projection)

定义包含欲从查询中返回的列的常量值。每个在ListView中的条目展示联系人的姓名,这些姓名包含了联系人的主要格式。在Android 3.0(API version 11)以及更高版本中,列名为Contacts.DISPLAY_NAME_PRIMARY;在之前的版本中,它的名字为Contacts.DISPLAY_NAME。

Contacts._ID一列被SimpleCursorAdapter用来绑定进程。Contacts._ID和LOOKUP_KEY被用来构建用户所选联系人的内容RUI。

...
@SuppressLint("InlinedApi")
private static final String[] PROJECTION =
        {
            Contacts._ID,
            Contacts.LOOKUP_KEY,
            Build.VERSION.SDK_INT
                    >= Build.VERSION_CODES.HONEYCOMB ?
                    Contacts.DISPLAY_NAME_PRIMARY :
                    Contacts.DISPLAY_NAME

        };
[8] 为Cursor列索引定义常量

欲从Cursor中的一列中获取数据,需要在Cursor中的列索引。可以为Cursor中的列定义常量来作为索引,因为在投影中,这些索引跟列名保持着相同的顺序。如:

// The column index for the _ID column
private static final int CONTACT_ID_INDEX = 0;
// The column index for the LOOKUP_KEY column
private static final int LOOKUP_KEY_INDEX = 1;
[9] 指定选择标准

欲指定想要的数据,创建一个文本表达式和变量的结合体来告知提供器欲搜索哪一列的数据以及所要查找的值。

对于文本表达式,即定义搜索列表的列的常量值。尽管此表达式也可以包含值,习惯的用法是将值部分用一个”?”占位符。在检索期间,占位符将会被数组中的值替换。使用”?”作为占位符确保了搜索室友绑定指定而非SQL编译。此步骤消除了SQL恶意注入的可能。如下例:

    // Defines the text expression
    @SuppressLint("InlinedApi")
    private static final String SELECTION =
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
            Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
            Contacts.DISPLAY_NAME + " LIKE ?";
    // Defines a variable for the search string
    private String mSearchString;
    // Defines the array to hold values that replace the ?
    private String[] mSelectionArgs = { mSearchString };

12.17

[10] 定义onItemClick()方法

在前一节为ListView设置条目点击监听器。现在需要通过使用AdapterView.OnItemClickListener.onItemClick()方法来实现监听的动作:

    @Override
    public void onItemClick(
        AdapterView<?> parent, View item, int position, long rowID) {
        // Get the Cursor
        Cursor cursor = parent.getAdapter().getCursor();
        // Move to the selected contact
        cursor.moveToPosition(position);
        // Get the _ID value
        mContactId = getLong(CONTACT_ID_INDEX);
        // Get the selected LOOKUP KEY
        mContactKey = getString(CONTACT_KEY_INDEX);
        // Create the contact's content Uri
        mContactUri = Contacts.getLookupUri(mContactId, mContactKey);
        /*
         * You can use mContactUri as the content URI for retrieving
         * the details for a contact.
         */
    }
[11] 初始化装载器(loader)

当开始使用CursorLoader来检索数据后,必须初始化后台线程和其它控制异步检索的变量。在onActivityCreate()中做这些初始化操作,在Fragment用户界面出现之前它会被立刻调用,如以下示例:

public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    // Called just before the Fragment displays its UI
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        // Always call the super method first
        super.onActivityCreated(savedInstanceState);
        ...
        // Initializes the loader
        getLoaderManager().initLoader(0, null, this);
[12] 实现onCreateLoader()

实现onCreateLoader()方法,此方法在调用initLoader()后会被载入器框架立刻调用。

在onCreateLoader()方法中,设置搜索字符串模式。欲将字符串制作成模式,插入“%”(百分号)字符来代替0或多个字符序列,或者用”_”(下划线)字符来代替单个字符,或同时使用它们。如模式“%Jefferson%”将会同时匹配”Thomas Jefferson”和“Jefferson Davis”。

从该方法中返回新的CursorLoader。对于内容URI,见Contacts.CONTENT_URI。此URI涉及整个表,如以下示例:

    ...
    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        mSelectionArgs[0] = "%" + mSearchString + "%";
        // Starts the query
        return new CursorLoader(
                getActivity(),
                Contacts.CONTENT_URI,
                PROJECTION,
                SELECTION,
                mSelectionArgs,
                null
        );
    }
[13] 实现onLoadFinished()和onLoaderReset()

实现onLoadFinished()方法。当Contacts Provider返回查询结果后载入框架会调用onLoadFinished()。在此方法中,将结果Cursor放置到SimpleCursorAdapter中。它会用搜索结果自动跟新ListView:

    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        // Put the result Cursor in the adapter for the ListView
        mCursorAdapter.swapCursor(cursor);
    }

当载入框架检测到结果Cursor包含陈旧的数据时onLoaderReset()方法会被调用。删除SimpleCursorAdapter与存在的Cursor的关联。如果不如此,载入框架将不会回收Cursor,这将会引起内存泄露。例如:

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // Delete the reference to the existing Cursor
        mCursorAdapter.swapCursor(null);

    }

至此,已经知道了在应用程序中用搜索字符串匹配联系人姓名并将结果返回到ListView中的代码片段。用户可以在列表中通过点击联系人而选择它。这将触发监听器,这就可以进一步的处理联系人数据。如检索联系人详细信息。欲知如何实现这个过程,继续看下一节”检索联系人详细信息“。

欲学更多关于用户界面的搜索功能,读API手册”创建搜索界面“。

本节中剩余的小节介绍其它的方式访问Contacts Provider而查找联系人。

(3) 根据指定数据类型匹配联系人

此技术允许指定欲匹配的特定的数据类型。以指定姓名来检索就是此技术的一个查询的例子,也可以用联系人中有的任何数据类型来做这个检索的过程。例如,可以通过指定的邮政区号来检索联系人;在这种情况下,搜索字符串需要匹配存储邮政区号的行。

与实现此种类型的检索,首先需要实现以下代码,正如之前一节中所列举:
- 请求读提供器的权限。
- 定义ListView和条目布局。
- 定义展示联系人列表的Fragment。
- 定义全局变量。
- 初始化Fragment。
- 为ListView设置CursorAdapter。
- 设置选择联系人监听器。
- 为Cursor列索引定义常量。尽管可以从不同的表格中检索数据,列投影的数据是相同的,可以给Cursor用相同的索引。
- 定义onItemClick()方法。
- 初始化载入器。
- 实现onLoadFinished()和onLoaderReset()方法。

以下步骤描述匹配搜索字符串和详细数据的指定类型并展示结果的其余代码。

[1] 选择数据类型和表格

欲用指定类型来搜索详细数据,需知数据类型的自定义MIME类型值。每个数据类型都有一个在关联此数据类型的子类ContactsContract.CommonDataKinds中由常量CONTENT_ITEM_TYPE定义的MIME类型值。此子类的名称表明它们的数据类型,如邮件数据子类为ContactsContract.CommonDataKinds.Email,邮件数据的自定义MIME类型被常量Email.CONTENT_ITEM_TYPE定义。

搜索使用ContactsContract.Data表。所有在投影中需要的常量,选择条款以及排序都被定义或继承于此表。

[2] 定义投影

欲定义投影,选择定义在ContactsContract.Data类或继承此类的类中选择一个或多个列。在返回行之前,Contacts Provider隐式的将ContactsContract.Data和其它的表联系起来。如下例:

    @SuppressLint("InlinedApi")
    private static final String[] PROJECTION =
        {
            /*
             * The detail data row ID. To make a ListView work,
             * this column is required.
             */
            Data._ID,
            // The primary display name
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                    Data.DISPLAY_NAME_PRIMARY :
                    Data.DISPLAY_NAME,
            // The contact's _ID, to construct a content URI
            Data.CONTACT_ID
            // The contact's LOOKUP_KEY, to construct a content URI
            Data.LOOKUP_KEY (a permanent link to the contact
        };
[3] 定义搜索标准

欲用字符串来搜索一个指定类型的数据,构建选择条款如下:
- 包含搜索字符串的列名。此名基于数据类型变化,所以需要找到关联数据类型的类ContactsContract.CommonDataKinds的子类然后从此子类中选出列名。例如,欲搜索邮件地址,使用Email.ADDRESS列。
- 搜索字符串,在选择条款中被“?”代替。
- 包含自定义MIME类型值的列名。此名通常是Data.MIMETYPE。
- 为数据类型自定义的MIME类型值。如前面所述,这是一个在ContactsContract.CommonDataKinds子类中的CONTENT_ITEM_TYPE常量。如邮件数据的MIME类型值为Email.CONTENT_ITEM_TYPE。通过”’”(单引号)字符将此值引起来就关闭了此值。不需要为此值使用占位符,因为这是一个常量而不是用户提供的值。举例如下:

    /*
     * Constructs search criteria from the search string
     * and email MIME type
     */
    private static final String SELECTION =
            /*
             * Searches for an email address
             * that matches the search string
             */
            Email.ADDRESS + " LIKE ? " + "AND " +
            /*
             * Searches for a MIME type that matches
             * the value of the constant
             * Email.CONTENT_ITEM_TYPE. Note the
             * single quotes surrounding Email.CONTENT_ITEM_TYPE.
             */
            Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";

接下来,定义变量来包含选择参数:

    String mSearchString;
    String[] mSelectionArgs = { "" };
[4] 实现onCreateLoader()

在上一小节中已经指定了所需数据以及如何查找它的方法,现在该在onCreateLoader()方法中定义查询了。使用投影、选择文本表达式以及选择数组参数从该方法中返回一个新的CursorLoader。对于内容URI,使用Data.CONTENT_URI。如下例所示:

    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        // OPTIONAL: Makes search string into pattern
        mSearchString = "%" + mSearchString + "%";
        // Puts the search string into the selection criteria
        mSelectionArgs[0] = mSearchString;
        // Starts the query
        return new CursorLoader(
                getActivity(),
                Data.CONTENT_URI,
                PROJECTION,
                SELECTION,
                mSelectionArgs,
                null
        );
    }

这些代码片段是基于详细数据的指定类型的简单反向查找的基础。如果应用程序集中使用特定类型来查找数据,此技术是最佳的实现方法,如邮件,同时允许用户获取与数据关联的姓名。

(4) 根据任何数据类型匹配联系人

基于数据的任何类型的检索并返回联系人(若有任何的数据匹配搜索字符串,包括姓名,邮箱地址,邮政区号,邮政地址,电话号码等等)。这将导致一个比较广泛的搜索结果。假设搜索字符串为”Doe“,然后以任意类型搜索联系人则将返回”John Doe“或住在”Doe Street”的联系人。

欲实现此种类型的检索,首先要实现以下代码,正如前面所描述:
- 请求读提供器的权限。
- 定义ListView和条目布局。
- 定义展示联系人列表的Fragment。
- 定义全局变量。
- 初始化Fragment。
- 为ListView设置CursorAdapter。
- 设置选择联系人动作的监听器。
- 定义投影。
- 为Cursor列索引定义常量。对于此种类型的检索,使用和前一节“基于姓名匹配联系人并列表结果”相同的表。同时也是使用相同的列索引。
- 定义onItemClick()方法。
- 初始化载入器。
- 实现onLoadFinished()和onLoaderReset()方法。

以下小节将描述需要用任意类型匹配数据并展示结果的剩余步骤(代码)。

[1] 移除选择标准

不用定义SELECTION 常量或mSelectionArgs 变量。在此种类型检索下不会使用它们。

[2] 实现onCreateLoader()

实现onCreateLoader()方法,返回一个新的CursorLoader。不必将搜索字符串转换为模式,因为Contacts Provider将会自动完成此步骤。用Contacts.CONTENT_FILTER_URI作为基本URI,然后通过调用Uri.withAppendedPath()将搜索字符串添加到其中。用此URI自动触发任意类型的搜索,如下例所示:

    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        /*
         * Appends the search string to the base URI. Always
         * encode search strings to ensure they're in proper
         * format.
         */
        Uri contentUri = Uri.withAppendedPath(
                Contacts.CONTENT_FILTER_URI,
                Uri.encode(mSearchString));
        // Starts the query
        return new CursorLoader(
                getActivity(),
                contentUri,
                PROJECTION,
                null,
                null,
                null
        );
    }

此代码片段是从Contacts Provider检索广泛结果的基础。此技术适用于需实现类似People 应用程序联系人列屏功能的应用程序。

1.2 检索联系人详细信息

描述如何检索某个联系人的详细信息。一个联系人的详细信息是诸如电话号码和邮件地址这样的数据。可以检索所有的详细信息,或检索指定类型的详细信息,如所有的邮件地址。

在此小节中的步骤假设已经有了用户选择的联系人行ContactsContract.Contacts。“检索联系人姓名”一节将描述如何检索联系人清单。

(1) 检索联系人所有的详细信息

欲检索某个联系人所有的详细信息,搜索ContactsContract.Data表中包含联系人LOOKUP_KEY的所有行。此列在ContactsContract.Data表中可用,因为Contacts Provider在ContactsContract.Contacts表和ContactsContract.Data表中做了隐式连接。LOOUP_KEY列在“检索联系人姓名”一节中被描述得更多。

注:检索联系人的所有详细信息降低了设备的性能,因为此过程需要检索ContactsContract.Data表中的所有列。在使用此项技术时考虑它对性能的影响。

[1] 请求权限

欲读Contacts Provider,必须在应用程序中拥有READ_CONTACTS权限。欲请求此权限,在清单文件中增加以下元素作为manifest元素的子元素:

 <uses-permission android:name="android.permission.READ_CONTACTS" />
[2] 设置投影

基于每行所包含的数据类型,可能只会用到几列或更多列。另外,不同列的数据基于具体的数据类型。欲确保从可能的列获得所有可能的数据类型,需要将所有的列名增加到投影中。若绑定了结果Cursor到ListView中则常检索Data._ID。否则,绑定不会工作。也检索Data.MIMETYPE,这样就能够标识所检索的行的数据类型。如下例:

    private static final String PROJECTION =
            {
                Data._ID,
                Data.MIMETYPE,
                Data.DATA1,
                Data.DATA2,
                Data.DATA3,
                Data.DATA4,
                Data.DATA5,
                Data.DATA6,
                Data.DATA7,
                Data.DATA8,
                Data.DATA9,
                Data.DATA10,
                Data.DATA11,
                Data.DATA12,
                Data.DATA13,
                Data.DATA14,
                Data.DATA15
            };

此投影将会根据ContactsContract.Data类中定义的列名检索ContactsContract.Data表中某行的所有列。

可选择的是,也可以使用其他定义的列常量或继承于ContactsContract.Data的类。然而需要注意,列SYNC1到列SYNC4被同步适配器使用,所以他们的数据没用。

12.18

[3] 定义选择标准

为选择条款定义一个常量、一个掌控选择参数的数组以及掌控选择值的变量。使用Contacts.LOOKUP_KEY列来查找联系人。如下例:

    // Defines the selection clause
    private static final String SELECTION = Data.LOOKUP_KEY + " = ?";
    // Defines the array to hold the search criteria
    private String[] mSelectionArgs = { "" };
    /*
     * Defines a variable to contain the selection value. Once you
     * have the Cursor from the Contacts table, and you've selected
     * the desired row, move the row's LOOKUP_KEY value into this
     * variable.
     */
    private String mLookupKey;

在选择文本表达式中使用”?”占位符以确保搜索结果是绑定产生的而不是SQL编译而来的。此方法消除了SQL参与的可能。

[4] 定义排序次序

在结果Cursor中定义排序次序。欲将所有的行都保持一种特别的数据类型,通过Data.MIMETPE排序。这个查询参数组织所有的邮件行,所有的电话号行等等。如下例:

    /*
     * Defines a string that specifies a sort order of MIME type
     */
    private static final String SORT_ORDER = Data.MIMETYPE;

注:有些数据类型不使用子类型,这样就不能对子类型进行排序。那么,就不得不迭代返回的Cursor,判断当前行的数据类型,并为使用子类型的行存储其数据。当完成读cursor后,就可以依据子类来排序每种数据并展示结果。

[5] 初始化载入器

常常在后台线程中检索Contacts Provider(以及其它的内容提供器)。使用由LoaderManager类定义的载入框架和LoaderManager.LoaderCallbacks接口来实现后台检索。

当准备检索行时,通过调用initLoader()来初始化载入器框架。传递一个整数标识符给此方法;此标识符会被传递给LoaderManager.LoaderCallbacks方法。通过吃标识符可以区分不同的载入器,所以在同一个应用程序中可以使用标识符来使用多个载入器。

以下代码片展示如何初始化载入器框架:

public class DetailsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    // Defines a constant that identifies the loader
    DETAILS_QUERY_ID = 0;
    ...
    /*
     * Invoked when the parent Activity is instantiated
     * and the Fragment's UI is ready. Put final initialization
     * steps here.
     */
    @Override
    onActivityCreated(Bundle savedInstanceState) {
        ...
        // Initializes the loader framework
        getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this);
[6] 实现onCreateLoader()方法

实现onCreateLoader()方法,在调用initLoader()后此方法会被载入器框架立即调用。在此方法中返回CursorLoader。因为此时正在搜索ContactsContract.Data表,使用Data.CONTENT_URI常量作为内容URI。如下例:

    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        // Choose the proper action
        switch (loaderId) {
            case DETAILS_QUERY_ID:
            // Assigns the selection parameter
            mSelectionArgs[0] = mLookupKey;
            // Starts the query
            CursorLoader mLoader =
                    new CursorLoader(
                            getActivity(),
                            Data.CONTENT_URI,
                            PROJECTION,
                            SELECTION,
                            mSelectionArgs,
                            SORT_ORDER
                    );
            ...
    }
Implement onLoadFini
[7] 实现onLoadFinished()和onLoaderReset()

实现onLoadFinished()方法。当Contacts Provider返回查询结果时载入器框架会调用onLoadFinished()。如下例:

    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        switch (loader.getId()) {
            case DETAILS_QUERY_ID:
                    /*
                     * Process the resulting Cursor here.
                     */
                }
                break;
            ...
        }
    }

当载入器框架检测到结果Cursor中的数据支持发生改变时载入器会调用onLoaderReset()。此时,通过设置Cursor的关联为null以移除它们。如果不这样做,载入框架不会销毁Cursor,那将会造成内存泄露。如下例:

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        switch (loader.getId()) {
            case DETAILS_QUERY_ID:
                /*
                 * If you have current references to the Cursor,
                 * remove them here.
                 */
                }
                break;
    }

(2) 检索联系人指定的详细信息

检索联系人指定的数据类型,如所有的邮箱,检索它们的详细信息所使用的都是同一个模式。唯一需要改变的是需要将代码列在“Retrieve All Details for a Contact”:
投影(Projection)
修改投影来检索包含指定数据类型的列。同时投影来使用关联数据类型的由ContractsContract.CommonDataKinds子类定义的列名常量。

选择(Selection)
修改选择文本来搜索指定数据类型的MIMETYPE值。

排序次序(Sort order)
因为只选择单个详细类型,不用组团根据Data.MIMETYPE返回的Cursor。

这些修改在接下来的板块中叙述。

[1] 定义投影

在ContractsContract.CommonDataKinds子类中为数据类型用列名常量定义欲检索的列。如果计划将Cursor绑定到ListView,要确保检索_ID列。如检索邮件数据,定义以下的投影:

    private static final String[] PROJECTION =
            {
                Email._ID,
                Email.ADDRESS,
                Email.TYPE,
                Email.LABEL
            };

注意投影会使用定义在ContractsContract.CommonDataKinds.Email类中定义的列名,而不是定义在ContractSContract.Data类中的列名。使用邮箱指定列名来增强代码的可读性。

在投影中,亦可使用定义在ContractsContract.CommonDataKinds子类中的其它的列。

[2] 定义选择标准

定义搜索文本表达式来检索指定联系人的所需详细信息的LOOKUP_KEY和Data.MIMETYPE的行。通过单引号来关闭MIMETYPE值;否则,提供器将会将常量作为一个变量名而不将其作为一个字符串。不用为此值使用占位符,因为这是在使用常量而不是用户提供的值。如下例:

    /*
     * Defines the selection clause. Search for a lookup key
     * and the Email MIME type
     */
    private static final String SELECTION =
            Data.LOOKUP_KEY + " = ?" +
            " AND " +
            Data.MIMETYPE + " = " +
            "'" + Email.CONTENT_ITEM_TYPE + "'";
    // Defines the array to hold the search criteria
    private String[] mSelectionArgs = { "" };
[3] 定义排序次序

为返回的Cursor定义一个排序次序。由于检索的是指定的数据类型,省略MIMETYPE的排序。如果所搜索的详细数据包括子类型,那么对其排序。如对邮件数据可以对Email.TYPE排序:

    private static final String SORT_ORDER = Email.TYPE + " ASC ";

1.3 用Intent修改联系人

描述如何通过发送意图到联系人应用程序修改联系人数据。

不用直接访问Contracts Provider,使用一个意图启动联系人应用程序,再返回合适的Activity。在此小节中所描述的修改操作,如果发送在Intent中的扩展数据,它被输入到所启动的Activity对应的用户界面中。

使用Intent来更新某个联系人信息是修改Contracts Provider的优先选择的方法,因为以下原因:
- 节约开发用户界面和代码的时间和辛苦成本。
- 避免由修改操作不符合Contracts Provider规则所导致的错误。
- 减少所需请求的权限。应用程序不需要写Contacts Provider的权限,因为这是联系人应用程序已经请求了此权限。

12.21

(1) 使用Intent插入新的联系人

当应用程序接收到新数据时应有让用户选择插入新联系人的功能。如一个餐馆检查的应用程序应能让用户在看到某餐馆时能够将其当做一个联系人的方式将其添加到联系人应用程序中。这个功能通过Intent来实现,用尽可能多的可用信息创建intent,然后发送intent到联系人应用程序。

使用联系人应用程序插入新行联系人到Contacts Provider的ContactsContract.RawContacts表中来插入联系人。如果有必要,在创建新行联系人时,联系人应用程序还应该为用户提供账户类型和账户。联系人应用程序也要注意联系人表中是否已经有了新建的联系人行。用户有取消插入的选择,这样就没有创建联系人。欲知更多关于原联系人见Contacts Provider API手册。

创建Intent

首先需要结合Intents.Insert.ACTION动作创建新一个新的Intent对象。将其MIME类型设置为RawContacts.CONTENT_TYPE。如下例:

...
// Creates a new Intent to insert a contact
Intent intent = new Intent(Intents.Insert.ACTION);
// Sets the MIME type to match the Contacts Provider
intent.setType(ContactsContract.RawContacts.CONTENT_TYPE);

如果有联系人的详细信息,如点好号码或邮件地址,可以将这些详细信息作为intent的额外数据插入。对于键值,根据Intents.Insert使用合适的常量。联系人应用程序将数据展示到插入屏幕中,允许用户做进一步的编辑和添加。

/* Assumes EditText fields in your UI contain an email address
 * and a phone number.
 *
 */
private EditText mEmailAddress = (EditText) findViewById(R.id.email);
private EditText mPhoneNumber = (EditText) findViewById(R.id.phone);
...
/*
 * Inserts new data into the Intent. This data is passed to the
 * contacts app's Insert screen
 */
// Inserts an email address
intent.putExtra(Intents.Insert.EMAIL, mEmailAddress.getText())
/*
 * In this example, sets the email type to be a work email.
 * You can set other email types as necessary.
 */
      .putExtra(Intents.Insert.EMAIL_TYPE, CommonDataKinds.Email.TYPE_WORK)
// Inserts a phone number
      .putExtra(Intents.Insert.PHONE, mPhoneNumber.getText())
/*
 * In this example, sets the phone type to be a work phone.
 * You can set other phone types as necessary.
 */
      .putExtra(Intents.Insert.PHONE_TYPE, Phone.TYPE_WORK);

一旦创建了intent,调用startActivity()将其发送。

    /* Sends the Intent
     */
    startActivity(intent);

此方法在联系人应用程序中打开一个允许用户输入新联系人的界面。联系人的账户类型和账户名都被列在界面的顶部。用户一旦完成数据输入并点击完成,联系人应用程序的联系人列表出现。用户通过点击返回到原先的应用程序中。

(2) 使用Intent编辑一个已存在的联系人

如果用户已经选择插入的联系人,用Intent来编辑存在的联系人十分有用。如发现有邮政地址但无邮政编码的联系人,用户就可以编辑此联系人将编码添加到这个联系人的详细信息中。

欲用intent来编辑存在的联系人,使用跟插入联系人相似的步骤。创建intent在“Insert a New Contact Using an Intent”一节中被描述,并添加联系人的Contacts.CONTENT_LOOKUP_URI和MIME类型Contacts.CONTENT_TIEM_TYPE到intent中。如果用已经有的详细信息编辑联系人,可将这些信息作为intent的额外数据。注意有写列名不能使用intent来编辑;这些列被ContactsContract.Contacts类的“更新”下的类参考API列举。

联系人查找键(Contacts Lookup Key)
联系人的LOOIUP_KEY值是用来检索联系人的标识符。即使提供器改变联系人的行ID来操控间歇的操作,它仍旧是一个常量。

最后,发送intent。作为响应,联系人应用程序展示编辑界面。当用户完成编辑并保存编辑,联系人应用程序展示练习人列表。用户通过点击返回返回到应用程序。

12.22

[1] 创建Intent

欲编辑联系人,使用ACTION_EDIT action来调用Intent(action)创建一个intent。调用setDataAndType()来设置联系人的Contacts.CONTENT_LOOKUP_URI和MIME类型Contacts.CONTENT_ITEM_TYPE的intent的值。setType()会重写intent当前的数据,所以必须在同时设置数据和MIME类型。

欲获取联系人的Contacts.CONTENT_LOOKUP_URI,使用联系人的Contacts._ID和Contacts.LOOKUP_KEY值作为参数调用Contacts.getLookupUri(id, lookupkey)。

以下代码片段展示如何创建一个intent:

    // The Cursor that contains the Contact row
    public Cursor mCursor;
    // The index of the lookup key column in the cursor
    public int mLookupKeyIndex;
    // The index of the contact's _ID value
    public int mIdIndex;
    // The lookup key from the Cursor
    public String mCurrentLookupKey;
    // The _ID value from the Cursor
    public long mCurrentId;
    // A content URI pointing to the contact
    Uri mSelectedContactUri;
    ...
    /*
     * Once the user has selected a contact to edit,
     * this gets the contact's lookup key and _ID values from the
     * cursor and creates the necessary URI.
     */
    // Gets the lookup key column index
    mLookupKeyIndex = mCursor.getColumnIndex(Contacts.LOOKUP_KEY);
    // Gets the lookup key value
    mCurrentLookupKey = mCursor.getString(mLookupKeyIndex);
    // Gets the _ID column index
    mIdIndex = mCursor.getColumnIndex(Contacts._ID);
    mCurrentId = mCursor.getLong(mIdIndex);
    mSelectedContactUri =
            Contacts.getLookupUri(mCurrentId, mCurrentLookupKey);
    ...
    // Creates a new Intent to edit a contact
    Intent editIntent = new Intent(Intent.ACTION_EDIT);
    /*
     * Sets the contact URI to edit, and the data type that the
     * Intent must match
     */
    editIntent.setDataAndType(mSelectedContactUri,Contacts.CONTENT_ITEM_TYPE);
[2] 添加导航标志

在Android 4.0(API version 14)以及以上的版本中,联系人应用程序中的一个问题导致了不正确的导航。当应用程序发送编辑intent给联系人应用程序,用户编辑并保存联系人,当点击返回按钮可见到联系人列表界面。欲导航回到应用程序中,得选择最近打开的应用程序界面再选择您的应用程序。

要在Android 4.03(API version 15)及更高版本的系统中解决此问题,增加扩展数据键finishActivityOnSaveCompleted(设置为true)给intent。Android 4.0之前的版本也能够接收此键,但此键无任何作用。设置此扩展数据如下所示:

    // Sets the special extended data for navigation
    editIntent.putExtra("finishActivityOnSaveCompleted", true);
[3] 增加另外的扩展数据

欲增加额外扩展数据到Intent中,按照需要调用putExtra()。可以使用在Intents.Insert中指定的键值对来为联系人域增加扩展数据。注意在ContactsContract.Contacts表中的有些列不能被更改。这些列被列举在Contacts.Contacts列API类参考的“更新“标题下。

[4] 发送Intent

最后,发送构建的intent。如下所示:

    // Sends the Intent
    startActivity(editIntent);

(3) 使用Intent让用户选择插入或编辑联系人

可通过发送拥有ACTION_INSERT_OR_EDIT动作的Intent来让用户决定是否插入联系人或编辑一个已存在的联系人。如邮件客户端应用允许用户增加收到的邮件地址到新联系人中,或将邮件地址增加到这个已经存在的联系人中。为intent的MIME类型设置为Contacts.CONTENT_ITEM_TYPE,不需要再设置数据URI。

当发送此intent,联系人应用程序将展示联系人列表。用户可以插入一个新的联系人也可以编辑一个已经存在的联系人。任何增加到intent中的数据将会填充到屏幕上显示。可以使用在Intent.Inset指定的任何键值对。以下代码片段展示了如何构建和发送intent:

    // Creates a new Intent to insert or edit a contact
    Intent intentInsertEdit = new Intent(Intent.ACTION_INSERT_OR_EDIT);
    // Sets the MIME type
    intentInsertEdit.setType(Contacts.CONTENT_ITEM_TYPE);
    // Add code here to insert extended data, if desired
    ...
    // Sends the Intent with an request ID
    startActivity(intentInsertEdit);

1.4 展示快速联系人

描述如何展示QuickContactBadge窗口部件。当用户点击联系人徽章窗口部件时,一个展示联系人细节的对话框被打开且有操控细节的按钮。例如,如果联系人有邮件地址,对话框将展示一个默认的邮件应用程序动作按钮。

此小节描述如何添加QuickContactBadge到应用程序的用户界面中并如何绑定数据。QuickContactBadge最初是以缩略图出现的窗体部件。尽管可以使用任何的Bitmap来显示缩略,通常使用Bitmap编码联系人中的照片缩略图。

小图片含控制动作,当用户点击此图片时,QuickContactBader将在一个对话框中展示以下内容:

  • 一张大图片。一张关联联系人的图片,如果没有可用的图片,那么会使用占位图形。
  • 应用程序图标。每个详细数据的应用程序图标都能够被内建的应用程序操控。例如,如果联系人详细信息包括一个或多个邮箱地址,邮箱图标将会出现。当用户点击此图标时,联系人的所有邮箱地址将会出现。当用户点击其中一个邮箱地址时,邮箱应用程序将为构成所选择邮箱地址的邮箱信息的界面。

QuickContactBadge提供了对联系人详细信息的即时访问,同时提供了和联系人快速交流的方式。用户不必查询联系人、查找和复制信息以及粘贴它们到合适的应用程序中。取而代之的做法是,可以点击QuickContactBadge,选择交流的方法并发该信息给合适的应用程序。

(1) 添加QuickContactBadge视图

欲增加QuickContactBadge,在布局文件中插入QuickContactBadge元素。举例如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
...
    <QuickContactBadge
               android:id=@+id/quickbadge
               android:layout_height="wrap_content"
               android:layout_width="wrap_content"
               android:scaleType="centerCrop"/>
    ...
</RelativeLayout>

(2) 检索提供器数据

欲在QuickContactBadge中展示联系人,需要为小图片准备联系人的内容URI和Bitmap。根据从Contacts Provider检索的列来产生内容URI和Bitmap。指定这些列作为投影的一部分,用于将数据载入到Cursor。

对于Android 3.0(API level 11)以及更高的版本,在投影中包含以下列:

  • Contacts._ID
  • Contacts.LOOKUP_KEY
  • Contacts.PHOTO_THUMBNAIL_URI

对于Android 2.3.3(API level 10)以及更早的版本,使用以下列:

  • Contacts._ID
  • Contacts.LOOKUP_KEY

此节剩余的部分将假设已经载入包含以上列举以及所选择的其它列的Cursor了。欲知如何在Cursor中检索这些列,读”检索Contacts列表“。

(3) 设置联系人URI和缩略图

一旦有了这些必要的列,就可以绑定数据到QuickContactBadge中了。

[1] 设置联系人URI

欲设置联系人的内容URI,调用getLookupUri(id, lookupkey)获取CONTENT_LOOKUP_URI,然后调用assignContactUri()来设置联系人。如下例:

    // The Cursor that contains contact rows
    Cursor mCursor;
    // The index of the _ID column in the Cursor
    int mIdColumn;
    // The index of the LOOKUP_KEY column in the Cursor
    int mLookupKeyColumn;
    // A content URI for the desired contact
    Uri mContactUri;
    // A handle to the QuickContactBadge view
    QuickContactBadge mBadge;
    ...
    mBadge = (QuickContactBadge) findViewById(R.id.quickbadge);
    /*
     * Insert code here to move to the desired cursor row
     */
    // Gets the _ID column index
    mIdColumn = mCursor.getColumnIndex(Contacts._ID);
    // Gets the LOOKUP_KEY index
    mLookupKeyColumn = mCursor.getColumnIndex(Contacts.LOOKUP_KEY);
    // Gets a content URI for the contact
    mContactUri =
            Contacts.getLookupUri(
                mCursor.getLong(mIdColumn),
                mCursor.getString(mLookupKeyColumn)
            );
    mBadge.assignContactUri(mContactUri);

当用户点击QuickContactBadge图标时,联系人的详细信息将自动出现在对话框中。

[2] 设置图片缩略图

为QuickContactBadge设置联系人URI不会自动载入联系人的缩略图。欲载入图片,从联系人Cursor的行中获取图片的URI,使用它打开包含被压缩的缩略图图片的文件,然后将其读到Bitmap中。

注:在3.0之前的平台中,PHOTO_THUMBNAIL_URI列不可用。对于这些版本,必须从Contacts.Photo子表中检索URI。

首先,设置变量来访问包含Contacts._ID和Contacts.LOOKUP_KEY列的Cursor,如之前所描述的那样:

    // The column in which to find the thumbnail ID
    int mThumbnailColumn;
    /*
     * The thumbnail URI, expressed as a String.
     * Contacts Provider stores URIs as String values.
     */
    String mThumbnailUri;
    ...
    /*
     * Gets the photo thumbnail column index if
     * platform version >= Honeycomb
     */
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        mThumbnailColumn =
                mCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI);
    // Otherwise, sets the thumbnail column to the _ID column
    } else {
        mThumbnailColumn = mIdColumn;
    }
    /*
     * Assuming the current Cursor position is the contact you want,
     * gets the thumbnail ID
     */
    mThumbnailUri = mCursor.getString(mThumbnailColumn);
    ...

定义个获取联系人的图片相关的数据和视图密度维数的方法,然后在Bitmap中返回合适尺寸的缩略图。构建URI指向此缩略图:

    /**
     * Load a contact photo thumbnail and return it as a Bitmap,
     * resizing the image to the provided image dimensions as needed.
     * @param photoData photo ID Prior to Honeycomb, the contact's _ID value.
     * For Honeycomb and later, the value of PHOTO_THUMBNAIL_URI.
     * @return A thumbnail Bitmap, sized to the provided width and height.
     * Returns null if the thumbnail is not found.
     */
    private Bitmap loadContactPhotoThumbnail(String photoData) {
        // Creates an asset file descriptor for the thumbnail file.
        AssetFileDescriptor afd = null;
        // try-catch block for file not found
        try {
            // Creates a holder for the URI.
            Uri thumbUri;
            // If Android 3.0 or later
            if (Build.VERSION.SDK_INT
                    >=
                Build.VERSION_CODES.HONEYCOMB) {
                // Sets the URI from the incoming PHOTO_THUMBNAIL_URI
                thumbUri = Uri.parse(photoData);
            } else {
            // Prior to Android 3.0, constructs a photo Uri using _ID
                /*
                 * Creates a contact URI from the Contacts content URI
                 * incoming photoData (_ID)
                 */
                final Uri contactUri = Uri.withAppendedPath(
                        Contacts.CONTENT_URI, photoData);
                /*
                 * Creates a photo URI by appending the content URI of
                 * Contacts.Photo.
                 */
                thumbUri =
                        Uri.withAppendedPath(
                                contactUri, Photo.CONTENT_DIRECTORY);
            }

        /*
         * Retrieves an AssetFileDescriptor object for the thumbnail
         * URI
         * using ContentResolver.openAssetFileDescriptor
         */
        afd = getActivity().getContentResolver().
                openAssetFileDescriptor(thumbUri, "r");
        /*
         * Gets a file descriptor from the asset file descriptor.
         * This object can be used across processes.
         */
        FileDescriptor fileDescriptor = afd.getFileDescriptor();
        // Decode the photo file and return the result as a Bitmap
        // If the file descriptor is valid
        if (fileDescriptor != null) {
            // Decodes the bitmap
            return BitmapFactory.decodeFileDescriptor(
                    fileDescriptor, null, null);
            }
        // If the file isn't found
        } catch (FileNotFoundException e) {
            /*
             * Handle file not found errors
             */
        // In all cases, close the asset file descriptor
        } finally {
            if (afd != null) {
                try {
                    afd.close();
                } catch (IOException e) {}
            }
        }
        return null;
    }

在代码中调用loadContactPhotoThumbnail()方法获取缩略图Bitmap,并使用结果在QuickContactBadge中设置图片缩略:

    ...
    /*
     * Decodes the thumbnail file to a Bitmap.
     */
    Bitmap mThumbnail =
            loadContactPhotoThumbnail(mThumbnailUri);
    /*
     * Sets the image in the QuickContactBadge
     * QuickContactBadge inherits from ImageView, so
     */
    mBadge.setImageBitmap(mThumbnail);

(4) 将QuickContactBadge添加到ListView中

QuickContactBadge是ListView展示联系人列表的一个有用的补充。使用QuickContactBadge来为联系人展示缩略图;当用户点击缩略图时,QuickContactBadge对话框将出现。

[1] 增加QuickContactBadge元素

首先,增加QuickContactBadge视图uansu到条目布局文件中。如欲展示QuickContactBadge和所检索的联系人的姓名将以下XML元素增加到布局文件中:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    <QuickContactBadge
        android:id="@+id/quickcontact"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:scaleType="centerCrop"/>
    <TextView android:id="@+id/displayname"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_toRightOf="@+id/quickcontact"
              android:gravity="center_vertical"
              android:layout_alignParentRight="true"
              android:layout_alignParentTop="true"/>
</RelativeLayout>

在后续节中,将该布局文件称为contact_item_layout.xml。

[2] 设置自定义CursorAdapter

欲绑定CursorAdapter到包含QuickContactBadge的ListView上,自定义扩展于CursorAdapter的适配器。此方法允许在绑定它到QuickContactBadge之前处理Cursor中的数据。此方法也可以绑定多个Cursor列到QuickContactBadge上。这些操作在规则的CursorAdapter中不可能实现。

定义的CursorAdapter子类必须重写以下方法:

  • CursorAdapter.newView() - 增加一个新的视图对象来填充条目布局。在重写该方法时,存储该布局文件的子视图对象,包含QuickContactBadge子对象。采取该方法,就避免了当每次增加新布局时不得不获取子视图对象句柄的情况。必须重写该方法来获取子视图对象的句柄。此技术能够控制到CursorAdapter.bindView()的绑定。
  • CursorAdapter.bindView() - 移动当前Cursor行中的数据到条目布局的子视图中。必须重写该方法来绑定联系人的URI、缩略图到QuickContactBadge上。默认实现只允1到1的列和视图之间映射。

”自定义列表适配器“中的代码片段包含自定义CursorAdapter的例子。

[3] 自定义列表适配器

定义CursorAdapter包括定义它的构造函数、重写newView()和bindView()方法:

    /**
     *
     *
     */
    private class ContactsAdapter extends CursorAdapter {
        private LayoutInflater mInflater;
        ...
        public ContactsAdapter(Context context) {
            super(context, null, 0);

            /*
             * Gets an inflater that can instantiate
             * the ListView layout from the file.
             */
            mInflater = LayoutInflater.from(context);
            ...
        }
        ...
        /**
         * Defines a class that hold resource IDs of each item layout
         * row to prevent having to look them up each time data is
         * bound to a row.
         */
        private class ViewHolder {
            TextView displayname;
            QuickContactBadge quickcontact;
        }
        ..
        @Override
        public View newView(
                Context context,
                Cursor cursor,
                ViewGroup viewGroup) {
            /* Inflates the item layout. Stores resource IDs in a
             * in a ViewHolder class to prevent having to look
             * them up each time bindView() is called.
             */
            final View itemView =
                    mInflater.inflate(
                            R.layout.contact_list_layout,
                            viewGroup,
                            false
                    );
            final ViewHolder holder = new ViewHolder();
            holder.displayname =
                    (TextView) view.findViewById(R.id.displayname);
            holder.quickcontact =
                    (QuickContactBadge)
                            view.findViewById(R.id.quickcontact);
            view.setTag(holder);
            return view;
        }
        ...
        @Override
        public void bindView(
                View view,
                Context context,
                Cursor cursor) {
            final ViewHolder holder = (ViewHolder) view.getTag();
            final String photoData =
                    cursor.getString(mPhotoDataIndex);
            final String displayName =
                    cursor.getString(mDisplayNameIndex);
            ...
            // Sets the display name in the layout
            holder.displayname = cursor.getString(mDisplayNameIndex);
            ...
            /*
             * Generates a contact URI for the QuickContactBadge.
             */
            final Uri contactUri = Contacts.getLookupUri(
                    cursor.getLong(mIdIndex),
                    cursor.getString(mLookupKeyIndex));
            holder.quickcontact.assignContactUri(contactUri);
            String photoData = cursor.getString(mPhotoDataIndex);
            /*
             * Decodes the thumbnail file to a Bitmap.
             * The method loadContactPhotoThumbnail() is defined
             * in the section "Set the Contact URI and Thumbnail"
             */
            Bitmap thumbnailBitmap =
                    loadContactPhotoThumbnail(photoData);
            /*
             * Sets the image in the QuickContactBadge
             * QuickContactBadge inherits from ImageView
             */
            holder.quickcontact.setImageBitmap(thumbnailBitmap);
    }
[4] 设置变量

在代码中设置变量,包括包括必要列的Cursor投影。

注:以下代码片段使用loadContactPhotoThumbnail()方法(在“设置联系人URI和缩略图”中被定义)。

public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
...
    // Defines a ListView
    private ListView mListView;
    // Defines a ContactsAdapter
    private ContactsAdapter mAdapter;
    ...
    // Defines a Cursor to contain the retrieved data
    private Cursor mCursor;
    /*
     * Defines a projection based on platform version. This ensures
     * that you retrieve the correct columns.
     */
    private static final String[] PROJECTION =
            {
                Contacts._ID,
                Contacts.LOOKUP_KEY,
                (Build.VERSION.SDK_INT >=
                 Build.VERSION_CODES.HONEYCOMB) ?
                        Contacts.DISPLAY_NAME_PRIMARY :
                        Contacts.DISPLAY_NAME
                (Build.VERSION.SDK_INT >=
                 Build.VERSION_CODES.HONEYCOMB) ?
                        Contacts.PHOTO_THUMBNAIL_ID :
                        /*
                         * Although it's not necessary to include the
                         * column twice, this keeps the number of
                         * columns the same regardless of version
                         */
                        Contacts_ID
                ...
            };
    /*
     * As a shortcut, defines constants for the
     * column indexes in the Cursor. The index is
     * 0-based and always matches the column order
     * in the projection.
     */
    // Column index of the _ID column
    private int mIdIndex = 0;
    // Column index of the LOOKUP_KEY column
    private int mLookupKeyIndex = 1;
    // Column index of the display name column
    private int mDisplayNameIndex = 3;
    /*
     * Column index of the photo data column.
     * It's PHOTO_THUMBNAIL_URI for Honeycomb and later,
     * and _ID for previous versions.
     */
    private int mPhotoDataIndex =
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
            3 :
            0;
    ...
[5] 设置ListView

在Fragment.onCreate()方法中,实例化自定义的cursor适配器并获取ListView的句柄:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        /*
         * Instantiates the subclass of
         * CursorAdapter
         */
        ContactsAdapter mContactsAdapter =
                new ContactsAdapter(getActivity());
        /*
         * Gets a handle to the ListView in the file
         * contact_list_layout.xml
         */
        mListView = (ListView) findViewById(R.layout.contact_list_layout);
        ...
    }
    ...

在onActivityCreated()方法中,绑定ContactsAdappter到ListView上:

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // Sets up the adapter for the ListView
        mListView.setAdapter(mAdapter);
        ...
    }
    ...

当重新获取包含联系人Cursor的时候,通常在onLoadFinished()方法中调用swapCursor()移动Cursor数据到ListView中。为每个在联系人列表中的入口展示QuickContactBadge:

    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        // When the loader has completed, swap the cursor into the adapter.
        mContactsAdapter.swapCursor(cursor);
    }

当绑定Cursor到拥有CursorAdapter(或子类)的ListView中并且用CursorLoader载入Cursor时,在实现的onLoaderReset()中清除对Cursor的关联。如下例:

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // Removes remaining reference to the previous Cursor
        mContactsAdapter.swapCursor(null);
    }

12.13

2. 添加Sign-In

此节描述如何添加用户签署功能到应用程序中。

为Android提供的Google Sign-In能够用用户的Google账户来认证用户。在用户用Google注册后,可以创建更多迷人的体验和增加应用程序的使用量。

Google Android API集成sign-in和社交特性到应用程序中。

2.1 主要开发特性

(1) 可信认证

Google Sign-In是一个简单的、可信和安全的方式来让人们用他们的Google凭证注册到应用程序中。
Add Sign-In

(2) 访问配置文件和社交图

在用户已经用Google注册后,应用程序可以通过姓名和展示他们的头像的方式来欢迎他们。如果应用程序请求社交范围,它能够让用户和他们的朋友联系起来,访问它们的年龄范围、语言以及公开的信息。
Getting Profile Information

2.2 开始

Google Android APIs是Google Play服务平台的一部分。欲使用Google的功能,设置Google Play服务SDK到应用程序的开发工程中。欲见更多信息,见Google Sign-In的 Start Integrating手册。

[2015.12.15 - 20:47]

程序的后台路径是/mxzadmin/,后台登陆账号和密码均为:mxiaozheng 本程序郑陆伟(www.zhengluwei.net)个人版权所有,不得以任何方式恶意散播,谢谢合作,违者必究。 如有疑问请联系作者QQ:512711380,站长导航QQ交流群:129293051 2.3版本程序演示地址:http://dh.mxiaozheng.cn/ 【V2.3】更新日志 更新于2013-7-27 1、增加百度联盟等联盟广告位,优化了首页的UI部分; 2、通过配置文件可以修改静态页面的生成路径,实现自定义路径功能,操作更加方面; 3、修复了部分虚拟主机点击后台登录按钮无反应的Bug; 4、修复了部分浏览器点击后台登录按钮无反应的Bug; 5、搜索框全面改版,只保留百度搜索。优化了用户体验,简化了操作。 【V2.1 】更新说明 1、修复了后台密码长度和管理员账号长度的相关错误; 2、在后台增加了用户可以自行修改设置弹窗信息的功能; 3、修复了数据库输入字符串不能为空的错误(其实这个错误是可以通过设置清空数据库实现的); 4、强化了SQL注入的防护。 关于本程序的环境配置和基本开发信息: .Net 2.0+Access数据库,MSSQL版本可以定制开发; 简单的采用了三层结构开发;全静态页面,有利于网站优化; 后台管理更加强大和方面,可以随意更换主站网址。 如果是虚拟空间使用本程序,请务必保证空间支持.net 2.0或以上版本,以免程序不能正常使用。 浏览器兼容:IE6-9,Firefox,Chrome内核的所有浏览器,Opera浏览器 技术特点:采用ASP.NET简单的三层架构开发,全站前台实现纯静态页面,利于网站整体优化。 功能描述:本代码是一个站长网址导航和搜索功能,用户可以在后台任意添加自己需要的链接。 注意事项:如果是虚拟空间使用本程序,请务必保证空间支持.net,以免程序不能正常使用。 另外,内置有标准的robots.txt文件,如果不明白,请勿随意修改,以免影响贵站的百度收录和排名。
CCF大数据与计算智能大赛-面向电信行业存量用户的智能套餐个性化匹配模型联通赛-复赛第二名-【多分类,embedding】.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值