page.title=Displaying the Quick Contact Badge trainingnavtop=true @jd:body
ContactsList.zip
This lesson shows you how to add a {@link android.widget.QuickContactBadge} to your UI and how to bind data to it. A {@link android.widget.QuickContactBadge} is a widget that initially appears as a thumbnail image. Although you can use any {@link android.graphics.Bitmap} for the thumbnail image, you usually use a {@link android.graphics.Bitmap} decoded from the contact's photo thumbnail image.
The small image acts as a control; when users click on the image, the {@link android.widget.QuickContactBadge} expands into a dialog containing the following:
The {@link android.widget.QuickContactBadge} view provides instant access to a contact's details, as well as a fast way of communicating with the contact. Users don't have to look up a contact, find and copy information, and then paste it into the appropriate app. Instead, they can click on the {@link android.widget.QuickContactBadge}, choose the communication method they want to use, and send the information for that method directly to the appropriate app.
To add a {@link android.widget.QuickContactBadge}, insert a
<QuickContactBadge>
element in your layout. For example:
<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>
To display a contact in the {@link android.widget.QuickContactBadge}, you need a content URI for the contact and a {@link android.graphics.Bitmap} for the small image. You generate both the content URI and the {@link android.graphics.Bitmap} from columns retrieved from the Contacts Provider. Specify these columns as part of the projection you use to load data into your {@link android.database.Cursor}.
For Android 3.0 (API level 11) and later, include the following columns in your projection:
For Android 2.3.3 (API level 10) and earlier, use the following columns:
The remainder of this lesson assumes that you've already loaded a {@link android.database.Cursor} that contains these columns as well as others you may have chosen. To learn how to retrieve this columns in a {@link android.database.Cursor}, read the lesson Retrieving a List of Contacts.
Once you have the necessary columns, you can bind data to the {@link android.widget.QuickContactBadge}.
To set the content URI for the contact, call {@link android.provider.ContactsContract.Contacts#getLookupUri getLookupUri(id,lookupKey)} to get a {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, then call {@link android.widget.QuickContactBadge#assignContactUri assignContactUri()} to set the contact. For example:
// 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);
When users click the {@link android.widget.QuickContactBadge} icon, the contact's details automatically appear in the dialog.
Setting the contact URI for the {@link android.widget.QuickContactBadge} does not automatically load the contact's thumbnail photo. To load the photo, get a URI for the photo from the contact's {@link android.database.Cursor} row, use it to open the file containing the compressed thumbnail photo, and read the file into a {@link android.graphics.Bitmap}.
Note: The {@link android.provider.ContactsContract.Contacts#PHOTO_THUMBNAIL_URI} column isn't available in platform versions prior to 3.0. For those versions, you must retrieve the URI from the {@link android.provider.ContactsContract.Contacts.Photo Contacts.Photo} subtable.
First, set up variables for accessing the {@link android.database.Cursor} containing the {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} and {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY} columns, as described previously:
// 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); ...
Define a method that takes photo-related data for the contact and dimensions for the destination view, and returns the properly-sized thumbnail in a {@link android.graphics.Bitmap}. Start by constructing a URI that points to the thumbnail:
/** * 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; }
Call the loadContactPhotoThumbnail()
method in your code to get the
thumbnail {@link android.graphics.Bitmap}, and use the result to set the photo thumbnail in
your {@link android.widget.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);
A {@link android.widget.QuickContactBadge} is a useful addition to a {@link android.widget.ListView} that displays a list of contacts. Use the {@link android.widget.QuickContactBadge} to display a thumbnail photo for each contact; when users click the thumbnail, the {@link android.widget.QuickContactBadge} dialog appears.
To start, add a {@link android.widget.QuickContactBadge} view element to your item layout For example, if you want to display a {@link android.widget.QuickContactBadge} and a name for each contact you retrieve, put the following XML into a layout file:
<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>
In the following sections, this file is referred to as contact_item_layout.xml
.
To bind a {@link android.support.v4.widget.CursorAdapter} to a {@link android.widget.ListView} containing a {@link android.widget.QuickContactBadge}, define a custom adapter that extends {@link android.support.v4.widget.CursorAdapter}. This approach allows you to process the data in the {@link android.database.Cursor} before you bind it to the {@link android.widget.QuickContactBadge}. This approach also allows you to bind multiple {@link android.database.Cursor} columns to the {@link android.widget.QuickContactBadge}. Neither of these operations is possible in a regular {@link android.support.v4.widget.CursorAdapter}.
The subclass of {@link android.support.v4.widget.CursorAdapter} that you define must override the following methods:
You must override this method so you can get handles to the individual child {@link android.view.View} objects. This technique allows you to control their binding in {@link android.support.v4.widget.CursorAdapter#bindView CursorAdapter.bindView()}.
The following code snippet contains an example of a custom subclass of {@link android.support.v4.widget.CursorAdapter}:
Define the subclass of {@link android.support.v4.widget.CursorAdapter} including its constructor, and override {@link android.support.v4.widget.CursorAdapter#newView newView()} and {@link android.support.v4.widget.CursorAdapter#bindView 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); }
In your code, set up variables, including a {@link android.database.Cursor} projection that includes the necessary columns.
Note: The following code snippets use the method
loadContactPhotoThumbnail()
, which is defined in the section
Set the Contact URI and Thumbnail
For example:
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; ...
In {@link android.support.v4.app.Fragment#onCreate Fragment.onCreate()}, instantiate the custom cursor adapter and get a handle to the {@link android.widget.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); ... } ...
In {@link android.support.v4.app.Fragment#onActivityCreated onActivityCreated()}, bind the
ContactsAdapter
to the {@link android.widget.ListView}:
@Override public void onActivityCreated(Bundle savedInstanceState) { ... // Sets up the adapter for the ListView mListView.setAdapter(mAdapter); ... } ...
When you get back a {@link android.database.Cursor} containing the contacts data, usually in {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()}, call {@link android.support.v4.widget.CursorAdapter#swapCursor swapCursor()} to move the {@link android.database.Cursor} data to the {@link android.widget.ListView}. This displays the {@link android.widget.QuickContactBadge} for each entry in the list of contacts:
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // When the loader has completed, swap the cursor into the adapter. mContactsAdapter.swapCursor(cursor); }
When you bind a {@link android.database.Cursor} to a {@link android.widget.ListView} with a {@link android.support.v4.widget.CursorAdapter} (or subclass), and you use a {@link android.support.v4.content.CursorLoader} to load the {@link android.database.Cursor}, always clear references to the {@link android.database.Cursor} in your implementation of {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()}. For example:
@Override public void onLoaderReset(Loader<Cursor> loader) { // Removes remaining reference to the previous Cursor mContactsAdapter.swapCursor(null); }