このページでは、UI に QuickContactBadge
を追加する方法と、UI にデータをバインドする方法について説明します。QuickContactBadge
は、初期状態ではサムネイル画像として表示されるウィジェットです。任意の Bitmap
をサムネイル画像として使用できますが、通常は、連絡先の写真のサムネイル画像からデコードした Bitmap
を使用します。
小さな画像はコントロールとして機能します。ユーザーが画像をタップすると、QuickContactBadge
が以下を含むダイアログに展開されます。
- 大きな画像
- 連絡先に関連付けられた大きな画像。利用可能な画像がない場合は、プレースホルダ グラフィックが表示されます。
- アプリアイコン
- 組み込みアプリで処理できる各詳細データのアプリアイコン。たとえば、連絡先の詳細に 1 つ以上のメールアドレスが含まれている場合、メールアイコンが表示されます。ユーザーがアイコンをタップすると、連絡先のすべてのメールアドレスが表示されます。ユーザーがいずれかのメールアドレスをタップすると、選択したメールアドレス宛てにメールを作成するための画面がメールアプリに表示されます。
QuickContactBadge
ビューを使用すると、連絡先の詳細にすばやくアクセスして、連絡先とすばやくコミュニケーションできます。ユーザーは、連絡先を検索で見つけた情報をコピーして、使用するアプリに貼り付ける必要はありません。代わりに、QuickContactBadge
をタップして使用する連絡方法を選択し、その情報を使用するアプリに直接送信できます。
QuickContactBadge ビューを追加する
QuickContactBadge
を追加するには、次の例に示すように、レイアウトに <QuickContactBadge>
要素を挿入します。
<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>
プロバイダ データを取得する
QuickContactBadge
に連絡先を表示するには、連絡先のコンテンツ URI と小さい画像の Bitmap
が必要です。コンテンツ URI と Bitmap
は、連絡先プロバイダから取得した列から生成します。データを Cursor
にロードする際に使用する代替表記の一部として、上記の列を指定します。
Android 3.0(API レベル 11)以降の場合は、プロジェクションに次の列を含めます。
Android 2.3.3(API レベル 10)以前の場合は、次の列を使用します。
このページの例では、これらの列と選択した他の列を含む Cursor
が読み込まれていることを前提としています。Cursor
で列を取得する方法については、連絡先のリストを取得するをご覧ください。
連絡先 URI とサムネイルを設定する
必要な列を取得したら、データを QuickContactBadge
にバインドできます。
連絡先 URI を設定する
連絡先のコンテンツ URI を設定するには、getLookupUri(id,lookupKey)
を呼び出して CONTENT_LOOKUP_URI
を取得し、assignContactUri()
を呼び出して連絡先を設定します。次の例に示します。
Kotlin
// The Cursor that contains contact rows var cursor: Cursor? = null // The index of the _ID column in the Cursor var idColumn: Int = 0 // The index of the LOOKUP_KEY column in the Cursor var lookupKeyColumn: Int = 0 // A content URI for the desired contact var contactUri: Uri? = null // A handle to the QuickContactBadge view ... cursor?.let { cursor -> /* * Insert code here to move to the desired cursor row */ // Gets the _ID column index idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID) // Gets the LOOKUP_KEY index lookupKeyColumn = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY) // Gets a content URI for the contact contactUri = ContactsContract.Contacts.getLookupUri( cursor.getLong(idColumn), cursor.getString(lookupKeyColumn) ) binding.badge.assignContactUri(contactUri) }
Java
// The Cursor that contains contact rows Cursor cursor; // The index of the _ID column in the Cursor int idColumn; // The index of the LOOKUP_KEY column in the Cursor int lookupKeyColumn; // A content URI for the desired contact Uri contactUri; ... /* * Insert code here to move to the desired cursor row */ // Gets the _ID column index idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID); // Gets the LOOKUP_KEY index lookupKeyColumn = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY); // Gets a content URI for the contact contactUri = Contacts.getLookupUri( cursor.getLong(idColumn), cursor.getString(lookupKeyColumn) ); binding.badge.assignContactUri(contactUri);
ユーザーが QuickContactBadge
アイコンをタップすると、連絡先の詳細がダイアログに表示されます。
写真のサムネイルを設定する
QuickContactBadge
用の連絡先 URI を設定しても、連絡先のサムネイル写真が自動的にロードされることはありません。写真をロードするには、連絡先の Cursor
行から写真の URI を取得し、それを使用して圧縮サムネイル写真を含むファイルを開き、そのファイルを Bitmap
に読み取ります。
注: PHOTO_THUMBNAIL_URI
列は、3.0 以前のバージョンのプラットフォームでは利用できません。このようなバージョンの場合、Contacts.Photo
サブテーブルから URI を取得する必要があります。
まず、Contacts._ID
列と Contacts.LOOKUP_KEY
列を含む Cursor
にアクセスするための変数を設定します。
Kotlin
// The column in which to find the thumbnail ID var thumbnailColumn: Int = 0 /* * The thumbnail URI, expressed as a String. * Contacts Provider stores URIs as String values. */ var thumbnailUri: String? = null ... cursor?.let { cursor -> /* * Gets the photo thumbnail column index if * platform version >= Honeycomb */ thumbnailColumn = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI) // Otherwise, sets the thumbnail column to the _ID column } else { idColumn } /* * Assuming the current Cursor position is the contact you want, * gets the thumbnail ID */ thumbnailUri = cursor.getString(thumbnailColumn) }
Java
// The column in which to find the thumbnail ID int thumbnailColumn; /* * The thumbnail URI, expressed as a String. * Contacts Provider stores URIs as String values. */ String thumbnailUri; ... /* * Gets the photo thumbnail column index if * platform version >= Honeycomb */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { thumbnailColumn = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI); // Otherwise, sets the thumbnail column to the _ID column } else { thumbnailColumn = idColumn; } /* * Assuming the current Cursor position is the contact you want, * gets the thumbnail ID */ thumbnailUri = cursor.getString(thumbnailColumn); ...
連絡先の写真関連データと、宛先ビューのサイズを取得し、適切なサイズのサムネイルを Bitmap
に返すメソッドを定義します。まず、サムネイルをポイントする URI を作成するところから始めます。
Kotlin
/** * 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 fun loadContactPhotoThumbnail(photoData: String): Bitmap? { // Creates an asset file descriptor for the thumbnail file var afd: AssetFileDescriptor? = null // try-catch block for file not found try { // Creates a holder for the URI val thumbUri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // If Android 3.0 or later, // sets the URI from the incoming PHOTO_THUMBNAIL_URI 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) */ val contactUri: Uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, photoData) /* * Creates a photo URI by appending the content URI of * Contacts.Photo */ Uri.withAppendedPath(contactUri, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY) } /* * Retrieves an AssetFileDescriptor object for the thumbnail URI * using ContentResolver.openAssetFileDescriptor */ afd = activity?.contentResolver?.openAssetFileDescriptor(thumbUri, "r") /* * Gets a file descriptor from the asset file descriptor. * This object can be used across processes. */ return afd?.fileDescriptor?.let {fileDescriptor -> // Decodes the photo file and returns the result as a Bitmap // if the file descriptor is valid BitmapFactory.decodeFileDescriptor(fileDescriptor, null, null) } } catch (e: FileNotFoundException) { /* * Handle file not found errors */ null } finally { // In all cases, close the asset file descriptor try { afd?.close() } catch (e: IOException) { } } }
Java
/** * 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( ContactsContract.Contacts.CONTENT_URI, photoData); /* * Creates a photo URI by appending the content URI of * Contacts.Photo */ thumbUri = Uri.withAppendedPath( contactUri, ContactsContract.Contacts.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(); // Decodes the photo file and returns 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; }
コードで loadContactPhotoThumbnail()
メソッドを呼び出してサムネイル Bitmap
を取得し、その結果を使用して QuickContactBadge
で写真のサムネイルを設定します。
Kotlin
... /* * Decodes the thumbnail file to a Bitmap */ mThumbnailUri?.also { thumbnailUri -> loadContactPhotoThumbnail(thumbnailUri).also { thumbnail -> /* * Sets the image in the QuickContactBadge. * QuickContactBadge inherits from ImageView. */ badge.setImageBitmap(thumbnail) } }
Java
... /* * Decodes the thumbnail file to a Bitmap */ Bitmap mThumbnail = loadContactPhotoThumbnail(thumbnailUri); /* * Sets the image in the QuickContactBadge. * QuickContactBadge inherits from ImageView. */ badge.setImageBitmap(mThumbnail);
QuickContactBadge を ListView に追加する
QuickContactBadge
は、連絡先のリストを表示する ListView
に追加された便利な機能です。QuickContactBadge
を使用すると、各連絡先のサムネイル写真を表示できます。ユーザーがサムネイルをタップすると、QuickContactBadge
ダイアログが表示されます。
QuickContactBadge 要素を追加する
まず、アイテム レイアウトに QuickContactBadge
ビュー要素を追加します。たとえば、QuickContactBadge
と、取得する各連絡先の名前を表示する場合は、レイアウト ファイル内に次の XML を配置します。
<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>
以降のセクションでは、このファイルを contact_item_layout.xml
と呼びます。
カスタム CursorAdapter をセットアップする
QuickContactBadge
を含む ListView
に CursorAdapter
をバインドするには、CursorAdapter
を拡張するカスタム アダプターを定義します。このアプローチにより、QuickContactBadge
にバインドする前に Cursor
内のデータを処理できるようになります。また、このアプローチでは、複数の Cursor
列を QuickContactBadge
にバインドすることもできます。通常の CursorAdapter
では、これらの操作はどちらも実行できません。
定義する CursorAdapter
のサブクラスは、次のメソッドをオーバーライドする必要があります。
CursorAdapter.newView()
-
新しい
View
オブジェクトをインフレートして、アイテム レイアウトを保持します。このメソッドのオーバーライドで、レイアウトの子View
オブジェクト(子QuickContactBadge
を含む)のハンドルを格納します。このアプローチにより、新しいレイアウトをインフレートするたびに子View
オブジェクトのハンドルを取得する必要がなくなります。子
View
オブジェクトのハンドルを個々に取得できるように、このメソッドをオーバーライドする必要があります。この方法により、CursorAdapter.bindView()
のバインディングを制御できます。 CursorAdapter.bindView()
- 現在の
Cursor
行からアイテム レイアウトの子View
オブジェクトにデータを移動します。連絡先 URI とサムネイルの両方をQuickContactBadge
にバインドできるように、このメソッドをオーバーライドする必要があります。デフォルトの実装では、列とView
との間で 1 対 1 のマッピングしか許可されません。
次のコード スニペットには、CursorAdapter
のカスタム サブクラスの例が含まれています。
カスタム リスト アダプターを定義する
コンストラクタを含む CursorAdapter
のサブクラスを定義し、newView()
と bindView()
をオーバーライドします。
Kotlin
/** * Defines a class that holds resource IDs of each item layout * row to prevent having to look them up each time data is * bound to a row */ private data class ViewHolder( internal var displayname: TextView? = null, internal var quickcontact: QuickContactBadge? = null ) /** * * */ private inner class ContactsAdapter( context: Context, val inflater: LayoutInflater = LayoutInflater.from(context) ) : CursorAdapter(context, null, 0) { ... override fun newView( context: Context, cursor: Cursor, viewGroup: ViewGroup ): View { /* Inflates the item layout. Stores view references * in a ViewHolder class to prevent having to look * them up each time bindView() is called. */ return ContactListLayoutBinding.inflate(inflater, viewGroup, false).also { binding -> view.tag = ViewHolder().apply { displayname = binding.displayname quickcontact = binding.quickcontact } }.root } ... override fun bindView(view: View?, context: Context?, cursor: Cursor?) { (view?.tag as? ViewHolder)?.also { holder -> cursor?.apply { ... // Sets the display name in the layout holder.displayname?.text = getString(displayNameIndex) ... /* * Generates a contact URI for the QuickContactBadge */ ContactsContract.Contacts.getLookupUri( getLong(idIndex), cursor.getString(lookupKeyIndex) ).also { contactUri -> holder.quickcontact?.assignContactUri(contactUri) } getString(photoDataIndex)?.also {photoData -> /* * Decodes the thumbnail file to a Bitmap. * The method loadContactPhotoThumbnail() is defined * in the section "Set the contact URI and thumbnail." */ loadContactPhotoThumbnail(photoData)?.also { thumbnailBitmap -> /* * Sets the image in the QuickContactBadge. * QuickContactBadge inherits from ImageView. */ holder.quickcontact?.setImageBitmap(thumbnailBitmap) } } } } } }
Java
private class ContactsAdapter extends CursorAdapter { private LayoutInflater inflater; ... public ContactsAdapter(Context context) { super(context, null, 0); /* * Gets an inflater that can instantiate * the ListView layout from the file */ inflater = LayoutInflater.from(context); ... } ... /** * Defines a class that holds 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 view references * in a ViewHolder class to prevent having to look * them up each time bindView() is called. */ final ContactListLayoutBinding binding = ContactListLayoutBinding.inflate(inflater, viewGroup, false); final ViewHolder holder = new ViewHolder(); holder.displayname = binding.displayName; holder.quickcontact = binding.quickContact; view.setTag(holder); return binding.root; } ... @Override public void bindView( View view, Context context, Cursor cursor) { final ViewHolder holder = (ViewHolder) view.getTag(); final String photoData = cursor.getString(photoDataIndex); final String displayName = cursor.getString(displayNameIndex); ... // Sets the display name in the layout holder.displayname = cursor.getString(displayNameIndex); ... /* * Generates a contact URI for the QuickContactBadge */ final Uri contactUri = Contacts.getLookupUri( cursor.getLong(idIndex), cursor.getString(lookupKeyIndex)); holder.quickcontact.assignContactUri(contactUri); String photoData = cursor.getString(photoDataIndex); /* * 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); }
変数をセットアップする
次の例のように、必要な列を含む Cursor
プロジェクションなどの変数をコードで設定します。
注: 次のコード スニペットは、連絡先 URI とサムネイルを設定するで定義した loadContactPhotoThumbnail()
メソッドを使用しています。
Kotlin
/* * Defines a projection based on platform version. This ensures * that you retrieve the correct columns. */ private val PROJECTION: Array<out String> = arrayOf( ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { ContactsContract.Contacts.DISPLAY_NAME_PRIMARY } else { ContactsContract.Contacts.DISPLAY_NAME }, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { ContactsContract.Contacts.PHOTO_FILE_ID } else { /* * Although it's not necessary to include the * column twice, this keeps the number of * columns the same regardless of version */ ContactsContract.Contacts._ID } ) ... class ContactsFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> { ... // Defines a ListView private val listView: ListView? = null // Defines a ContactsAdapter private val adapter: ContactsAdapter? = null ... // Defines a Cursor to contain the retrieved data private val cursor: Cursor? = null /* * 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 val idIndex = 0 // Column index of the LOOKUP_KEY column private val lookupKeyIndex = 1 // Column index of the display name column private val displayNameIndex = 3 /* * Column index of the photo data column. * It's PHOTO_THUMBNAIL_URI for Honeycomb and later, * and _ID for previous versions. */ private val photoDataIndex: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) 3 else 0 ...
Java
public class ContactsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> { ... // Defines a ListView private ListView listView; // Defines a ContactsAdapter private ContactsAdapter adapter; ... // Defines a Cursor to contain the retrieved data private Cursor cursor; /* * Defines a projection based on platform version. This ensures * that you retrieve the correct columns. */ private static final String[] PROJECTION = { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY, (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? ContactsContract.Contacts.DISPLAY_NAME_PRIMARY : ContactsContract.Contacts.DISPLAY_NAME (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? ContactsContract.Contacts.PHOTO_FILE_ID : /* * Although it's not necessary to include the * column twice, this keeps the number of * columns the same regardless of version */ ContactsContract.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 idIndex = 0; // Column index of the LOOKUP_KEY column private int lookupKeyIndex = 1; // Column index of the display name column private int displayNameIndex = 3; /* * Column index of the photo data column. * It's PHOTO_THUMBNAIL_URI for Honeycomb and later, * and _ID for previous versions. */ private int photoDataIndex = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? 3 : 0; ...
ListView をセットアップする
Fragment.onCreate()
内で、カスタム カーソル アダプターをインスタンス化して、ListView
のハンドルを取得します。
Kotlin
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return FragmentListViewBinding.inflate(...).let { binding -> ... /* * Gets a handle to the ListView in the file * contact_list_layout.xml */ listView = binding.contactList mAdapter?.also { listView?.adapter = it } ... }.root } ...
Java
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { FragmentListViewBinding binding = FragmentListViewBinding.inflate(...) ... /* * Gets a handle to the ListView in the file * contact_list_layout.xml */ if (binding.contactListView != null && adapter != null) { binding.contactListView.setAdapter(adapter); } ... } ...
onViewCreated()
内で、ContactsAdapter
を ListView
にバインドします。
Kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) /* * Instantiates the subclass of * CursorAdapter */ mAdapter = activity?.let { ContactsAdapter(it).also { adapter -> // Sets up the adapter for the ListView listView?.adapter = adapter } } }
Java
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { ... /* * Instantiates the subclass of * CursorAdapter */ mAdapter = new ContactsAdapter(getActivity()); // Sets up the adapter for the ListView if (listView != null && mAdapter != null) { listView.setAdapter(mAdapter); } ... } ...
連絡先データを含む Cursor
が返されたら、通常は onLoadFinished()
内で、swapCursor()
を呼び出して Cursor
データを ListView
に移動します。これにより、連絡先リストの各エントリの QuickContactBadge
が表示されます。
Kotlin
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) { // When the loader has completed, swap the cursor into the adapter mAdapter?.swapCursor(cursor) }
Java
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // When the loader has completed, swap the cursor into the adapter mAdapter.swapCursor(cursor); }
Cursor
を ListView
と CursorAdapter
(またはサブクラス)にバインドし、CursorLoader
を使用して Cursor
をロードする場合は、必ず、onLoaderReset()
実装内にある Cursor
への参照を削除してください。次の例に示します。
Kotlin
override fun onLoaderReset(loader: Loader<Cursor>) { // Removes remaining reference to the previous Cursor adapter?.swapCursor(null) }
Java
@Override public void onLoaderReset(Loader<Cursor> loader) { // Removes remaining reference to the previous Cursor adapter.swapCursor(null); }