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.
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:
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.
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.
Atributo | Descripción |
---|---|
android:completionHint | Determina el texto de indicación para el menú de sugerencias |
android:completionHintView | Determina 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:dropDownHeight | Altura 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:
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:
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 aprice
en unString
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>
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:
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
- TextView
- EditText
- Exposed Dropdown Menu (todo)
- Interfaz gráfica en Android
- Guía De Desarrollo Android
Ú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!