Cómo mostrar la insignia de Contacto rápido

En esta lección, se muestra cómo agregar una QuickContactBadge a tu IU y cómo vincular datos a ella. Una QuickContactBadge es un widget que aparece inicialmente como una imagen en miniatura. Si bien puedes usar cualquier Bitmap para la imagen en miniatura, por lo general, se suele usar un Bitmap decodificado de la imagen en miniatura de la foto del contacto.

La imagen pequeña actúa como un control: cuando el usuario hace clic en ella, la QuickContactBadge se expande en un diálogo que contiene lo siguiente:

Una imagen grande
Es la imagen grande asociada con el contacto o, si no hay una imagen disponible, un gráfico marcador de posición.
Íconos de apps
Corresponde a un ícono de la app para cada porción de datos detallados que una app incorporada puede administrar. Por ejemplo, si los detalles del contacto incluyen una o varias direcciones de correo electrónico, aparecerá un ícono de correo electrónico. Cuando los usuarios hacen clic en él, aparecen todas las direcciones de correo electrónico del contacto. Asimismo, cuando hacen clic en una de las direcciones, la app de correo electrónico muestra una pantalla para redactar un mensaje que se enviará a la dirección seleccionada.

En la vista QuickContactBadge, se proporciona acceso instantáneo a los detalles de un contacto, además de una manera rápida de comunicarse con él. Los usuarios no necesitan buscar un contacto ni buscar y copiar información, y luego pegarla en la app correspondiente. En su lugar, pueden hacer clic en la QuickContactBadge, seleccionar el método de comunicación que quieran usar y enviar la información del método directamente a la app correspondiente.

Cómo agregar una vista QuickContactBadge

Para agregar una QuickContactBadge, inserta un elemento <QuickContactBadge> en tu diseño. Por ejemplo:

    <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>
    

Cómo obtener los datos del proveedor

Si quieres mostrar un contacto en la QuickContactBadge, necesitas un URI de contenido para el contacto y un Bitmap para la imagen pequeña. Debes usar las columnas que obtuviste del Proveedor de contactos para generar el URI de contenido y el Bitmap. Especifícalas como parte de la proyección que usas para cargar datos en tu Cursor.

En el caso de Android 3.0 (API nivel 11) y versiones posteriores, incluye las siguientes columnas en tu proyección:

En el caso de Android 2.3.3 (API nivel 10) y versiones anteriores, usa las siguientes columnas:

En el resto de la lección, se asume que ya cargaste un Cursor que contiene estas columnas y otras que hayas seleccionado. Para obtener información sobre cómo obtener estas columnas en un Cursor, lee la lección Cómo obtener una lista de contactos.

Cómo configurar el URI y la miniatura del contacto

Cuando tengas las columnas necesarias, puedes vincular los datos a la QuickContactBadge.

Cómo configurar el URI de contacto

A fin de configurar el URI de contenido del contacto, llama a getLookupUri(id,lookupKey) para obtener un CONTENT_LOOKUP_URI y luego establece el contacto con assignContactUri(). Por ejemplo:

Kotlin

        // The Cursor that contains contact rows
        var mCursor: 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
        lateinit var mBadge: QuickContactBadge
        ...
        mBadge = findViewById(R.id.quickbadge)

        mCursor?.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)
            )
            mBadge.assignContactUri(contactUri)
        }
    

Java

        // The Cursor that contains contact rows
        Cursor mCursor;
        // 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;
        // 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
        idColumn = mCursor.getColumnIndex(ContactsContract.Contacts._ID);
        // Gets the LOOKUP_KEY index
        lookupKeyColumn = mCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
        // Gets a content URI for the contact
        contactUri =
                Contacts.getLookupUri(
                    mCursor.getLong(idColumn),
                    mCursor.getString(lookupKeyColumn)
                );
        mBadge.assignContactUri(contactUri);
    

Cuando los usuarios hacen clic en el ícono de QuickContactBadge, los detalles del contacto aparecen automáticamente en el diálogo.

Cómo configurar la miniatura de la foto

Cuando configuras el URI del contacto de la QuickContactBadge, la foto en miniatura del contacto no se carga automáticamente. Si quieres cargarla, deberás obtener un URI de la fila del Cursor del contacto, usarlo para abrir el archivo que contiene la foto en miniatura comprimida y, luego, leer el archivo en un Bitmap.

Nota: La columna PHOTO_THUMBNAIL_URI no está disponible en las versiones de la plataforma anteriores a la 3.0. En el caso de esas versiones, deberás obtener el URI de la subtabla Contacts.Photo.

Primero, configura las variables para acceder al Cursor que contiene las columnas Contacts._ID y Contacts.LOOKUP_KEY, según lo descrito arriba.

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
        ...
        mCursor?.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);
        ...
    

Define un método que tome datos relacionados con una foto del contacto y las dimensiones de la vista de destino y que muestre la miniatura en un Bitmap con un tamaño adecuado. Comienza por crear un URI que apunte a la miniatura:

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 ->
                    // Decode the photo file and return 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();
            // 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;
        }
    

Llama al método loadContactPhotoThumbnail() en tu código para obtener la miniatura Bitmap y usa el resultado para establecer la miniatura de la foto en tu 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, so
                 */
                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, so
         */
        badge.setImageBitmap(mThumbnail);
    

Cómo agregar una QuickContactBadge a una ListView

Una QuickContactBadge es una adición útil a una ListView que muestra una lista de contactos. Usa la QuickContactBadge para mostrar una foto en miniatura de cada contacto; cuando los usuarios hacen clic en ella, aparece el diálogo de la QuickContactBadge.

Cómo agregar el elemento QuickContactBadge

Para comenzar, agrega un elemento de vista QuickContactBadge al diseño de tu elemento. Por ejemplo, si quieres mostrar una QuickContactBadge y un nombre por cada contacto que recuperas, agrega el siguiente XML a tu archivo de diseño:

    <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>
    

En las siguientes secciones, se hace referencia a este archivo como contact_item_layout.xml.

Cómo configurar un CursorAdapter personalizado

Para vincular un CursorAdapter a una ListView que contiene una QuickContactBadge, define un adaptador personalizado que extienda el CursorAdapter. Este enfoque te permite procesar los datos en el Cursor antes de vincularlos a la QuickContactBadge. También te permite vincular varias columnas Cursor a la QuickContactBadge. Ninguna de estas operaciones puede realizarse en un CursorAdapter común.

La subclase de CursorAdapter que definas debe anular los siguientes métodos:

CursorAdapter.newView()
Infla un objeto View nuevo para conservar el diseño del elemento. Cuando se anula el método, almacena controladores para los objetos View secundarios del diseño, incluida la QuickContactBadge secundaria. Al adoptar este enfoque, no necesitas obtener controladores para los objetos View secundarios cada vez que inflas un diseño nuevo.

Debes anular este método a fin de obtener controladores para los objetos View secundarios individuales. Esta técnica te permite controlar la vinculación en CursorAdapter.bindView().

CursorAdapter.bindView()
Mueve los datos de la fila Cursor actual a los objetos View secundarios del diseño del elemento. Debes anular este método para poder vincular la miniatura y el URI del contacto a la QuickContactBadge. La implementación predeterminada solo permite la asignación de 1 a 1 entre una columna y un objeto View.

En el siguiente fragmento de código, se incluye un ejemplo de una subclase personalizada de CursorAdapter:

Cómo definir el adaptador de listas personalizado

Define la subclase de CursorAdapter, incluido el constructor, y anula newView() y bindView():

Kotlin

        /**
         * 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 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 resource IDs in a
                 * in a ViewHolder class to prevent having to look
                 * them up each time bindView() is called.
                 */
                return inflater.inflate(
                        R.layout.contact_list_layout,
                        viewGroup,
                        false
                ).also { view ->
                    view.tag = ViewHolder().apply {
                        displayname = view.findViewById(R.id.displayname)
                        quickcontact = view.findViewById(R.id.quickcontact)
                    }
                }
            }

            ...

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

Cómo configurar variables

En tu código, configura variables, incluida una proyección de un Cursor con las columnas necesarias.

Nota: En el siguiente fragmento de código, se usa el método loadContactPhotoThumbnail(), que se define en la sección Cómo configurar el URI y la miniatura del contacto.

Por ejemplo:

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;
        ...
    

Cómo configurar el ListView

En Fragment.onCreate(), crea una instancia del adaptador de cursor personalizado y obtén un controlador para la ListView:

Kotlin

        override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
        ): View? {
            return inflater.inflate(...).let { view ->
                ...
                /*
                 * Gets a handle to the ListView in the file
                 * contact_list_layout.xml
                 */
                listView = view.findViewById<ListView>(R.id.contact_list)
                mAdapter?.also {
                    listView?.adapter = it
                }
                ...
            }
        }
        ...
    

Java

        @Override
        public View onCreateView(LayoutInflater inflater,
                ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(...)
            ...
            /*
             * Gets a handle to the ListView in the file
             * contact_list_layout.xml
             */
            listView = (ListView) view.findViewById(R.id.contact_list_view);
            if (listView != null && adapter != null) {
                listView.setAdapter(adapter);
            }
            ...
        }
        ...
    

En onViewCreated(), vincula el ContactsAdapter a la 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);
            }
            ...
        }
        ...
    

Cuando recuperas un Cursor que contiene los datos de los contactos, por lo general, en onLoadFinished(), llama a swapCursor() para mover los datos de Cursor a la ListView. De esta manera, se muestra la QuickContactBadge de cada entrada en la lista de contactos:

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

Cuando vinculas un Cursor a una ListView con un CursorAdapter (o una subclase), y usas un CursorLoader para cargar el Cursor, siempre debes borrar las referencias al Cursor en la implementación de onLoaderReset(). Por ejemplo:

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