Retrieving a List of Contacts

这里介绍如何获得与你搜索匹配或者部分匹配的联系人列表,主要通过以下技术:

联系人名字匹配

获得与搜索的联系人名字匹配或者部分匹配的联系人列表。Contacts Provider允许多个相同的联系人名字,所以可能会返回一个列表。

特定数据类型匹配,如电话号码匹配

获得与搜索的指定的数据类型匹配,比如电话号码,email等。返回匹配的列表

所有数据类型匹配

获得与搜索的字符匹配的列表,包括名字,电话号码,街道名,email等。通过这个技术,可以得到与你搜索字符匹配的联系人列表,只要联系人其中一项数据域字符匹配,就符合条件。

注意:在这里都是通过CursorLoader从Contacts Provider获得数据。CursorLoader在独立于UI进程的另一个进程中进行查询,这样保证查询不会影响UI的反应速度和影响用户体验。关于更多在独立进程中下载数据,请参阅Loading Data in the Background。


Request Permission to Read the Provider (请求相应权限以便读取Provider内容)

通过Contacts Provider搜索联系人,你的app必须有READ_CONTACTS权限,可以通过下列代码获得访问权限:

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

Match a Contact by Name and List the Results (通过名字匹配联系人信息并列出结果)

你需要一个ListView来显示搜索的结果,所以在main layout中定义包括ListView的UI,外加一个layout file来填充ListView的每一行。比如,你可以通过下面代码定义main layout file,文件名为contacts_list_view.xml

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

在上面的XML文件中使用android内置的ListView

通过下面代码定义另一个layout来充当ListView的每一行,文件名为contacts_list_item.xml

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


注意:这里没有关于用户输入查找的字符串的UI,因为有可能这个字符串有其他来源,比如你可能会取短信中的一个字符串来查询

上面两个layout文件定义了显示搜索结果的UI,接下来就是如何编写代码实现显示搜索结果。


Define a Fragment that displays the list of contacts ( 定义一个Fragment来显示联系人列表)

为了显示联系人列表,可以通过Activity 来加载一个Fragment, 因为除了用一个Fragment来显示联系人列表外,还有可能会用另一个Fragment来显示用户选择的联系人的详细信息。使用这种方法,就可以和Retrieving Details for a Contact结合起来。

更多关于在activity使用Fragment可以借鉴Building a Dynamic UI with Fragments.

为了便于开发者通过Contacts Provider 进行查询,android框架提供了一个类ContactsContract,类里面定义了一些有用的常量和函数。当你使用这个类时,你就不需要自己定义常量用于content URIS,table names 或者columns。通过下面代码就可以使用这个类:

import android.provider.ContactsContract;

由于使用CursorLoader从provider 获得数据,因此必须实现接口LoaderManager.LoaderCallbacks。再者为了方便删除用户选择的联系方式,实现adapter 接口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 {

Define global variables (定义全局变量)

定义在其它地方用到的全局变量


...
/*
 * Defines an array that contains column names to move from
 * 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或更高版本,所以当设置app的minSdkVersion小于等于10的时候会产生警告,为了消除这个警告,特加上@SuppressLint("InlinedApi")。

Initialize the Fragment (初始化Fragment)

初始化Fragment,添加空的构造函数,在onCreateView()函数添加Fragment的对象UI。

    // 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.contacts_list_layout, container, false);
    }


Set up the CursorAdapter for the ListView (设置CursorAdapter,以便操作ListView)

通过设置SimpleCursorAdapter将搜索的结果与ListVIew绑定。通过调用在parent activity调用函数Activity.findViewById()函数来获得ListView对象。调用ListView的setAdapter()来绑定SimpleCursorAdapter。

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

Set the selected contact listener (为联系人选项设置响应函数)

当你已经将查找的联系人信息展示时,你往往想让用户选择其中的一条联系人信息进一步的操作。比如,当用户单击一条联系人信息时,你可以将此联系人的地址显示在地图上。为了完成这个功能,你的Fragment需要实现接口AdapterView.OnItemClickListener,这个已经在上面做好了。

接下来就是通过ListView的函数setOnItemClickListener将ListView与当前Fragment绑定起来。

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

由于你将当前的Fragment设置成ListView的OnItemClickListener,你需要实现函数onItemClick()。下面会讲。


Define a projection (定义一个投射)

定义一个常量,里面包含你想要从查询返回的选项。在ListView的每项将会显示联系人的名字。在android3.0以后,联系人的姓名所在的列为Contacts.DISPLAY_NAME_PRIMARY,在之前的版本中是Contacts.DISPLAY_NAME.

另外一列Contacts._ID是用来和SimpleCursorAdapter绑定。Contacts._ID 和LOOKUP_KEY是用来为选择的联系人构造对应的content URI。

...
@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

        };

Define constants for the Cursor column indexes (为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;


Specify the selection criteria (指定选择标准)

为了指定需要的数据,你可以创建一些变量来保存你想要的数据类型的文本表达式,通过这些变量告诉provider该搜索哪些数据列。

对于文本表达式,可以定义一个常量包含想要查找的列。虽然这个表达式可以包含数值,但是最好的方法是将想要的值外加一个“?”占位符。

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

Define the onItemClick() (定义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.
         */
    }

Initialize the loader (初始化loader)

由于你使用CursorLoader来获取数据,你必须初始化这个后台进程和其它控制异步获取的变量。可以在函数onActivityCreated()中进行初始化,这个函数在Fragment UI出现之前就会被调用:

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

Implement onCreateLoader() (实现onCreateLoader()函数)

当你调用initLoader()时,loader framework马上会调用函数onCreateLoader()。

在onCreateLoader()函数中,设置搜索的字符串样式。为了使得字符串成为pattern,可以插入%来表示一串任何字符,也可以用_来表示一个字符。比如pattern:“%Jefferson%"与”Thomas Jefferson"和“Jefferson Davis"都匹配。

在这个函数返回一个新的CursorLoader,对于contentURI参数,用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
        );
    }


Implement onLoadFinished() and onLoaderReset() (实现函数onLoadFinished()和onLoaderReset())

当Contracts Provider返回查询的结果时,loader framework会调用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);
    }

当loader framework发现最终的Cursor包含陈旧的数据是,会调用函数onLoaderReset()函数。可以在这个函数中取消SimpleCursorAdapter对当前Cursor的引用。如果你不这样做,那么loader framework将不会回收Cursor,这样会导致内存泄露。

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

    }

Match a Contact By a Specific Type of Data (根据特定数据类型匹配联系人)

这个技术允许你根据你指定的数据类型来匹配联系人。例如通过联系人的名字来查找联系人就是一种,你也可以指定联系人其他部分信息,比如email等来进行匹配。比如,你可以获得指定的邮政编码的联系人,在正中情况下,查找的字符串必须与邮编。

为了实现这个技术,可以通过以下步骤实现:

请求读取Provider的权限

定义ListView和item layout.

定义一个显示联系人信息的Fragment

定义全局变量

初始化Fragment

为ListView设置CursorAdapter

为选择的联系人设置响应函数

定义Cursor行索引常量

虽然你从不同的表获得数据,但是表的行顺序和投射前是一样的,所以你可以在Cursor中使用相同的索引

定义对应的响应函数onItemClick()

初始化loader

实现onLoadFinished()和onLoaderReset()

下面的步骤叫你如何添加额外的代码以便通过指定查找的数据类型来找到想要的联系人信息。

Choose the data type and table (选择数据类型和table)

为了查找特定的数据类型,你必须知道这个数据类型对应的MIME类型。那个数据类型都有唯一的MIME类型,定义在ContactsContract.CommonDataKinds中,以CONTENT_ITEM_YTPE为后缀。比如email的数据类型就是ContactsContract.CommonDataKinds.Email, 对应的MIME类型是Email.CONTENT_TIEM_TYPE。

可以使用ContactsContract.Data表格用于你的查找,在这边表格中有你需要的投射, 选择规则,排序规则。

Define a projection (定义投射)

可以从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
        };

Define search criteria (定义查找规则)

可以通过下面选择规则来查找特定的数据类型:

查找的数据类型所在的列应包含在查找的字符串中。这个列名随着数据类型而不同,所以你需要从ContactsContract.CommonDataKinds的子类中找到对应的数据类型的列明。比如当查找email地址是,对应的列名为Email.ADDRESS。

查找的字符串应包含”?“

查找的字符串应包含MIME类型,如上面提到,这个MIME类型定义在ContactsContract.CommonDataKinds的子类中,以CONTENT_ITEM_TYPE作为后缀。这个MIME必须用单引号包括来说明常量的边界,要不然provider回见这个值解析成一个变量名,而不是字符串。由于这个MIME是常量,因此你不需要在定义对应的常量。

    /*
     * 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 = { "" };

Implement onCreateLoader() (实现onCreateLoader()函数)

现在你已经指定了你想要查找的数据,并定义好如何查找的字符串,接下来就是在onCreateLoader实现查找,在这个函数中,返回一个以你的投射数组,选择文本表达式,选择数组作为参数的CursorLoader。

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

上面这些代码可以帮助你如何查找一个特定数据类型的联系人信息。


Match a Contact by Any Type of Data (通过与任意数据类型匹配查找联系人)

与上部分不同,这里只要求联系人的任何一个信息与搜索的字符串匹配,包括名字,email,邮编,电话号码等,那么这个联系人就符合要求。比如当查找”Doe"是,联系人的名字是“John Doe”或者联系人的地址是“Doe Street"都符合要求。

可以通过下列步骤来实现:

请求读取Provider的权限

定义ListView和item layout.

定义一个显示联系人信息的Fragment

定义全局变量

初始化Fragment

为ListView设置CursorAdapter

为选择的联系人设置响应函数

定义投射数组

定义Cursor行索引常量

虽然你从不同的表获得数据,但是表的行顺序和投射前是一样的,所以你可以在Cursor中使用相同的索引

定义对应的响应函数onItemClick()

初始化loader

实现onLoadFinished()和onLoaderReset()

下面步骤叫你如何改上面代码实现这个功能

Remove selection criteria (删除选择规则)

在这里就不用定义SELECTION常量了。

Implement onCreateLoader() (实现onCreateLoader())

通过函数onCreateLoader()返回新的CursorLoader。由于Contacts Provider自动地将查找字符串转换成patter,因此你不需要这样弄了。使用Contacts.CONTENT_FILTER_URI作为基本的URI,并调用Uri.withAppenddedPath()将这个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
        );
    }








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值