AutoCompleteTextView En Android

Usa al widget AutoCompleteTextView en Android para mostrar sugerencias de autocompletado al usuario mientras teclea letras en un campo de texto. El ítem que se seleccione del menú desplegado será asignado como valor al campo.

Aspecto de AutoCompleteTextView en Android

La clase AutoCompleteTextView hereda directamente de EditText, pero requiere de un adaptador para inflar los ítems que aparecerán en el menú emergente de sugerencias. Puedes ver su uso en casos de uso como: sugerencias en formularios, sugerencias de etiquetas de filtro, búsqueda rápida de elementos, etc.

En este tutorial verás cómo implementar este view de la mano al siguiente ejemplo. Un formulario que construye una línea de factura simple a partir de productos existentes:

App ejemplo para AutoCompleteTextView en Android

Descargar el código del proyecto Android Studio desde el siguiente enlace:

1. Crear Un AutoCompleteTextView

Para añadir un AutoCompleteTextView desde el editor de layouts de Android Studio, dirígete a Palette>Text>AutoCompleteTextView.

Añadir AutoCompleteTextView en Android Studio

Desde XML este se crea usando la etiqueta <AutoCompleteTextView>. Este puede ser inicializado con el mínimo de atributos de su padre, como lo son android:text y android:hint.

En nuestro ejemplo lo incorporaremos como el primer campo de un pequeño formulario cuyos campos son rellenados si se elige una sugerencia:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/linear_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/name_label"
        style="@style/ExampleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Nombre" />

    <AutoCompleteTextView
        android:id="@+id/name_text_field"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:completionThreshold="1"
        android:hint="Escribe o selecciona producto"
        android:imeOptions="actionDone"
        android:inputType="text" />

    <TextView
        android:id="@+id/description_label"
        style="@style/ExampleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Descripción" />

    <EditText
        android:id="@+id/description_field"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:hint="Descripción"
        android:importantForAutofill="no"
        android:inputType="text">

    </EditText>

    <TextView
        android:id="@+id/price_label"
        style="@style/ExampleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Precio" />

    <EditText
        android:id="@+id/price_field"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:hint="Precio"
        android:importantForAutofill="no"
        android:inputType="text"></EditText>

</LinearLayout>

Como ves, usamos android:completionThreshold="1" para generar el menú de sugerencias al primer carácter tipeado. En nuestro caso como tenemos productos que solo empiezan por la letra A, este será el disparador para obtener toda la lista.

Por otro lado, si necesitas crear un AutoCompleteTextView programáticamente desde Kotlin, usa unos de los constructores públicos de clase y configura sus atributos base. Por ejemplo:

private fun createAndAddAutoCompleteTextView() {
    val layout: LinearLayout = findViewById(R.id.linear_layout)
    val productSuggestions = AutoCompleteTextView(this).apply {
        hint = "Escribe o selecciona un producto"
        threshold = 1
        layoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        )
    }
    layout.addView(productSuggestions)
}

2. Atributos De AutoCompleteTextView

La siguiente es una tabla con los atributos usados para configurar instancias de AutoCompleteTextView. Encontrarás el nombre del atributo junto a la descripción y valores aceptados.

AtributoDescripción
android:completionHintDetermina el texto de indicación para el menú de sugerencias
android:completionHintViewDetermina el view usado para representar al texto de indicación
android:completionThreshold Define el número de caracteres que se deben escribir para desplegar las sugerencias
android:dropDownAnchor Id del view que servirá como punto de aparición del menú de sugerencias
android:dropDownHeightAltura general del menú de sugerencias. Puedes usar wrap_content para ajustar al contenido o match_parent para permitir un despliegue expandido en el padre
android:dropDownHorizontalOffset Cantidad de pixeles que el menú estará separado horizontalmente
android:dropDownSelector Selector para especificar el color de las sugerencias según el estado
android:dropDownVerticalOffset Cantidad de pixeles que el menú estará separado verticalmente
android:dropDownWidth Ancho general del menú de sugerencias
android:popupBackground Establece el fondo del menú

3. Crear Adaptador Para Poblar Sugerencias

El AutoCompleteTextView necesita un adaptador para desplegar las sugerencias. En nuestra App de ejemplo mostramos un menú con nombres de productos en un campo que pide el nombre:

Poblar AutoCompleteTextView desde array de strings

La forma más fácil para poblar las sugerencias es usar un array de strings desde los recursos. Es decir, creamos un elemento <string-array> dentro de strings.xml e incluimos los ítems que serán mostrados:

<resources>
    <string name="app_name">AutoCompleteTextView En Android</string>

    <!-- Fuente de datos -->
    <string-array name="products">
        <item>Aceite de oliva</item>
        <item>Aceitunas</item>
        <item>Aguacate</item>
        <item>Ajo</item>
        <item>Alcachofa</item>
        <item>Amaranto</item>
        <item>Apio</item>
        <item>Arroz</item>
        <item>Avena</item>
    </string-array>
</resources>

Luego relacionamos a la instancia del AutoCompleteTextView con una instancia de un ArrayAdapter. Este adaptador es construido a partir de la obtención del arreglo de strings anterior.

Y finalmente usamos setAdapter() para la asignación:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val nameTextField: AutoCompleteTextView = findViewById(R.id.name_text_field)

        val products = resources.getStringArray(R.array.products)

        ArrayAdapter(this, android.R.layout.simple_list_item_1, products).also { adapter ->
            nameTextField.setAdapter(adapter)
        }
    }
}

Recuerda que la función de alcance also() te permite usar al objeto invocador dentro del cuerpo del lambda que describen a las sentencias.

4. Personalizar Layout De Sugerencias

No obstante, el diseño actual es muy básico. Lo que deseamos es ver los productos con su precio y nombre:

AutoCompleteTextView con adaptador personalizado

Llegar a este resultado requiere en primer lugar, crear un modelo de dominio representativo de los productos con: nombre, descripción y precio:

data class Product(val name: String, val price: Double, val description: String) {

    val formatPrice: String get() = NumberFormat.getCurrencyInstance().format(price)

    companion object {
        val productsSource = listOf(
            Product("Aceite de oliva", 15000.0, "Muy rico"),
            Product("Aceitunas", 6000.0, "Deliciosas"),
            Product("Aguacate", 2400.0, "Verde naturaleza"),
            Product("Ajo", 1000.0, "Fresco"),
            Product("Alcachofa", 3100.0, "Todas las vitaminas"),
            Product("Amaranto", 4500.0, "Poderoso"),
            Product("Apio", 900.0, "Antioxidantes"),
            Product("Arroz", 1450.0, "Para todos los días"),
            Product("Avena", 4700.0, "Mejora tu digestión"),
        )
    }

    override fun toString(): String {
        return name
    }
}

Tres puntos a tener en cuenta:

  • La propiedad calculada formatPrice permite formatear a price en un String con forma de divisa
  • El objeto compañero contendrá el origen de datos productsSource para probar el adaptador
  • Sobrescribir el método toString() permitirá que el filtro interno del adaptador personalizado use al nombre para comparar. El algoritmo interno usa este método para encontrar coincidencias.

Seguido, creamos un layout personalizado para el ítem de la sugerencia. La composición es simple, ya que necesitamos dos TextViews distribuidos horizontalmente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:maxHeight="56dp"
    android:padding="16dp"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_weight="1"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
        android:layout_height="wrap_content"
        tools:text="Apio" />

    <TextView
        android:id="@+id/price"
        android:layout_width="match_parent"
        android:layout_weight="1"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
        android:gravity="end"
        android:layout_height="wrap_content"
        tools:text="$2000" />
</LinearLayout>
Layout personalizado para sugerencia de AutoCompleteTextView

Luego creamos un adaptador personalizado, al cual le podamos indicar en su método de binding como deberían estar atados las propiedades de nuestra clase Product a los views:

class ProductAdapter(context: Context, products: List<Product>) :
    ArrayAdapter<Product>(context, 0, products) {

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val view = convertView ?: LayoutInflater.from(context)
            .inflate(R.layout.product_suggestion, parent, false)

        getItem(position)?.let { product ->
            view.findViewById<TextView>(R.id.name).apply {
                text = product.name
            }
            view.findViewById<TextView>(R.id.price).apply {
                text = product.formatPrice
            }
        }

        return view
    }
}

Tan solo sobrescribimos a getView() para realizar el enlace de datos del objeto Product en views inflados a partir de product_suggestion.xml.

Como última instancia, reemplazamos la creación anterior por los nuevos elementos implementados. Creamos el adaptador con el layout personalizado y luego seteamos el adaptador:

class MainActivity : AppCompatActivity() {
    private lateinit var nameTextField: AutoCompleteTextView
    private lateinit var descriptionTextField: TextView
    private lateinit var priceTextField: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        nameTextField = findViewById(R.id.name_text_field)

        ProductAdapter(this, Product.productsSource).also { adapter ->
            nameTextField.setAdapter(adapter)
        }
    }
}

5. Escuchar Eventos De Click En Items

Si quisiéramos darle más actividad a la lógica de selección de una sugerencia, entonces procesemos los clicks sobre ellas. En nuestra app, cuando se selecciona el producto, rellenamos los campos de la línea de la factura asociados:

Escuchar eventos de AutoCompleteTextView con OnItemClickListener

La forma de conseguir esta observación de clicks es usando a la interfaz AdapterView.OnItemClickListener.

Tomamos a la referencia del AutoCompleteTextView y con setOnItemClickListener() pasamos una función lambda que represente la creación anónima de una clase.

nameTextField.setOnItemClickListener { parent, _, position, _ ->
    val product = parent.adapter.getItem(position) as Product
    priceTextField.text = product.formatPrice
    descriptionTextField.text = product.description
}

Nuestra lógica son las asignaciones a los campos de texto que conforman un ítem de línea, por lo que pasamos la descripción y el precio a los views referentes.

Claro está que a través de parent podemos obtener la referencia del adaptador y con su método getItem(), conseguir el ítem que fue clickeado en position.

Nota: Si deseas diferenciar tu flujo entre un click y una selección asentada, entonces puedes usar a AdapterView.OnItemSelectedListener. Esta te asiste cuando ya se ha confirmado la selección.

Más Tutoriales

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!