显示联系人头像

    这一课展示了如何在我们的应用界面上添加一个QuickContactBadge,以及如何为它绑定数据。
    QuickContactBadge是一个在初始情况下显示联系人缩略图头像的widget。尽管我们可以使用任何作为缩略图头像,但是我们通常会使用从联系人照片缩略图中解码出来的Bitmap。

    这个小的图片是一个控件,当用户点击它时,QuickContactBadge会展开一个包含以下内容的对话框:

    • 一个大的联系人头像

      与这个联系人关联的大的头像,如果此人没有设置头像,则显示预留的图案。

    • 应用程序图标

      根据联系人详情数据,显示每一个能够被手机中的应用所处理的数据的图标。例如,如果联系人的数据包含一个或多个email地址,就会显示email应用的图标。当用户点击这个图标的时候,这个联系人所有的email地址都会显示出来。当用户点击其中一个email地址时,email应用将会显示一个界面,让用户为选中的地址撰写邮件。

    QuickContactBadge视图提供了对联系人数据的即时访问,是一种与联系人沟通的快捷方式。用户不用查询一个联系人,查找并复制信息,然后把信息粘贴到合适的应用中。他们可以点击QuickContactBadge,选择他们想要的沟通方式,然后直接把信息发送给合适的应用中。

    为了添加一个QuickContactBadge视图,需要在布局文件中插入一个QuickContactBadge。例如:

    为了能在QuickContactBadge中显示联系人,我们需要这个联系人的内容URI和显示头像的Bitmap。我们可以从在Contacts Provider中获取到的数据列中生成这两个数据。需要指定这些列作为查询映射去把数据加载到Cursor中。

    对于Android 3.0(API版本为11)以及以后的版本,需要在查询映射中添加以下列:

    对于Android 2.3.3(API版本为10)以及之前的版本,则使用以下列:

    这一课的剩余部分假设你已经获取到了包含这些以及其他你可能选择的数据列的Cursor对象。想要学习如何获取这些列对象的Cursor,请参阅课程。

    为了设置联系人URI,需要调用getLookupUri(id, lookupKey)去获取,然后调用assignContactUri())去为QuickContactBadge设置对应的联系人。例如:

    1. Cursor mCursor;
    2. // The index of the _ID column in the Cursor
    3. int mIdColumn;
    4. // The index of the LOOKUP_KEY column in the Cursor
    5. int mLookupKeyColumn;
    6. // A content URI for the desired contact
    7. Uri mContactUri;
    8. // A handle to the QuickContactBadge view
    9. QuickContactBadge mBadge;
    10. ...
    11. mBadge = (QuickContactBadge) findViewById(R.id.quickbadge);
    12. /*
    13. * Insert code here to move to the desired cursor row
    14. */
    15. // Gets the _ID column index
    16. mIdColumn = mCursor.getColumnIndex(Contacts._ID);
    17. // Gets the LOOKUP_KEY index
    18. mLookupKeyColumn = mCursor.getColumnIndex(Contacts.LOOKUP_KEY);
    19. // Gets a content URI for the contact
    20. mContactUri =
    21. Contacts.getLookupUri(
    22. mCursor.getLong(mIdColumn),
    23. mCursor.getString(mLookupKeyColumn)
    24. );
    25. mBadge.assignContactUri(mContactUri);

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

    设置联系人照片的缩略图

    为QuickContactBadge设置联系人URI并不会自动加载联系人的缩略图照片。为了加载联系人照片,需要从联系人的Cursor对象的一行数据中获取照片的URI,使用这个URI去打开包含压缩的缩略图文件,并把这个文件读到Bitmap对象中。

    首先,为包含Contacts._ID和Contacts.LOOKUP_KEY的Cursor数据列设置对应的变量,这在之前已经有描述:

    1. // The column in which to find the thumbnail ID
    2. int mThumbnailColumn;
    3. /*
    4. * The thumbnail URI, expressed as a String.
    5. * Contacts Provider stores URIs as String values.
    6. */
    7. String mThumbnailUri;
    8. ...
    9. /*
    10. * Gets the photo thumbnail column index if
    11. * platform version >= Honeycomb
    12. */
    13. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    14. mThumbnailColumn =
    15. mCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI);
    16. // Otherwise, sets the thumbnail column to the _ID column
    17. } else {
    18. mThumbnailColumn = mIdColumn;
    19. }
    20. /*
    21. * Assuming the current Cursor position is the contact you want,
    22. * gets the thumbnail ID
    23. */
    24. mThumbnailUri = mCursor.getString(mThumbnailColumn);
    25. ...

    定义一个方法,使用与这个联系人的照片有关的数据和目标视图的尺寸作为参数,返回一个尺寸合适的缩略图Bitmap对象。下面先构建一个指向这个缩略图的URI:

    1. /**
    2. * Load a contact photo thumbnail and return it as a Bitmap,
    3. * resizing the image to the provided image dimensions as needed.
    4. * @param photoData photo ID Prior to Honeycomb, the contact's _ID value.
    5. * For Honeycomb and later, the value of PHOTO_THUMBNAIL_URI.
    6. * @return A thumbnail Bitmap, sized to the provided width and height.
    7. * Returns null if the thumbnail is not found.
    8. */
    9. private Bitmap loadContactPhotoThumbnail(String photoData) {
    10. // Creates an asset file descriptor for the thumbnail file.
    11. AssetFileDescriptor afd = null;
    12. // try-catch block for file not found
    13. try {
    14. // Creates a holder for the URI.
    15. Uri thumbUri;
    16. // If Android 3.0 or later
    17. if (Build.VERSION.SDK_INT
    18. >=
    19. Build.VERSION_CODES.HONEYCOMB) {
    20. // Sets the URI from the incoming PHOTO_THUMBNAIL_URI
    21. thumbUri = Uri.parse(photoData);
    22. } else {
    23. // Prior to Android 3.0, constructs a photo Uri using _ID
    24. /*
    25. * Creates a contact URI from the Contacts content URI
    26. * incoming photoData (_ID)
    27. */
    28. final Uri contactUri = Uri.withAppendedPath(
    29. Contacts.CONTENT_URI, photoData);
    30. /*
    31. * Creates a photo URI by appending the content URI of
    32. * Contacts.Photo.
    33. */
    34. thumbUri =
    35. Uri.withAppendedPath(
    36. }
    37. /*
    38. * Retrieves an AssetFileDescriptor object for the thumbnail
    39. * URI
    40. * using ContentResolver.openAssetFileDescriptor
    41. */
    42. afd = getActivity().getContentResolver().
    43. openAssetFileDescriptor(thumbUri, "r");
    44. /*
    45. * Gets a file descriptor from the asset file descriptor.
    46. * This object can be used across processes.
    47. FileDescriptor fileDescriptor = afd.getFileDescriptor();
    48. // Decode the photo file and return the result as a Bitmap
    49. // If the file descriptor is valid
    50. if (fileDescriptor != null) {
    51. // Decodes the bitmap
    52. return BitmapFactory.decodeFileDescriptor(
    53. fileDescriptor, null, null);
    54. }
    55. // If the file isn't found
    56. } catch (FileNotFoundException e) {
    57. /*
    58. * Handle file not found errors
    59. */
    60. }
    61. // In all cases, close the asset file descriptor
    62. } finally {
    63. if (afd != null) {
    64. try {
    65. afd.close();
    66. } catch (IOException e) {}
    67. }
    68. }
    69. return null;
    70. }

    在代码中调用loadContactPhotoThumbnail()去获取缩略图Bitmap对象,使用获取的Bitmap对象去设置QuickContactBadge头像缩略图。

    QuickContactBadge对于一个展示联系人列表的ListView来说是一个非常有用的添加功能。使用QuickContactBadge去为每一个联系人显示一个缩略图,当用户点击这个缩略图时,QuickContactBadge对话框将会显示。

    首先,在列表项布局文件中添加QuickContactBadge视图元素。例如,如果我们想为获取到的每一个联系人显示QuickContactBadge和名字,把以下的XML内容放到对应的布局文件中:

    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    2. android:layout_width="match_parent"
    3. android:layout_height="wrap_content">
    4. <QuickContactBadge
    5. android:id="@+id/quickcontact"
    6. android:layout_height="wrap_content"
    7. android:layout_width="wrap_content"
    8. android:scaleType="centerCrop"/>
    9. <TextView android:id="@+id/displayname"
    10. android:layout_width="match_parent"
    11. android:layout_height="wrap_content"
    12. android:layout_toRightOf="@+id/quickcontact"
    13. android:gravity="center_vertical"
    14. android:layout_alignParentRight="true"
    15. android:layout_alignParentTop="true"/>
    16. </RelativeLayout>

    在以下的章节中,这个文件被称为contact_item_layout.xml

    设置自定义的CursorAdapter

    定义一个继承自CursorAdapter的adapter来将CursorAdapter绑定到一个包含QuickContactBadge的ListView中。这种方式允许我们在绑定数据到QuickContactBadge之前对Cursor中的数据进行处理。同时也能将多个Cursor中的列绑定到QuickContactBadge。而使用普通的CursorAdapter是不能完成这些操作的。

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

    • 填充一个View对象去持有列表项布局。在重写这个方法的过程中,需要保存这个布局的子View的handles,包括QuickContactBadge的handles。通过采用这种方法,避免了每次在填充新的布局时都去获取子View的handles。

      我们必须重写这个方法以便能够获取每个子View对象的handles。这种方法允许我们控制这些子View对象在CursorAdapter.bindView()方法中的绑定。

    • 将数据从当前Cursor行绑定到列表项布局的子View对象中。必须重写这个方法以便能够将联系人的URI和缩略图信息绑定到QuickContactBadge。这个方法的默认实现仅仅允许在数据列和View之间的一对一映射。

    以下的代码片段是一个包含了自定义CursorAdapter子类的例子。

    定义CursorAdapter的子类包括编写这个类的构造方法,以及重写newView()和bindView():

    1. private class ContactsAdapter extends CursorAdapter {
    2. private LayoutInflater mInflater;
    3. ...
    4. public ContactsAdapter(Context context) {
    5. super(context, null, 0);
    6. /*
    7. * Gets an inflater that can instantiate
    8. * the ListView layout from the file.
    9. */
    10. mInflater = LayoutInflater.from(context);
    11. ...
    12. }
    13. ...
    14. /**
    15. * Defines a class that hold resource IDs of each item layout
    16. * row to prevent having to look them up each time data is
    17. * bound to a row.
    18. */
    19. private class ViewHolder {
    20. TextView displayname;
    21. QuickContactBadge quickcontact;
    22. }
    23. ..
    24. @Override
    25. public View newView(
    26. Context context,
    27. Cursor cursor,
    28. ViewGroup viewGroup) {
    29. /* Inflates the item layout. Stores resource IDs in a
    30. * in a ViewHolder class to prevent having to look
    31. * them up each time bindView() is called.
    32. */
    33. final View itemView =
    34. mInflater.inflate(
    35. R.layout.contact_list_layout,
    36. viewGroup,
    37. false
    38. );
    39. final ViewHolder holder = new ViewHolder();
    40. holder.displayname =
    41. (TextView) view.findViewById(R.id.displayname);
    42. holder.quickcontact =
    43. (QuickContactBadge)
    44. view.findViewById(R.id.quickcontact);
    45. view.setTag(holder);
    46. return view;
    47. }
    48. ...
    49. public void bindView(
    50. View view,
    51. Context context,
    52. Cursor cursor) {
    53. final ViewHolder holder = (ViewHolder) view.getTag();
    54. final String photoData =
    55. cursor.getString(mPhotoDataIndex);
    56. cursor.getString(mDisplayNameIndex);
    57. ...
    58. // Sets the display name in the layout
    59. holder.displayname = cursor.getString(mDisplayNameIndex);
    60. ...
    61. /*
    62. * Generates a contact URI for the QuickContactBadge.
    63. */
    64. final Uri contactUri = Contacts.getLookupUri(
    65. cursor.getLong(mIdIndex),
    66. cursor.getString(mLookupKeyIndex));
    67. holder.quickcontact.assignContactUri(contactUri);
    68. String photoData = cursor.getString(mPhotoDataIndex);
    69. /*
    70. * Decodes the thumbnail file to a Bitmap.
    71. * The method loadContactPhotoThumbnail() is defined
    72. * in the section "Set the Contact URI and Thumbnail"
    73. */
    74. Bitmap thumbnailBitmap =
    75. loadContactPhotoThumbnail(photoData);
    76. /*
    77. * Sets the image in the QuickContactBadge
    78. * QuickContactBadge inherits from ImageView
    79. */
    80. holder.quickcontact.setImageBitmap(thumbnailBitmap);
    81. }

    设置变量

    在代码中,设置相关变量,添加一个包括必须数据列的Cursor。

    例如:

    1. public class ContactsFragment extends Fragment implements
    2. LoaderManager.LoaderCallbacks<Cursor> {
    3. ...
    4. // Defines a ListView
    5. private ListView mListView;
    6. // Defines a ContactsAdapter
    7. private ContactsAdapter mAdapter;
    8. ...
    9. // Defines a Cursor to contain the retrieved data
    10. private Cursor mCursor;
    11. /*
    12. * Defines a projection based on platform version. This ensures
    13. * that you retrieve the correct columns.
    14. */
    15. private static final String[] PROJECTION =
    16. {
    17. Contacts._ID,
    18. Contacts.LOOKUP_KEY,
    19. (Build.VERSION.SDK_INT >=
    20. Build.VERSION_CODES.HONEYCOMB) ?
    21. Contacts.DISPLAY_NAME_PRIMARY :
    22. Contacts.DISPLAY_NAME
    23. (Build.VERSION.SDK_INT >=
    24. Build.VERSION_CODES.HONEYCOMB) ?
    25. Contacts.PHOTO_THUMBNAIL_ID :
    26. /*
    27. * Although it's not necessary to include the
    28. * column twice, this keeps the number of
    29. * columns the same regardless of version
    30. */
    31. Contacts_ID
    32. ...
    33. };
    34. /*
    35. * As a shortcut, defines constants for the
    36. * column indexes in the Cursor. The index is
    37. * 0-based and always matches the column order
    38. * in the projection.
    39. */
    40. // Column index of the _ID column
    41. private int mIdIndex = 0;
    42. // Column index of the LOOKUP_KEY column
    43. private int mLookupKeyIndex = 1;
    44. // Column index of the display name column
    45. private int mDisplayNameIndex = 3;
    46. /*
    47. * Column index of the photo data column.
    48. * It's PHOTO_THUMBNAIL_URI for Honeycomb and later,
    49. * and _ID for previous versions.
    50. */
    51. private int mPhotoDataIndex =
    52. Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
    53. 3 :
    54. 0;
    55. ...

    在)方法中,实例化自定义的adapter对象,获得一个ListView的handle。

    onActivityCreated())方法中,将ContactsAdapter绑定到ListView。

    1. @Override
    2. public void onActivityCreated(Bundle savedInstanceState) {
    3. ...
    4. // Sets up the adapter for the ListView
    5. mListView.setAdapter(mAdapter);
    6. ...
    7. }
    8. ...

    当获取到一个包含联系人数据的Cursor时(通常在onLoadFinished()的时候),调用swapCursor()把Cursor中的数据绑定到ListView。这将会为联系人列表中的每一项都显示一个QuickContactBadge。

    1. public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    2. // When the loader has completed, swap the cursor into the adapter.
    3. mContactsAdapter.swapCursor(cursor);
    4. }
    1. @Override
    2. public void onLoaderReset(Loader<Cursor> loader) {
    3. // Removes remaining reference to the previous Cursor