检索通讯录列表

这节课将通过下面的技术为你展示怎样根据搜索内容进行匹配部分或全部来获取通讯录列表:

  • 根据姓名:

    通过搜索姓名的部分或全部来获取通讯列表。
    Contacts Provider允许有过个相同的名字,所以这种方法额可以返回一个匹配列表。

  • 根据具体类型,例如一个电话号码:

    通过一个具体的数据类型来搜索获取通讯列表,像一个邮件地址。比如,这个方法允许你通过搜索邮件地址来获取所有匹配的通讯录列表。

  • 根据任何类型:

    通过任何具体的数据类型进行搜索来获取通讯列表,包括姓名,电话号码,街道地址,邮箱地址等四项。比如。这个方法允许你通过搜索通讯录所能匹配的任何类型的字符来获取通讯列表。

记:这节课里所有的例子都是运用CursorLoader从Contacts Provider来获取资料。CursorLoader通过一个不同于UI线程的线程进行的。这样确保查询不会拖慢UI的响应时间而影响用户体验。相应获取更多信息,请查看Android 培训里的类:后台获取数据(Loading Data in the Background)。

获取权限读取Provider

通过任何类型字符来搜索contacts Provider,你的App都必须有READ_CONTACTS的权限。添加<uses-permission>元素作为一个<mainfest>子元素到你的清单文件里

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

通过姓名匹配通讯录并展示出来

这个方法是尝试通过搜索Contact Provider里表ContactsContract.Contacts通讯录里的名字。你通常是通过ListView将结果展示出来让用户选择其中的通讯录。

定义ListView和条目布局

为了在ListView里面展示搜索结果,你需要一个包含ListView的完整UI的main布局文件和ListView的子条目布局文件。例如,通过如下的XML创建一个main布局文件res/layout/contacts_list_view.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 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.

记:这节课没有对获取搜索字符的UI的描述,因为你可能间接获取。比如,你可以让用户选择搜索名字与输入内容匹配的.

这两个布局文件定义了UI展示一个ListView。下一步是写代码让UI展示通讯列表。

创建Fragment来显示通讯列表

为了展示通讯列表,通过创建Activity加载的Fragment开始。使用Fragment是一个更为合适的方法。因为你可以用一个Fragment展示通讯列表,另一个Fragment展示选中的条目详情页面。用这个方法,你可以将本课中的介绍的一种技术与Retrieving Details for a Contact(获取联系人详情)中的一种技术相结合。

学习怎么在一个Activ用一个或更多Fragment,请阅读Building a Dynamic UI with Fragments(用Fragment创建动态UI).
为了查询Contact Provider,Android framework提供了一个contracts类叫做ContractsContract,里面定义了进入provider的常量和方法。当使用此类时,你不用为content URI定义自己的常量,表名,列。使用此类,包含如下状态:

import android.provider.ContactsContract;

由于代码使用CursorLoader获取资料,你必须确保实现loader interface(加载接口)LoaderManager.LoaderCallbacks。同时,为了检测用户在查询列表中选择的联系人,必须实现adapter interface(适配器接口)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 {
定义全局变量

定义在代码其他地方使用到的全局变量

   ...
    /*
     * 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需要Android3.0(API版本11)或以上,在Android Studio中设置app的minSdkVersion为10或以下来生成一个 Android Lint警告。要关闭警告,在FROM_COLUMNS前添加 @SuppressLint("InlinedApi")注解即可

初始化Fragment

初始化Fragment,需要通过Android系统添加一个无参构造方法,并在回调方法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.contact_list_fragment,
            container, false);
    }
为ListView设置CursorAdapter

设置SimpleCursorAdapter绑定ListView中的搜索结果,为获取ListView类展示通讯录列表,需要用Fragment的父层Activity调用 Activity.findViewById() ,用父层Activity的上下文调用setAdapter()如:

 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);
    }
设置选中联系人监听

当你展示搜索结果的时候,你通常想要用户进一步的选择单个联系 。例如,当用户点击一个可以在地图上展示出来的联系人的通讯地址的时候。为了提供这个功能,首先要让当前Fragment实习一个点击监听AdapterView.OnItemClickListener,像在Define a Fragment that displays the list of contacts(定义一个显示通讯列表的Fragment)中写的那样。
为了继续设置监听,通过在方法onActivityCreated()中调用setOnItemClickListener(),例如:

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

由于你已经指定当前Fragment中的ListView的OnItemClickListener(条目点击监听),你现在需要实现用来执行点击事件的方法onItemClick(),这个将在后续部分讲解。

定义projection

定义一个常量包含你查询中所需要的列,每个ListView条目中显示联系人的名字。在安卓3.0(API版本11)及以上,列名是Contacts.DISPLAY_NAME_PRIMARY,3.0以前的版本列名是Contacts.DISPLAY_NAME

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

        };
为Cursor每一列定义一个常量

为了从Cursor中的列中获取信息,你需要列在Cursor中的索引。你可以为列的索引定义常量,因为在你的projection中索引顺序和列名一样。如:

// 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;
指定选择条件

为了指定所需要的信息,创建文本表达式和变量的组合,告诉provider要搜索的数据列和要查找的值。
对于文本表达式,定义出搜索列的常量。虽然这个表达式也可以包含值,但是最好的做法是用“?”占位符。在检索期间,占位符由数组中的值替换。使用“?”作为占位符,确保搜索规范是通过绑定而不是通过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 };
定义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.
         */
    }
初始化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);
实现onCreateLoader()

实现方法onCreateLoader(),它是在调用initLoader()之后立即由loader框架调用的。在onCreateLoader()中,设置搜索字符串规则。要将字符串转换为规则,请插入“%”(百分比)字符以表示零个或多个字符的序列,或者使用“_”(下划线)字符表示单个字符,或两者都用。例如,模式“%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
        );
    }
实现onLoadFinished() and onLoaderReset()

实现onLoadFinished()方法。当Contacts Provider返回查询的结果时,loader框架调用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框架检测到结果Cursor包含老的数据时,会调用onLoaderReset()方法。删除引用已有Cursor的SimpleCursorAdapter。如果不这样做,loader框架将不会回收Cursor,这会导致内存泄漏。例如:

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

    }

现在,你获得了应用程序的关键部分,它与搜索字符串相匹配的联系人姓名,并在ListView中返回。用户可以单击联系人姓名来选择它。这将触发监听,这样你可以进一步对联系人数据进行处理。例如,您可以检索联系人的详细信息。要了解如何执行此操作,请继续下一课,检索联系人的详细信息。要了解有关搜索用户界面的更多信息,请阅读API Creating a Search Interface(指南创建搜索界面)。

本课程的其余部分演示了在Contacts Provider中查找联系人的其他方法

按特定类型的数据匹配联系人

此技术允许您指定要匹配的数据类型。按名称检索是此类型查询的特定示例,但也可以对与联系人相关的任何类型的详细信息数据执行此操作。例如,您可以检索具有具体邮政编码的联系人;在这种情况下,搜索字符串必须匹配存储在邮政编码行中的数据。
要实现此类型的检索,请首先实现以下代码,如上一节所述:

  • 请求读取Provider权限。
  • 定义ListView和条目布局。
  • 定义显示联系人列表的Fragment。
  • 定义全局变量。
  • 初始化Fragment。
  • 设置ListView的CursorAdapter。
  • 设置所选的联系人监听。
  • 定义Cursor列索引的常量。 虽然您正在从不同的表中检索数据,但projection中列的顺序是相同的,因此您可以为Cursor使用相同的索引。
  • 初始化loader。
  • 实现onLoadFinished()和onLoaderReset()。

以下步骤显示了将搜索字符串与特定类型的详细信息数据匹配并显示结果所需的附加代码。

移除选择条件

不要定义SELECTION常量或mSelectionArgs变量。这些不用于这种类型的检索。

实现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进行模糊搜索的基本代码,该技术对于要实现类似应用程序联系人列表展示到屏幕上很有用。

注:

本篇文章系本人第一次尝试翻译,有很多不足之处,还望见谅。后面会不断完善。
希望共同学习,共同进步!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值