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