1 page.title=Displaying the Quick Contact Badge 2 3 trainingnavtop=true 4 @jd:body 5 6 7 <div id="tb-wrapper"> 8 <div id="tb"> 9 10 <!-- table of contents --> 11 <h2>This lesson teaches you to</h2> 12 <ol> 13 <li> 14 <a href="#AddView">Add a QuickContactBadge View</a> 15 </li> 16 <li> 17 <a href="#SetURIThumbnail">Set the Contact URI and Thumbnail</a> 18 </li> 19 <li> 20 <a href="#ListView"> 21 Add a QuickContactBadge to a ListView 22 </a> 23 </li> 24 </ol> 25 26 <!-- other docs (NOT javadocs) --> 27 <h2>You should also read</h2> 28 <ul> 29 <li> 30 <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> 31 Content Provider Basics 32 </a> 33 </li> 34 <li> 35 <a href="{@docRoot}guide/topics/providers/contacts-provider.html"> 36 Contacts Provider 37 </a> 38 </li> 39 </ul> 40 41 <h2>Try it out</h2> 42 43 <div class="download-box"> 44 <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button"> 45 Download the sample 46 </a> 47 <p class="filename">ContactsList.zip</p> 48 </div> 49 50 </div> 51 </div> 52 <p> 53 This lesson shows you how to add a {@link android.widget.QuickContactBadge} to your UI 54 and how to bind data to it. A {@link android.widget.QuickContactBadge} is a widget that 55 initially appears as a thumbnail image. Although you can use any {@link android.graphics.Bitmap} 56 for the thumbnail image, you usually use a {@link android.graphics.Bitmap} decoded from the 57 contact's photo thumbnail image. 58 </p> 59 <p> 60 The small image acts as a control; when users click on the image, the 61 {@link android.widget.QuickContactBadge} expands into a dialog containing the following: 62 </p> 63 <dl> 64 <dt>A large image</dt> 65 <dd> 66 The large image associated with the contact, or no image is available, a placeholder 67 graphic. 68 </dd> 69 <dt> 70 App icons 71 </dt> 72 <dd> 73 An app icon for each piece of detail data that can be handled by a built-in app. For 74 example, if the contact's details include one or more email addresses, an email icon 75 appears. When users click the icon, all of the contact's email addresses appear. When users 76 click one of the addresses, the email app displays a screen for composing a message to the 77 selected email address. 78 </dd> 79 </dl> 80 <p> 81 The {@link android.widget.QuickContactBadge} view provides instant access to a contact's 82 details, as well as a fast way of communicating with the contact. Users don't have to look up 83 a contact, find and copy information, and then paste it into the appropriate app. Instead, they 84 can click on the {@link android.widget.QuickContactBadge}, choose the communication method they 85 want to use, and send the information for that method directly to the appropriate app. 86 </p> 87 <h2 id="AddView">Add a QuickContactBadge View</h2> 88 <p> 89 To add a {@link android.widget.QuickContactBadge}, insert a 90 <code><QuickContactBadge></code> element in your layout. For example: 91 </p> 92 <pre> 93 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 94 android:layout_width="match_parent" 95 android:layout_height="match_parent"> 96 ... 97 <QuickContactBadge 98 android:id=@+id/quickbadge 99 android:layout_height="wrap_content" 100 android:layout_width="wrap_content" 101 android:scaleType="centerCrop"/> 102 ... 103 </RelativeLayout> 104 </pre> 105 <h2 id="">Retrieve provider data</h2> 106 <p> 107 To display a contact in the {@link android.widget.QuickContactBadge}, you need a content URI 108 for the contact and a {@link android.graphics.Bitmap} for the small image. You generate 109 both the content URI and the {@link android.graphics.Bitmap} from columns retrieved from the 110 Contacts Provider. Specify these columns as part of the projection you use to load data into 111 your {@link android.database.Cursor}. 112 </p> 113 <p> 114 For Android 3.0 (API level 11) and later, include the following columns in your projection:</p> 115 <ul> 116 <li>{@link android.provider.ContactsContract.Contacts#_ID Contacts._ID}</li> 117 <li>{@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY}</li> 118 <li> 119 {@link android.provider.ContactsContract.Contacts#PHOTO_THUMBNAIL_URI 120 Contacts.PHOTO_THUMBNAIL_URI} 121 </li> 122 </ul> 123 <p> 124 For Android 2.3.3 (API level 10) and earlier, use the following columns: 125 </p> 126 <ul> 127 <li>{@link android.provider.ContactsContract.Contacts#_ID Contacts._ID}</li> 128 <li>{@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY}</li> 129 </ul> 130 <p> 131 The remainder of this lesson assumes that you've already loaded a 132 {@link android.database.Cursor} that contains these columns as well as others you may have 133 chosen. To learn how to retrieve this columns in a {@link android.database.Cursor}, read the 134 lesson <a href="retrieve-names.html">Retrieving a List of Contacts</a>. 135 </p> 136 <h2 id="SetURIThumbnail">Set the Contact URI and Thumbnail</h2> 137 <p> 138 Once you have the necessary columns, you can bind data to the 139 {@link android.widget.QuickContactBadge}. 140 </p> 141 <h3>Set the Contact URI</h3> 142 <p> 143 To set the content URI for the contact, call 144 {@link android.provider.ContactsContract.Contacts#getLookupUri getLookupUri(id,lookupKey)} to 145 get a {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, then 146 call {@link android.widget.QuickContactBadge#assignContactUri assignContactUri()} to set the 147 contact. For example: 148 </p> 149 <pre> 150 // The Cursor that contains contact rows 151 Cursor mCursor; 152 // The index of the _ID column in the Cursor 153 int mIdColumn; 154 // The index of the LOOKUP_KEY column in the Cursor 155 int mLookupKeyColumn; 156 // A content URI for the desired contact 157 Uri mContactUri; 158 // A handle to the QuickContactBadge view 159 QuickContactBadge mBadge; 160 ... 161 mBadge = (QuickContactBadge) findViewById(R.id.quickbadge); 162 /* 163 * Insert code here to move to the desired cursor row 164 */ 165 // Gets the _ID column index 166 mIdColumn = mCursor.getColumnIndex(Contacts._ID); 167 // Gets the LOOKUP_KEY index 168 mLookupKeyColumn = mCursor.getColumnIndex(Contacts.LOOKUP_KEY); 169 // Gets a content URI for the contact 170 mContactUri = 171 Contacts.getLookupUri( 172 mCursor.getLong(mIdColumn), 173 mCursor.getString(mLookupKeyColumn) 174 ); 175 mBadge.assignContactUri(mContactUri); 176 </pre> 177 <p> 178 When users click the {@link android.widget.QuickContactBadge} icon, the contact's 179 details automatically appear in the dialog. 180 </p> 181 <h3>Set the photo thumbnail</h3> 182 <p> 183 Setting the contact URI for the {@link android.widget.QuickContactBadge} does not automatically 184 load the contact's thumbnail photo. To load the photo, get a URI for the photo from the 185 contact's {@link android.database.Cursor} row, use it to open the file containing the compressed 186 thumbnail photo, and read the file into a {@link android.graphics.Bitmap}. 187 </p> 188 <p class="note"> 189 <strong>Note:</strong> The 190 {@link android.provider.ContactsContract.Contacts#PHOTO_THUMBNAIL_URI} column isn't available 191 in platform versions prior to 3.0. For those versions, you must retrieve the URI 192 from the {@link android.provider.ContactsContract.Contacts.Photo Contacts.Photo} subtable. 193 </p> 194 <p> 195 First, set up variables for accessing the {@link android.database.Cursor} containing the 196 {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} and 197 {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY} columns, as 198 described previously: 199 </p> 200 <pre> 201 // The column in which to find the thumbnail ID 202 int mThumbnailColumn; 203 /* 204 * The thumbnail URI, expressed as a String. 205 * Contacts Provider stores URIs as String values. 206 */ 207 String mThumbnailUri; 208 ... 209 /* 210 * Gets the photo thumbnail column index if 211 * platform version >= Honeycomb 212 */ 213 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 214 mThumbnailColumn = 215 mCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI); 216 // Otherwise, sets the thumbnail column to the _ID column 217 } else { 218 mThumbnailColumn = mIdColumn; 219 } 220 /* 221 * Assuming the current Cursor position is the contact you want, 222 * gets the thumbnail ID 223 */ 224 mThumbnailUri = mCursor.getString(mThumbnailColumn); 225 ... 226 </pre> 227 <p> 228 Define a method that takes photo-related data for the contact and dimensions for the 229 destination view, and returns the properly-sized thumbnail in a 230 {@link android.graphics.Bitmap}. Start by constructing a URI that points to the 231 thumbnail: 232 <p> 233 <pre> 234 /** 235 * Load a contact photo thumbnail and return it as a Bitmap, 236 * resizing the image to the provided image dimensions as needed. 237 * @param photoData photo ID Prior to Honeycomb, the contact's _ID value. 238 * For Honeycomb and later, the value of PHOTO_THUMBNAIL_URI. 239 * @return A thumbnail Bitmap, sized to the provided width and height. 240 * Returns null if the thumbnail is not found. 241 */ 242 private Bitmap loadContactPhotoThumbnail(String photoData) { 243 // Creates an asset file descriptor for the thumbnail file. 244 AssetFileDescriptor afd = null; 245 // try-catch block for file not found 246 try { 247 // Creates a holder for the URI. 248 Uri thumbUri; 249 // If Android 3.0 or later 250 if (Build.VERSION.SDK_INT 251 >= 252 Build.VERSION_CODES.HONEYCOMB) { 253 // Sets the URI from the incoming PHOTO_THUMBNAIL_URI 254 thumbUri = Uri.parse(photoData); 255 } else { 256 // Prior to Android 3.0, constructs a photo Uri using _ID 257 /* 258 * Creates a contact URI from the Contacts content URI 259 * incoming photoData (_ID) 260 */ 261 final Uri contactUri = Uri.withAppendedPath( 262 Contacts.CONTENT_URI, photoData); 263 /* 264 * Creates a photo URI by appending the content URI of 265 * Contacts.Photo. 266 */ 267 thumbUri = 268 Uri.withAppendedPath( 269 contactUri, Photo.CONTENT_DIRECTORY); 270 } 271 272 /* 273 * Retrieves an AssetFileDescriptor object for the thumbnail 274 * URI 275 * using ContentResolver.openAssetFileDescriptor 276 */ 277 afd = getActivity().getContentResolver(). 278 openAssetFileDescriptor(thumbUri, "r"); 279 /* 280 * Gets a file descriptor from the asset file descriptor. 281 * This object can be used across processes. 282 */ 283 FileDescriptor fileDescriptor = afd.getFileDescriptor(); 284 // Decode the photo file and return the result as a Bitmap 285 // If the file descriptor is valid 286 if (fileDescriptor != null) { 287 // Decodes the bitmap 288 return BitmapFactory.decodeFileDescriptor( 289 fileDescriptor, null, null); 290 } 291 // If the file isn't found 292 } catch (FileNotFoundException e) { 293 /* 294 * Handle file not found errors 295 */ 296 } 297 // In all cases, close the asset file descriptor 298 } finally { 299 if (afd != null) { 300 try { 301 afd.close(); 302 } catch (IOException e) {} 303 } 304 } 305 return null; 306 } 307 </pre> 308 <p> 309 Call the <code>loadContactPhotoThumbnail()</code> method in your code to get the 310 thumbnail {@link android.graphics.Bitmap}, and use the result to set the photo thumbnail in 311 your {@link android.widget.QuickContactBadge}: 312 </p> 313 <pre> 314 ... 315 /* 316 * Decodes the thumbnail file to a Bitmap. 317 */ 318 Bitmap mThumbnail = 319 loadContactPhotoThumbnail(mThumbnailUri); 320 /* 321 * Sets the image in the QuickContactBadge 322 * QuickContactBadge inherits from ImageView, so 323 */ 324 mBadge.setImageBitmap(mThumbnail); 325 </pre> 326 <h2 id="ListView">Add a QuickContactBadge to a ListView</h2> 327 <p> 328 A {@link android.widget.QuickContactBadge} is a useful addition to a 329 {@link android.widget.ListView} that displays a list of contacts. Use the 330 {@link android.widget.QuickContactBadge} to display a thumbnail photo for each contact; when 331 users click the thumbnail, the {@link android.widget.QuickContactBadge} dialog appears. 332 </p> 333 <h3>Add the QuickContactBadge element</h3> 334 <p> 335 To start, add a {@link android.widget.QuickContactBadge} view element to your item layout 336 For example, if you want to display a {@link android.widget.QuickContactBadge} and a name for 337 each contact you retrieve, put the following XML into a layout file: 338 </p> 339 <pre> 340 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 341 android:layout_width="match_parent" 342 android:layout_height="wrap_content"> 343 <QuickContactBadge 344 android:id="@+id/quickcontact" 345 android:layout_height="wrap_content" 346 android:layout_width="wrap_content" 347 android:scaleType="centerCrop"/> 348 <TextView android:id="@+id/displayname" 349 android:layout_width="match_parent" 350 android:layout_height="wrap_content" 351 android:layout_toRightOf="@+id/quickcontact" 352 android:gravity="center_vertical" 353 android:layout_alignParentRight="true" 354 android:layout_alignParentTop="true"/> 355 </RelativeLayout> 356 </pre> 357 <p> 358 In the following sections, this file is referred to as <code>contact_item_layout.xml</code>. 359 </p> 360 <h3>Set up a custom CursorAdapter</h3> 361 <p> 362 To bind a {@link android.support.v4.widget.CursorAdapter} to a {@link android.widget.ListView} 363 containing a {@link android.widget.QuickContactBadge}, define a custom adapter that 364 extends {@link android.support.v4.widget.CursorAdapter}. This approach allows you to process the 365 data in the {@link android.database.Cursor} before you bind it to the 366 {@link android.widget.QuickContactBadge}. This approach also allows you to bind multiple 367 {@link android.database.Cursor} columns to the {@link android.widget.QuickContactBadge}. Neither 368 of these operations is possible in a regular {@link android.support.v4.widget.CursorAdapter}. 369 </p> 370 <p> 371 The subclass of {@link android.support.v4.widget.CursorAdapter} that you define must 372 override the following methods: 373 </p> 374 <dl> 375 <dt>{@link android.support.v4.widget.CursorAdapter#newView CursorAdapter.newView()}</dt> 376 <dd> 377 Inflates a new {@link android.view.View} object to hold the item layout. In the override 378 of this method, store handles to the child {@link android.view.View} objects of the layout, 379 including the child {@link android.widget.QuickContactBadge}. By taking this approach, you 380 avoid having to get handles to the child {@link android.view.View} objects each time you 381 inflate a new layout. 382 <p> 383 You must override this method so you can get handles to the individual child 384 {@link android.view.View} objects. This technique allows you to control their binding in 385 {@link android.support.v4.widget.CursorAdapter#bindView CursorAdapter.bindView()}. 386 </p> 387 </dd> 388 <dt>{@link android.support.v4.widget.CursorAdapter#bindView CursorAdapter.bindView()}</dt> 389 <dd> 390 Moves data from the current {@link android.database.Cursor} row to the child 391 {@link android.view.View} objects of the item layout. You must override this method so 392 you can bind both the contact's URI and thumbnail to the 393 {@link android.widget.QuickContactBadge}. The default implementation only allows a 1-to-1 394 mapping between a column and a {@link android.view.View} 395 </dd> 396 </dl> 397 <p> 398 The following code snippet contains an example of a custom subclass of 399 {@link android.support.v4.widget.CursorAdapter}: 400 </p> 401 <h3>Define the custom list adapter</h3> 402 <p> 403 Define the subclass of {@link android.support.v4.widget.CursorAdapter} including its 404 constructor, and override 405 {@link android.support.v4.widget.CursorAdapter#newView newView()} and 406 {@link android.support.v4.widget.CursorAdapter#bindView bindView()}: 407 </p> 408 <pre> 409 /** 410 * 411 * 412 */ 413 private class ContactsAdapter extends CursorAdapter { 414 private LayoutInflater mInflater; 415 ... 416 public ContactsAdapter(Context context) { 417 super(context, null, 0); 418 419 /* 420 * Gets an inflater that can instantiate 421 * the ListView layout from the file. 422 */ 423 mInflater = LayoutInflater.from(context); 424 ... 425 } 426 ... 427 /** 428 * Defines a class that hold resource IDs of each item layout 429 * row to prevent having to look them up each time data is 430 * bound to a row. 431 */ 432 private class ViewHolder { 433 TextView displayname; 434 QuickContactBadge quickcontact; 435 } 436 .. 437 @Override 438 public View newView( 439 Context context, 440 Cursor cursor, 441 ViewGroup viewGroup) { 442 /* Inflates the item layout. Stores resource IDs in a 443 * in a ViewHolder class to prevent having to look 444 * them up each time bindView() is called. 445 */ 446 final View itemView = 447 mInflater.inflate( 448 R.layout.contact_list_layout, 449 viewGroup, 450 false 451 ); 452 final ViewHolder holder = new ViewHolder(); 453 holder.displayname = 454 (TextView) view.findViewById(R.id.displayname); 455 holder.quickcontact = 456 (QuickContactBadge) 457 view.findViewById(R.id.quickcontact); 458 view.setTag(holder); 459 return view; 460 } 461 ... 462 @Override 463 public void bindView( 464 View view, 465 Context context, 466 Cursor cursor) { 467 final ViewHolder holder = (ViewHolder) view.getTag(); 468 final String photoData = 469 cursor.getString(mPhotoDataIndex); 470 final String displayName = 471 cursor.getString(mDisplayNameIndex); 472 ... 473 // Sets the display name in the layout 474 holder.displayname = cursor.getString(mDisplayNameIndex); 475 ... 476 /* 477 * Generates a contact URI for the QuickContactBadge. 478 */ 479 final Uri contactUri = Contacts.getLookupUri( 480 cursor.getLong(mIdIndex), 481 cursor.getString(mLookupKeyIndex)); 482 holder.quickcontact.assignContactUri(contactUri); 483 String photoData = cursor.getString(mPhotoDataIndex); 484 /* 485 * Decodes the thumbnail file to a Bitmap. 486 * The method loadContactPhotoThumbnail() is defined 487 * in the section "Set the Contact URI and Thumbnail" 488 */ 489 Bitmap thumbnailBitmap = 490 loadContactPhotoThumbnail(photoData); 491 /* 492 * Sets the image in the QuickContactBadge 493 * QuickContactBadge inherits from ImageView 494 */ 495 holder.quickcontact.setImageBitmap(thumbnailBitmap); 496 } 497 </pre> 498 499 <h3>Set up variables</h3> 500 <p> 501 In your code, set up variables, including a {@link android.database.Cursor} projection that 502 includes the necessary columns. 503 </p> 504 <p class="note"> 505 <strong>Note:</strong> The following code snippets use the method 506 <code>loadContactPhotoThumbnail()</code>, which is defined in the section 507 <a href="#SetURIThumbnail">Set the Contact URI and Thumbnail</a> 508 </p> 509 <p> 510 For example: 511 </p> 512 <pre> 513 public class ContactsFragment extends Fragment implements 514 LoaderManager.LoaderCallbacks<Cursor> { 515 ... 516 // Defines a ListView 517 private ListView mListView; 518 // Defines a ContactsAdapter 519 private ContactsAdapter mAdapter; 520 ... 521 // Defines a Cursor to contain the retrieved data 522 private Cursor mCursor; 523 /* 524 * Defines a projection based on platform version. This ensures 525 * that you retrieve the correct columns. 526 */ 527 private static final String[] PROJECTION = 528 { 529 Contacts._ID, 530 Contacts.LOOKUP_KEY, 531 (Build.VERSION.SDK_INT >= 532 Build.VERSION_CODES.HONEYCOMB) ? 533 Contacts.DISPLAY_NAME_PRIMARY : 534 Contacts.DISPLAY_NAME 535 (Build.VERSION.SDK_INT >= 536 Build.VERSION_CODES.HONEYCOMB) ? 537 Contacts.PHOTO_THUMBNAIL_ID : 538 /* 539 * Although it's not necessary to include the 540 * column twice, this keeps the number of 541 * columns the same regardless of version 542 */ 543 Contacts_ID 544 ... 545 }; 546 /* 547 * As a shortcut, defines constants for the 548 * column indexes in the Cursor. The index is 549 * 0-based and always matches the column order 550 * in the projection. 551 */ 552 // Column index of the _ID column 553 private int mIdIndex = 0; 554 // Column index of the LOOKUP_KEY column 555 private int mLookupKeyIndex = 1; 556 // Column index of the display name column 557 private int mDisplayNameIndex = 3; 558 /* 559 * Column index of the photo data column. 560 * It's PHOTO_THUMBNAIL_URI for Honeycomb and later, 561 * and _ID for previous versions. 562 */ 563 private int mPhotoDataIndex = 564 Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? 565 3 : 566 0; 567 ... 568 </pre> 569 <h3>Set up the ListView</h3> 570 <p> 571 In {@link android.support.v4.app.Fragment#onCreate Fragment.onCreate()}, instantiate the custom 572 cursor adapter and get a handle to the {@link android.widget.ListView}: 573 </p> 574 <pre> 575 @Override 576 public void onCreate(Bundle savedInstanceState) { 577 ... 578 /* 579 * Instantiates the subclass of 580 * CursorAdapter 581 */ 582 ContactsAdapter mContactsAdapter = 583 new ContactsAdapter(getActivity()); 584 /* 585 * Gets a handle to the ListView in the file 586 * contact_list_layout.xml 587 */ 588 mListView = (ListView) findViewById(R.layout.contact_list_layout); 589 ... 590 } 591 ... 592 </pre> 593 <p> 594 In {@link android.support.v4.app.Fragment#onActivityCreated onActivityCreated()}, bind the 595 <code>ContactsAdapter</code> to the {@link android.widget.ListView}: 596 </p> 597 <pre> 598 @Override 599 public void onActivityCreated(Bundle savedInstanceState) { 600 ... 601 // Sets up the adapter for the ListView 602 mListView.setAdapter(mAdapter); 603 ... 604 } 605 ... 606 </pre> 607 <p> 608 When you get back a {@link android.database.Cursor} containing the contacts data, usually in 609 {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()}, 610 call {@link android.support.v4.widget.CursorAdapter#swapCursor swapCursor()} to move the 611 {@link android.database.Cursor} data to the {@link android.widget.ListView}. This displays the 612 {@link android.widget.QuickContactBadge} for each entry in the list of contacts: 613 </p> 614 <pre> 615 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 616 // When the loader has completed, swap the cursor into the adapter. 617 mContactsAdapter.swapCursor(cursor); 618 } 619 </pre> 620 <p> 621 When you bind a {@link android.database.Cursor} to a 622 {@link android.widget.ListView} with a {@link android.support.v4.widget.CursorAdapter} 623 (or subclass), and you use a {@link android.support.v4.content.CursorLoader} to load the 624 {@link android.database.Cursor}, always clear references to the {@link android.database.Cursor} 625 in your implementation of 626 {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()}. 627 For example: 628 </p> 629 <pre> 630 @Override 631 public void onLoaderReset(Loader<Cursor> loader) { 632 // Removes remaining reference to the previous Cursor 633 mContactsAdapter.swapCursor(null); 634 } 635 </pre> 636