This lesson teaches you to
- Add a QuickContactBadge View
- Set the Contact URI and Thumbnail
- Add a QuickContactBadge to a ListView
You should also read
Try it out
ContactsList.zip
This lesson shows you how to add a
QuickContactBadge
to your UI
and how to bind data to it. A
QuickContactBadge
is a widget that
initially appears as a thumbnail image. Although you can use any
Bitmap
for the thumbnail image, you usually use a
Bitmap
decoded from the
contact's photo thumbnail image.
The small image acts as a control; when users click on the image, the
QuickContactBadge
expands into a dialog containing the following:
- A large image
- The large image associated with the contact, or no image is available, a placeholder graphic.
- App icons
- An app icon for each piece of detail data that can be handled by a built-in app. For example, if the contact's details include one or more email addresses, an email icon appears. When users click the icon, all of the contact's email addresses appear. When users click one of the addresses, the email app displays a screen for composing a message to the selected email address.
The
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
QuickContactBadge
, choose the communication method they
want to use, and send the information for that method directly to the appropriate app.
Add a QuickContactBadge View
To add a
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>
Retrieve provider data
To display a contact in the
QuickContactBadge
, you need a content URI
for the contact and a
Bitmap
for the small image. You generate
both the content URI and the
Bitmap
from columns retrieved from the
Contacts Provider. Specify these columns as part of the projection you use to load data into
your
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
Cursor
that contains these columns as well as others you may have
chosen. To learn how to retrieve this columns in a
Cursor
, read the
lesson
Retrieving a List of Contacts
.
Set the Contact URI and Thumbnail
Once you have the necessary columns, you can bind data to the
QuickContactBadge
.
Set the Contact URI
To set the content URI for the contact, call
getLookupUri(id,lookupKey)
to
get a
CONTENT_LOOKUP_URI
, then
call
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
QuickContactBadge
icon, the contact's
details automatically appear in the dialog.
Set the photo thumbnail
Setting the contact URI for the
QuickContactBadge
does not automatically
load the contact's thumbnail photo. To load the photo, get a URI for the photo from the
contact's
Cursor
row, use it to open the file containing the compressed
thumbnail photo, and read the file into a
Bitmap
.
Note:
The
PHOTO_THUMBNAIL_URI
column isn't available
in platform versions prior to 3.0. For those versions, you must retrieve the URI
from the
Contacts.Photo
subtable.
First, set up variables for accessing the
Cursor
containing the
Contacts._ID
and
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
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
Bitmap
, and use the result to set the photo thumbnail in
your
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 QuickContactBadge to a ListView
A
QuickContactBadge
is a useful addition to a
ListView
that displays a list of contacts. Use the
QuickContactBadge
to display a thumbnail photo for each contact; when
users click the thumbnail, the
QuickContactBadge
dialog appears.
Add the QuickContactBadge element
To start, add a
QuickContactBadge
view element to your item layout
For example, if you want to display a
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
.
Set up a custom CursorAdapter
To bind a
CursorAdapter
to a
ListView
containing a
QuickContactBadge
, define a custom adapter that
extends
CursorAdapter
. This approach allows you to process the
data in the
Cursor
before you bind it to the
QuickContactBadge
. This approach also allows you to bind multiple
Cursor
columns to the
QuickContactBadge
. Neither
of these operations is possible in a regular
CursorAdapter
.
The subclass of
CursorAdapter
that you define must
override the following methods:
-
CursorAdapter.newView()
-
Inflates a new
View
object to hold the item layout. In the override of this method, store handles to the childView
objects of the layout, including the childQuickContactBadge
. By taking this approach, you avoid having to get handles to the childView
objects each time you inflate a new layout.You must override this method so you can get handles to the individual child
View
objects. This technique allows you to control their binding inCursorAdapter.bindView()
. -
CursorAdapter.bindView()
-
Moves data from the current
Cursor
row to the childView
objects of the item layout. You must override this method so you can bind both the contact's URI and thumbnail to theQuickContactBadge
. The default implementation only allows a 1-to-1 mapping between a column and aView
The following code snippet contains an example of a custom subclass of
CursorAdapter
:
Define the custom list adapter
Define the subclass of
CursorAdapter
including its
constructor, and override
newView()
and
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); }
Set up variables
In your code, set up variables, including a
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; ...
Set up the ListView
In
Fragment.onCreate()
, instantiate the custom
cursor adapter and get a handle to the
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
onActivityCreated()
, bind the
ContactsAdapter
to the
ListView
:
@Override public void onActivityCreated(Bundle savedInstanceState) { ... // Sets up the adapter for the ListView mListView.setAdapter(mAdapter); ... } ...
When you get back a
Cursor
containing the contacts data, usually in
,
call
swapCursor()
to move the
Cursor
data to the
ListView
. This displays the
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
Cursor
to a
ListView
with a
CursorAdapter
(or subclass), and you use a
CursorLoader
to load the
Cursor
, always clear references to the
Cursor
in your implementation of
.
For example:
@Override public void onLoaderReset(Loader<Cursor> loader) { // Removes remaining reference to the previous Cursor mContactsAdapter.swapCursor(null); }