1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
|
page.title=Displaying the Quick Contact Badge
trainingnavtop=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<!-- table of contents -->
<h2>This lesson teaches you to</h2>
<ol>
<li>
<a href="#AddView">Add a QuickContactBadge View</a>
</li>
<li>
<a href="#SetURIThumbnail">Set the Contact URI and Thumbnail</a>
</li>
<li>
<a href="#ListView">
Add a QuickContactBadge to a ListView
</a>
</li>
</ol>
<!-- other docs (NOT javadocs) -->
<h2>You should also read</h2>
<ul>
<li>
<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
Content Provider Basics
</a>
</li>
<li>
<a href="{@docRoot}guide/topics/providers/contacts-provider.html">
Contacts Provider
</a>
</li>
</ul>
<h2>Try it out</h2>
<div class="download-box">
<a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button">
Download the sample
</a>
<p class="filename">ContactsList.zip</p>
</div>
</div>
</div>
<p>
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.
</p>
<p>
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:
</p>
<dl>
<dt>A large image</dt>
<dd>
The large image associated with the contact, or no image is available, a placeholder
graphic.
</dd>
<dt>
App icons
</dt>
<dd>
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.
</dd>
</dl>
<p>
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.
</p>
<h2 id="AddView">Add a QuickContactBadge View</h2>
<p>
To add a {@link android.widget.QuickContactBadge}, insert a
<code><QuickContactBadge></code> element in your layout. For example:
</p>
<pre>
<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>
</pre>
<h2 id="">Retrieve provider data</h2>
<p>
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}.
</p>
<p>
For Android 3.0 (API level 11) and later, include the following columns in your projection:</p>
<ul>
<li>{@link android.provider.ContactsContract.Contacts#_ID Contacts._ID}</li>
<li>{@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY}</li>
<li>
{@link android.provider.ContactsContract.Contacts#PHOTO_THUMBNAIL_URI
Contacts.PHOTO_THUMBNAIL_URI}
</li>
</ul>
<p>
For Android 2.3.3 (API level 10) and earlier, use the following columns:
</p>
<ul>
<li>{@link android.provider.ContactsContract.Contacts#_ID Contacts._ID}</li>
<li>{@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY}</li>
</ul>
<p>
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 <a href="retrieve-names.html">Retrieving a List of Contacts</a>.
</p>
<h2 id="SetURIThumbnail">Set the Contact URI and Thumbnail</h2>
<p>
Once you have the necessary columns, you can bind data to the
{@link android.widget.QuickContactBadge}.
</p>
<h3>Set the Contact URI</h3>
<p>
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:
</p>
<pre>
// 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);
</pre>
<p>
When users click the {@link android.widget.QuickContactBadge} icon, the contact's
details automatically appear in the dialog.
</p>
<h3>Set the photo thumbnail</h3>
<p>
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}.
</p>
<p class="note">
<strong>Note:</strong> 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.
</p>
<p>
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:
</p>
<pre>
// 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);
...
</pre>
<p>
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:
<p>
<pre>
/**
* 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;
}
</pre>
<p>
Call the <code>loadContactPhotoThumbnail()</code> 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}:
</p>
<pre>
...
/*
* 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);
</pre>
<h2 id="ListView">Add a QuickContactBadge to a ListView</h2>
<p>
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.
</p>
<h3>Add the QuickContactBadge element</h3>
<p>
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:
</p>
<pre>
<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>
</pre>
<p>
In the following sections, this file is referred to as <code>contact_item_layout.xml</code>.
</p>
<h3>Set up a custom CursorAdapter</h3>
<p>
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}.
</p>
<p>
The subclass of {@link android.support.v4.widget.CursorAdapter} that you define must
override the following methods:
</p>
<dl>
<dt>{@link android.support.v4.widget.CursorAdapter#newView CursorAdapter.newView()}</dt>
<dd>
Inflates a new {@link android.view.View} object to hold the item layout. In the override
of this method, store handles to the child {@link android.view.View} objects of the layout,
including the child {@link android.widget.QuickContactBadge}. By taking this approach, you
avoid having to get handles to the child {@link android.view.View} objects each time you
inflate a new layout.
<p>
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()}.
</p>
</dd>
<dt>{@link android.support.v4.widget.CursorAdapter#bindView CursorAdapter.bindView()}</dt>
<dd>
Moves data from the current {@link android.database.Cursor} row to the child
{@link android.view.View} objects of the item layout. You must override this method so
you can bind both the contact's URI and thumbnail to the
{@link android.widget.QuickContactBadge}. The default implementation only allows a 1-to-1
mapping between a column and a {@link android.view.View}
</dd>
</dl>
<p>
The following code snippet contains an example of a custom subclass of
{@link android.support.v4.widget.CursorAdapter}:
</p>
<h3>Define the custom list adapter</h3>
<p>
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()}:
</p>
<pre>
/**
*
*
*/
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);
}
</pre>
<h3>Set up variables</h3>
<p>
In your code, set up variables, including a {@link android.database.Cursor} projection that
includes the necessary columns.
</p>
<p class="note">
<strong>Note:</strong> The following code snippets use the method
<code>loadContactPhotoThumbnail()</code>, which is defined in the section
<a href="#SetURIThumbnail">Set the Contact URI and Thumbnail</a>
</p>
<p>
For example:
</p>
<pre>
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;
...
</pre>
<h3>Set up the ListView</h3>
<p>
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}:
</p>
<pre>
@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);
...
}
...
</pre>
<p>
In {@link android.support.v4.app.Fragment#onActivityCreated onActivityCreated()}, bind the
<code>ContactsAdapter</code> to the {@link android.widget.ListView}:
</p>
<pre>
@Override
public void onActivityCreated(Bundle savedInstanceState) {
...
// Sets up the adapter for the ListView
mListView.setAdapter(mAdapter);
...
}
...
</pre>
<p>
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:
</p>
<pre>
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// When the loader has completed, swap the cursor into the adapter.
mContactsAdapter.swapCursor(cursor);
}
</pre>
<p>
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:
</p>
<pre>
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// Removes remaining reference to the previous Cursor
mContactsAdapter.swapCursor(null);
}
</pre>
|