Contacts数据的访问

正如ContentProvider提供了数据跨进程访问的标准接口,Contacts Provider提供了通讯录app和社交app的用户信息和数据的标准分访问接口,你可以在你的app中通过调用ContentResolver的相关方法来访问这些信息.

注: 本文主要关注如何使用intent(实际上好像不怎么focus,只占了1/4)来获取,展示,和修改联系人信息,比较简单介绍整个处理的流程,当然每一项都可以延伸下去以满足更复杂的需求,具体可参考下列文章:

1. 检索联系人列表

要检索出满足一定条件的联系人,要使用下面的机制:

  • 匹配联系人名字.
  • 匹配某个具体的数据类型,如手机号.
  • 匹配任意的数据类型.

1.1 请求Provider读取权限

要对Contacts Provider进行任何形式的查询查找,都需要有READ_CONTACTS权限:

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

1.2 显示名字匹配结果

下面进行的匹配操作是针对于ContactsContract.Contacts.

1.2.1 定义ListView和item的layout文件

目录为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"/>

// 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"/>

1.2.2 定义一个Fragment来展示数据

...
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.widget.AdapterView;

// 方便常量获取
import android.provider.ContactsContract.Contacts;
...
public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {

1.2.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 11)引入的,如果你的项目的minSdkVersion设置的小于11,则Android Lint会产生警告,为了关掉对此的警告,就在FROM_COLUMNS添加了@SuppressLint("InlinedApi")注解.

1.2.4 初始化Fragment

    // 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
        View layout= inflater.inflate(R.layout.contacts_list_view,
                container, false);
        // Gets the ListView
        mContactsList= (ListView) layout.findViewById(R.id.list);
        return layout;
    }

1.2.5 设置CursorAdapter

    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        // Gets a CursorAdapter
        mCursorAdapter = new SimpleCursorAdapter(
                getActivity(),
                R.layout.contacts_list_item,
                null,
                FROM_COLUMNS, TO_IDS,
                0);
        // Sets the adapter for the ListView
        mContactsList.setAdapter(mCursorAdapter);
    }

1.2.6 设置item点击事件

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

1.2.7 定义一个projection

也就是想要获取的数据列.

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

        };

1.2.8 定义Cursor列索引常数

要从Cursor中获取数据,需要传入列的索引值,该索引值是根据projection中的顺序从0开始增加的,这里我们先定义:

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

1.2.9 设置筛选条件

    // 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,default as blank
    private String mSearchString="";
    // Defines the array to hold values that replace the ?
    private String[] mSelectionArgs = { mSearchString };

1.2.10 重写onItemClick()方法.

在1.2.6中调用了mContactsList.setOnItemClickListener(this)方法将事件设置给类this,也就是我们这个Fragment,就要去实现这个Listener的抽象方法:

    @Override
    public void onItemClick(
            AdapterView<?> parent, View item, int position, long rowID) {
        // Get the Cursor
        Cursor cursor = ((SimpleCursorAdapter) parent.getAdapter()).getCursor();
        // Move to the selected contact
        cursor.moveToPosition(position);
        // Get the _ID value
        mContactId = cursor.getLong(CONTACT_ID_INDEX);
        // Get the selected LOOKUP KEY
        mContactKey = cursor.getString(LOOKUP_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.
         */
    }

1.2.11 初始化loader

    // Initializes the loader
    getLoaderManager().initLoader(0, null, this);

1.2.12 实现LoaderManager.LoaderCallbacks的抽象方法.

    @Override
    public Loader<Cursor> onCreateLoader(int id, 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
        );
    }

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

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

注: 在SQL中,"%"是通配符,只能与LIKE运算符一起使用,表示替代一个或多个字符.

在CursorLoader的构造参数中,URI还可以调用Uri.withAppendedPath()拼接Contacts.CONTENT_FILTER_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
        );
    }

2. 获取联系人详情

在获取了联系人列表之后,你可能还需要查看详情信息,比如手机号,地址等等.

2.1 获取联系人的所有详情信息

联系人详情信息存放在ContactsContract.Data这个表中,可以用联系人的LOOKUP_KEY来获取详情.因为LOOKUP_KEYContactsContract.Contacts表中的一列,而ContactsContract.Contacts又与ContactsContract.Data是关联关系.

注意: 获取联系人的所有详情信息会影响设备的性能,因为这需要去ContactsContract.Data获取所有列,你在是否获取联系人全部详情的时候要注意考虑这个性能问题.

2.1.1 请求权限

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

2.1.2 设置一个projection

projection的设置视你要获取的信息而定,这里我们要获取所有的列,所以把所有的列都列出来.如果你要将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
            };

2.1.3 设置检索条件

    // Defines the selection clause
    private static final String SELECTION = ContactsContract.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;

2.1.4 设置排序顺序

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

2.1.5 Loader相关方法设置

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    ...
        // 从前面的列表中拿到lookup key
        mLookupKey = getIntent().getStringExtra("lookup_key");

        // Initializes the loader framework
        getSupportLoaderManager().initLoader(0, null, this);
    }
    ...
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {

        // Assigns the selection parameter
        mSelectionArgs[0] = mLookupKey;

        // Starts the query
        CursorLoader mLoader =
                new CursorLoader(
                        this,
                        ContactsContract.Data.CONTENT_URI,
                        PROJECTION,
                        SELECTION,
                        mSelectionArgs,
                        SORT_ORDER
                );
        return mLoader;
    }
        @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
                    /*
                     * Process the resulting Cursor here.
                     */
    }
        @Override
    public void onLoaderReset(Loader<Cursor> loader) {
                /*
                 * If you have current references to the Cursor,
                 * remove them here.
                 */
    }

上述设置之后即可拿到联系人详细信息

2.2 获取联系人详情的某些信息

要获取联系人的某些信息,只需要按需调整一下参数即可.例如要获取邮箱信息,可以使用CommonDataKinds这个类,这里面定义ContactsContract.Data表中使用的数据的类型,比如下面要用到的Email类.如下示例,代码中你只要做下面一些改变:

  • Projection
  • Selection
  • Sort order
    private static final String[] PROJECTION =
            {
                Email._ID,
                Email.ADDRESS,
                Email.TYPE,
                Email.LABEL
            };
    /*
     * 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 = { "" };
    
    // Define a sort order
    private static final String SORT_ORDER = Email.TYPE + " ASC ";

3. 使用Intent修改联系人信息

下面介绍如何使用Intent来插入或修改联系人信息,而不是直接操作Contacts Provider,同时这种方式是你要优先考虑的,有三点原因:

  • 你无需写联系人处理相关的UI和其他处理代码,省时省力.
  • 避免了修改时违背Contact Provider的规则而导致的错误.
  • 减少了权限的请求.

3.1 使用Intent插入联系人信息

很多时候你需要允许用户在获取到新数据的时候插入新的联系人信息.比如,一个酒店评价app就可以允许用户在浏览该酒店信息时将其添加到通讯录中.
下面将介绍调用联系人app插入新将数据到raw contact(ContactsContract.RawContacts)这个表中,具体看Retrieval and modification with intents.

新建Intent时使用的是action是Intents.Insert.ACTION,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对象中,而存放的时候要注意key要从Intents.Insert获取,如下示例:


    ...
    private EditText mEmailAddress;
    private EditText mPhoneNumber;
    ...
        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        /* Assumes EditText fields in your UI contain an email address
         * and a phone number.
         *
         */
        mEmailAddress = (EditText) findViewById(R.id.email);
        mPhoneNumber = (EditText) findViewById(R.id.phone);
    }
    public void submit(View v) {
        // 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);
        /*
         * 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, CommonDataKinds.Phone.TYPE_WORK);

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

启动之后会打开联系人的新建联系人页面,就可以进行相应操作.

3.2 使用Intent编辑联系人信息

编辑联系人信息的流程和插入类型,但是要添加联系人的Contacts.CONTENT_LOOKUP_URI和MIME类型Contacts.CONTENT_ITEM_TYPE到intent中,当然你还可以将已有数据传到intent中,然后启动intent即可.

注意: 有一些列通过是无法通过intent修改的,具体在ContactsContract.Contacts中的"Update"标题下可以查看.

如下示例:

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

注意:在Android 4.0(API 14)及以上,使用带edit action的intent启动联系人app之后,当用户点完成之后无法正确返回到自己的app中,解决方案是添加一行代码,如下:

    // Sets the special extended data for navigation
    editIntent.putExtra("finishActivityOnSaveCompleted", true);

3.3 使用Intent让用户选择插入还是编辑

除了直接插入和编辑之外,你还可以让用户选择要哪种操作,使用ACTION_INSERT_OR_EDIT为action,如下示例:

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

4. 联系人Badge的展示

本节将展示如何添加QuickContactBadge到UI并且绑定数据. QuickContactBadge是一个用于展示缩略图的控件,虽然你可以给它使用任意的Bitmap,但是通常你还是应该给它设置缩略图.
QuickContactBadge表现的像一个控制器,当用户点击的时候,它会展开成一个dialog,这个dialog包含以下信息:

  • 一个大图. 这个大图与联系人绑定在一起,如果该联系人无图片,则显示默认图片.
  • App icons. 每一条数据的app icon都可以被内置的app所处理. 比如, 如果联系人详情中有一个或多个email地址,则email icon就会出现,当用户点击这个icon的时候,该条联系人的所有的email地址都会出现,当用户点击email地址时,就会跳转到email app来处理.

QuickContactBadge提供了一个快速访问联系人详情的方法,用户因此就可以省去查找,复制和粘贴联系人信息到其他地方,取而代之的时选择合适的方法来直接发送联系人信息.

4.1 添加一个QuickContactBadge View

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

4.2 获取provider数据

为了在QuickContactBadge中展示联系人信息,你需要给它提供一个content URI和一个Bitmap,而这两个可以从Contacts Provider中获取,要获取这些数据你的projection需要这样设置:

a. Android 3.0(API 11)及以后:

b. Android 2.3.3(API 10)及以前:

4.3 设置Contact URI和缩略图

4.3.1 设置Contact URI

首先通过getLookupUri(id,lookupKey)方法获取到一个CONTENT_LOOKUP_URI,然后调用assignContactUri()设置contact uri,如下示例:

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

4.3.2 设置缩略图

给QuickContactBadge设置了contact URI之后不会自动加载联系人头像缩略图,要手动加载.先从联系人的Cursor中拿到头像的URI,然后使用它来打开这个文件,接着将该文件读入Bitmap中.

注意: PHOTO_THUMBNAIL_URI这一列在Android 3.0之前没有,所以你必须要从Contacts.Photo这个子表中获取.

首先,设置变量:

    // 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:

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

最后,调用上述方法来获取一个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);
Add a QuickContactBad

4.4 添加QuickContactBadge到ListView

4.4.1 设置ListView的Item View

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

4.4.2 自定义CursorAdapter

CursorAdapter顾名思义,适用于处理Cursor的Adapter,它是一个抽象类,里面有两个抽象方法需要子类来实现:

如下示例:

    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 view =
                    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.setText(displayName);
            ...
            /*
             * Generates a contact URI for the QuickContactBadge.
             */
            final Uri contactUri = Contacts.getLookupUri(
                    cursor.getLong(mIdIndex),
                    cursor.getString(mLookupKeyIndex));
            holder.quickcontact.assignContactUri(contactUri);
            /*
             * 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.4.3 设置变量

    // 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_URI :
                        /*
                         * 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;

4.4.4 设置ListView

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

         /*
         * Instantiates the subclass of
         * CursorAdapter
         */
        mAdapter = new ContactsAdapter(this);
        /*
         * Gets a handle to the ListView in the file
         * contact_list_layout.xml
         */
        mListView = (ListView) findViewById(R.id.list);
        // Sets up the adapter for the ListView
        mListView.setAdapter(mAdapter);
        ...
    }

还有一个关键代码:

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mAdapter.swapCursor(data);
    }

OK,跑起来就可以看到效果了.

总结

在本例中,官方的training中有很多地方的代码是有问题的,并且官方提供的demo和training中代码也不是很一致,最好的方式还是跟着training的思路,然后自己写demo,官方的demo拿来做参考.
同时在访问Contacts数据的过程中,有一些要注意的地方:

  • 使用Contacts Provider时需要设置权限.
  • 不同Android版本的数据库的字段可能不一致.
  • 类的导入要注意,v4就统一v4.
  • 尽量使用Intent来操作.

Reference

  1. Accessing Contacts Data
  2. Retrieving a List of Contacts
  3. Retrieving Details for a Contact
  4. Modifying Contacts Using Intents
  5. Displaying the Quick Contact Badge
  6. Loader
  7. SQL 通配符
  8. ContactsList Demo
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容