Controles en Android
Resumen completo del tema Controls: cada control con su XML, su código (Java y Kotlin — cambia arriba a la derecha), un esquema visual de cómo funciona y las correcciones de lo que estaba mal escrito o se ha quedado antiguo.
00 Cómo funciona un control
Todos los controles siguen el mismo ciclo. Si entiendes esto, entiendes el tema entero.
res/layout/activity_main.xml con un android:id.R.id.miBoton para ese id.onCreate(), tras setContentView(), obtienes la referencia con findViewById().setOnClickListener, setOnCheckedChangeListener…).public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 1. carga el XML
Button btn = findViewById(R.id.btnAceptar); // 2-3. referencia (sin cast desde API 26)
btn.setOnClickListener(v -> { // 4. listener (lambda)
Toast.makeText(this, "Pulsado", Toast.LENGTH_SHORT).show(); // 5. acción
});
}
}class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val btn = findViewById<Button>(R.id.btnAceptar)
btn.setOnClickListener {
Toast.makeText(this, "Pulsado", Toast.LENGTH_SHORT).show()
}
}
}- Ya no hace falta el cast
(Button) findViewById(...): desde API 26 el método es genérico. - Las clases anónimas
new View.OnClickListener() { ... }siguen funcionando, pero hoy se escriben como lambda (v -> { ... }), que es más corto. En los ejemplos pongo la lambda y, donde ayuda, también la versión larga. - En Kotlin, los synthetics (
kotlinx.android.synthetic) están eliminados. Se usafindViewByIdo ViewBinding.
Bonus: ViewBinding (la forma recomendada hoy)
Evita los findViewById uno a uno y los errores de id/tipo. Se activa en build.gradle del módulo:
android {
buildFeatures {
viewBinding = true
}
}private ActivityMainBinding binding; // clase generada a partir de activity_main.xml
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.btnAceptar.setOnClickListener(v -> binding.lblMensaje.setText("Hola"));
}private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnAceptar.setOnClickListener { binding.lblMensaje.text = "Hola" }
}En el resto del resumen uso findViewById porque es lo que sale en los apuntes y en el examen; con ViewBinding la lógica es idéntica.
02 ToggleButton
Botón con dos estados (pulsado / no pulsado) que se queda en el estado elegido. Cada estado tiene su propio texto.
Cómo funciona: internamente es un CompoundButton (igual que CheckBox, RadioButton y Switch). Guarda un booleano checked.
android:textOn→ texto cuando está activadoandroid:textOff→ texto cuando está desactivadoisChecked()/setChecked(bool)→ leer / cambiar el estado
<ToggleButton
android:id="@+id/tgbBoton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOn="@string/on"
android:textOff="@string/off" />android:textoOff — es android:textOff. Con la “o” extra el atributo no existe y el XML no compila.com.google.android.material.materialswitch.MaterialSwitch (o SwitchCompat). Se programa exactamente igual (isChecked, setOnCheckedChangeListener).04 Eventos de un botón (onClick)
Hay dos formas de reaccionar al click. La segunda es la buena.
Forma A · atributo XML
Pones android:onClick="metodo" y en la Activity creas public void metodo(View v).
Forma B · listener en código ✅
Obtienes el botón y le asignas un View.OnClickListener con setOnClickListener(). Comprobado al compilar y funciona en todas partes.
Forma A (para reconocerla en el examen)
<Button
android:id="@+id/btnBoton1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pulsar"
android:onClick="pulsarBoton" />// Debe ser public, void y recibir un View. Si no, crash al pulsar.
public void pulsarBoton(View view) {
TextView lbl = findViewById(R.id.lblMensaje);
lbl.setText("Botón pulsado");
}fun pulsarBoton(view: View) {
findViewById<TextView>(R.id.lblMensaje).text = "Botón pulsado"
}Forma B (la recomendada)
Button btnBoton1 = findViewById(R.id.btnBoton1);
TextView lblMensaje = findViewById(R.id.lblMensaje);
// Versión clásica (clase anónima) — la de los apuntes
btnBoton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
lblMensaje.setText("Botón 1 pulsado");
}
});
// Versión moderna (lambda) — hace exactamente lo mismo
btnBoton1.setOnClickListener(v -> lblMensaje.setText("Botón 1 pulsado"));val btnBoton1 = findViewById<Button>(R.id.btnBoton1)
val lblMensaje = findViewById<TextView>(R.id.lblMensaje)
btnBoton1.setOnClickListener {
lblMensaje.text = "Botón 1 pulsado"
}Saber el estado de un ToggleButton
ToggleButton tgbBoton = findViewById(R.id.tgbBoton);
// Mejor escuchar el CAMBIO de estado que el click:
tgbBoton.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
lblMensaje.setText("Botón ON");
} else {
lblMensaje.setText("Botón OFF");
}
});
// Lo de los apuntes (onClick + isChecked()) también funciona:
tgbBoton.setOnClickListener(v -> {
lblMensaje.setText(tgbBoton.isChecked() ? "Botón ON" : "Botón OFF");
});val tgbBoton = findViewById<ToggleButton>(R.id.tgbBoton)
tgbBoton.setOnCheckedChangeListener { _, isChecked ->
lblMensaje.text = if (isChecked) "Botón ON" else "Botón OFF"
}setOnCheckedChangeListener? Porque también salta si el estado cambia por código (setChecked(true)), no solo por un toque, y ya te da el booleano isChecked como parámetro.05 Personalizar un botón: selector
Poner una imagen fija en el fondo no da sensación de “botón”. Un selector es un XML en res/drawable que elige un fondo distinto según el estado del control.
Cómo lo evalúa Android: recorre los <item> de arriba abajo y usa el primero que cumple. Por eso el item sin condición (el “por defecto”) va siempre el último.
| Estado | Cuándo |
|---|---|
state_checked | ToggleButton/CheckBox activado |
state_pressed | Mientras se mantiene pulsado |
state_enabled="false" | Control deshabilitado |
state_focused | Tiene el foco |
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="false" android:drawable="@drawable/toggle_off" />
<item android:state_checked="true" android:drawable="@drawable/toggle_on" />
</selector><?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 1º estado más específico -->
<item android:state_checked="true">
<shape android:shape="rectangle">
<solid android:color="#2E9E5B" />
<corners android:radius="24dp" />
</shape>
</item>
<!-- último: estado por defecto -->
<item>
<shape android:shape="rectangle">
<solid android:color="#CFD8DC" />
<corners android:radius="24dp" />
</shape>
</item>
</selector><ToggleButton
android:id="@+id/tgbBoton2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOn="@string/on"
android:textOff="@string/off"
android:padding="12dp"
android:background="@drawable/toggle_style" />- En los apuntes pone
android: background(con espacio) — se escribeandroid:backgroundtodo junto. - La carpeta es
res/drawable(no “recursos/res/drawable”). - Con
ToggleButton/CheckBoxelandroid:backgroundfunciona. Si lo aplicas a un<Button>normal con tema Material, añade tambiénapp:backgroundTint="@null"o no se verá. - Con
<shape>no necesitas diseñar PNG: se adapta a cualquier tamaño y densidad.
06 ImageView
Muestra una imagen. No es clicable por defecto (para eso está ImageButton).
<ImageView
android:id="@+id/imgFoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/unaimatge"
android:adjustViewBounds="true"
android:maxWidth="200dp"
android:maxHeight="200dp"
android:scaleType="fitCenter"
android:contentDescription="@string/desc_foto" />ImageView imgFoto = findViewById(R.id.imgFoto);
imgFoto.setImageResource(R.drawable.otra_imagen);val imgFoto = findViewById<ImageView>(R.id.imgFoto)
imgFoto.setImageResource(R.drawable.otra_imagen)- El método es
setImageResource(...)— en los apuntes ponesetImageResorce(falta la u). maxWidthymaxHeightno hacen nada si no ponesandroid:adjustViewBounds="true". Esto no lo dicen los apuntes y es la causa típica de “no me funciona”.scaleTypecontrola cómo encaja la imagen:fitCenter(entera, centrada),centerCrop(llena y recorta),fitXY(deforma).- Para imágenes de internet no se usa
setImageResource: se usa una librería como Coil o Glide.
07 TextView
Etiqueta de texto de solo lectura.
| Propiedad XML | Método en código | Qué hace |
|---|---|---|
android:text | setText() / getText() | Texto mostrado |
android:background | setBackgroundColor() | Color de fondo |
android:textColor | setTextColor() | Color del texto |
android:textSize | setTextSize() | Tamaño (en sp) |
android:textStyle / fontFamily | setTypeface() | Negrita, cursiva, fuente |
<TextView
android:id="@+id/lblEtiqueta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/valor"
android:background="#AA44FF"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold|italic" />TextView lblEtiqueta = findViewById(R.id.lblEtiqueta);
String texto = lblEtiqueta.getText().toString(); // getText() devuelve CharSequence
texto += "123";
lblEtiqueta.setText(texto);
// Color desde resources (forma correcta) o constante
lblEtiqueta.setBackgroundColor(ContextCompat.getColor(this, R.color.azul));
lblEtiqueta.setBackgroundColor(Color.BLUE);val lblEtiqueta = findViewById<TextView>(R.id.lblEtiqueta)
val texto = lblEtiqueta.text.toString() + "123"
lblEtiqueta.text = texto
lblEtiqueta.setBackgroundColor(ContextCompat.getColor(this, R.color.azul))getResources().getColor(id)está deprecated → usaContextCompat.getColor(context, id).- Al pasar un número a
setText()ojo:setText(5)busca un recurso con id 5 y peta (Resources$NotFoundException). UsasetText(String.valueOf(5)).
08 EditText
Caja para que el usuario escriba. Es una subclase de TextView, así que hereda todas sus propiedades.
Clave del examen: getText() en un EditText devuelve un objeto Editable, no un String. Por eso siempre se hace .toString().
android:hint = texto gris de ayuda que desaparece al escribir.android:inputType = tipo de teclado (text, number, textEmailAddress, textPassword, phone…).
<EditText
android:id="@+id/txtNombre"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_nombre"
android:inputType="textPersonName"
android:autofillHints="name" />EditText txtNombre = findViewById(R.id.txtNombre);
Button btnSaludar = findViewById(R.id.btnSaludar);
TextView lblSaludo = findViewById(R.id.lblSaludo);
btnSaludar.setOnClickListener(v -> {
String nombre = txtNombre.getText().toString().trim(); // Editable -> String
if (nombre.isEmpty()) {
txtNombre.setError("Escribe un nombre"); // muestra el error en la caja
return;
}
lblSaludo.setText("Hola, " + nombre);
txtNombre.setText(""); // limpiar
});val txtNombre = findViewById<EditText>(R.id.txtNombre)
val btnSaludar = findViewById<Button>(R.id.btnSaludar)
val lblSaludo = findViewById<TextView>(R.id.lblSaludo)
btnSaludar.setOnClickListener {
val nombre = txtNombre.text.toString().trim()
if (nombre.isEmpty()) {
txtNombre.error = "Escribe un nombre"
return@setOnClickListener
}
lblSaludo.text = "Hola, $nombre"
txtNombre.setText("") // en Kotlin .text = "" no compila (es Editable)
}TextInputLayout + TextInputEditText (etiqueta flotante, contador, errores bonitos). El código para leer el texto es el mismo.09 CheckBox
Marcar / desmarcar opciones independientes (puedes marcar varias).
isChecked()→ ¿está marcado?setChecked(true/false)→ marcarlo por códigotoggle()→ invertir el estado- Evento:
setOnCheckedChangeListener→ métodoonCheckedChanged(CompoundButton, boolean)
<CheckBox
android:id="@+id/chkMarcame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/marcame"
android:checked="false" />CheckBox chkMarcame = findViewById(R.id.chkMarcame);
// Leer y cambiar el estado
if (chkMarcame.isChecked()) {
chkMarcame.setChecked(false);
}
// Escuchar cambios (versión clásica)
chkMarcame.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
chkMarcame.setText("Checkbox marcado!");
} else {
chkMarcame.setText("Checkbox desmarcado!");
}
}
});
// Igual con lambda
chkMarcame.setOnCheckedChangeListener((buttonView, isChecked) ->
chkMarcame.setText(isChecked ? "Checkbox marcado!" : "Checkbox desmarcado!"));val chkMarcame = findViewById<CheckBox>(R.id.chkMarcame)
if (chkMarcame.isChecked) chkMarcame.isChecked = false
chkMarcame.setOnCheckedChangeListener { _, isChecked ->
chkMarcame.text = if (isChecked) "Checkbox marcado!" else "Checkbox desmarcado!"
}CompoundButton.OnCheckedChangeListener. Existe otro RadioGroup.OnCheckedChangeListener con el mismo nombre y parámetros distintos; si Android Studio importa el que no toca, no compila.10 RadioButton y RadioGroup
Grupo de opciones donde solo una puede estar marcada. Los RadioButton se meten dentro de un RadioGroup, que es quien gestiona la exclusividad y el evento.
| Método de RadioGroup | Qué hace |
|---|---|
check(id) | Marca la opción con ese id |
clearCheck() | Desmarca todas |
getCheckedRadioButtonId() | Id de la marcada, o -1 si ninguna |
El listener se pone en el RadioGroup, no en cada RadioButton.
<RadioGroup
android:id="@+id/grbGrupo1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/radio1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/opcion_1" />
<RadioButton
android:id="@+id/radio2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/opcion_2" />
</RadioGroup>RadioGroup grbGrupo1 = findViewById(R.id.grbGrupo1);
TextView lblMensaje = findViewById(R.id.lblMensaje);
grbGrupo1.clearCheck(); // ninguna marcada
grbGrupo1.check(R.id.radio1); // marca la opción 1
int idSeleccionado = grbGrupo1.getCheckedRadioButtonId(); // -1 si no hay ninguna
// Evento: cambio de opción
grbGrupo1.setOnCheckedChangeListener((group, checkedId) -> {
// ⚠ NO usar switch(checkedId) { case R.id.radio1: ... } (ver aviso)
if (checkedId == R.id.radio1) {
lblMensaje.setText("Opción 1");
} else if (checkedId == R.id.radio2) {
lblMensaje.setText("Opción 2");
}
});
// Alternativa: obtener el RadioButton y su texto
RadioButton rb = findViewById(grbGrupo1.getCheckedRadioButtonId());
if (rb != null) lblMensaje.setText(rb.getText());val grbGrupo1 = findViewById<RadioGroup>(R.id.grbGrupo1)
val lblMensaje = findViewById<TextView>(R.id.lblMensaje)
grbGrupo1.clearCheck()
grbGrupo1.check(R.id.radio1)
val idSeleccionado = grbGrupo1.checkedRadioButtonId
grbGrupo1.setOnCheckedChangeListener { _, checkedId ->
lblMensaje.text = when (checkedId) { // en Kotlin 'when' sí funciona
R.id.radio1 -> "Opción 1"
R.id.radio2 -> "Opción 2"
else -> ""
}
}- Desde Android Gradle Plugin 8 los
R.id.*ya no son constantes (final). En Java unswitchconcase R.id.radio1:da el error “Constant expression required”. Usaif / else if. Muchos ejemplos de internet y apuntes antiguos usanswitch. - El evento se llama
onCheckedChanged(con d); en los apuntes aparece como “onCheckedChange”.
11 Adapters
Spinner, ListView y GridView no saben de dónde vienen los datos. Se los da un adaptador, que hace de puente y además crea la vista de cada elemento.
getCount() cuántos haygetItem(pos) cuál esgetView(pos) crea la fila con un layout(
setAdapter())| Adaptador | Datos de origen | Estado hoy |
|---|---|---|
ArrayAdapter | Array o List de objetos (usa su toString()) | ✅ Se usa (sobre todo con Spinner) |
SimpleAdapter | List de Map → vistas de un layout XML | ⚠ Antiguo, poco usado |
SimpleCursorAdapter | Columnas de un Cursor (SQLite) | ✕ Legacy → hoy Room + RecyclerView |
String[] datos = {"Elem1", "Elem2", "Elem3", "Elem4", "Elem5"};
ArrayAdapter<String> adaptador = new ArrayAdapter<>(
this, // 1. contexto (la Activity)
android.R.layout.simple_spinner_item, // 2. layout de cada elemento (un TextView)
datos); // 3. los datosval datos = arrayOf("Elem1", "Elem2", "Elem3", "Elem4", "Elem5")
val adaptador = ArrayAdapter(
this, // contexto
android.R.layout.simple_spinner_item, // layout de cada elemento
datos // datos
)android.R.layout.* son predefinidos y contienen un solo TextView) y el array/lista de datos.12 Spinner (lista desplegable)
Muestra el elemento seleccionado; al tocarlo se abre una lista emergente con todas las opciones.
Dos layouts distintos:
simple_spinner_item(en el constructor) → cómo se ve el elemento cerrado.simple_spinner_dropdown_item(consetDropDownViewResource()) → cómo se ven las filas de la lista abierta.
⚠ onItemSelected se dispara también al arrancar con la posición 0.
<Spinner
android:id="@+id/cmbOpciones"
android:layout_width="match_parent"
android:layout_height="wrap_content" />Spinner cmbOpciones = findViewById(R.id.cmbOpciones);
TextView lblMensaje = findViewById(R.id.lblMensaje);
String[] datos = {"Elem1", "Elem2", "Elem3", "Elem4", "Elem5"};
ArrayAdapter<String> adaptador = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, datos);
adaptador.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
cmbOpciones.setAdapter(adaptador);
// Evento: OnItemSelectedListener tiene DOS métodos obligatorios (no admite lambda)
cmbOpciones.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
lblMensaje.setText("Seleccionado: " + parent.getItemAtPosition(position));
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
lblMensaje.setText("");
}
});val cmbOpciones = findViewById<Spinner>(R.id.cmbOpciones)
val lblMensaje = findViewById<TextView>(R.id.lblMensaje)
val datos = arrayOf("Elem1", "Elem2", "Elem3", "Elem4", "Elem5")
val adaptador = ArrayAdapter(this, android.R.layout.simple_spinner_item, datos)
adaptador.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
cmbOpciones.adapter = adaptador
cmbOpciones.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
lblMensaje.text = "Seleccionado: ${parent.getItemAtPosition(position)}"
}
override fun onNothingSelected(parent: AdapterView<*>) {
lblMensaje.text = ""
}
}Extra: datos desde strings.xml (mejor práctica)
<string-array name="opciones">
<item>Elem1</item>
<item>Elem2</item>
<item>Elem3</item>
</string-array>ArrayAdapter<CharSequence> adaptador = ArrayAdapter.createFromResource(this,
R.array.opciones, android.R.layout.simple_spinner_item);
adaptador.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
cmbOpciones.setAdapter(adaptador);
// O directamente en el XML del Spinner: android:entries="@array/opciones"val adaptador = ArrayAdapter.createFromResource(
this, R.array.opciones, android.R.layout.simple_spinner_item
)
adaptador.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
cmbOpciones.adapter = adaptador13 ListView
Lista de opciones visible directamente (sin desplegar). Si no caben, hace scroll sola.
onItemClick(parent, view, position, id)
parent→ el ListViewview→ la fila pulsadaposition→ 3er parámetro: índice en el array (empieza en 0)id→ id de la fila (en ArrayAdapter = position)
<ListView
android:id="@+id/lstOpciones"
android:layout_width="match_parent"
android:layout_height="match_parent" />ListView lstOpciones = findViewById(R.id.lstOpciones);
TextView lblMensaje = findViewById(R.id.lblMensaje);
String[] datos = {"Elem1", "Elem2", "Elem3", "Elem4", "Elem5"};
ArrayAdapter<String> adaptador = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, datos);
lstOpciones.setAdapter(adaptador);
// Evento click en un elemento (OnItemClickListener tiene 1 método → admite lambda)
lstOpciones.setOnItemClickListener((parent, view, position, id) ->
lblMensaje.setText("Opción: " + datos[position]));
/* Versión clásica:
lstOpciones.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
lblMensaje.setText("Opción: " + parent.getItemAtPosition(position));
}
});
*/val lstOpciones = findViewById<ListView>(R.id.lstOpciones)
val lblMensaje = findViewById<TextView>(R.id.lblMensaje)
val datos = arrayOf("Elem1", "Elem2", "Elem3", "Elem4", "Elem5")
lstOpciones.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, datos)
lstOpciones.setOnItemClickListener { _, _, position, _ ->
lblMensaje.text = "Opción: ${datos[position]}"
}- El método es
setOnItemClickListener— en los apuntes ponesetOnItemClicListener(falta la k). - Si los datos cambian (p.ej. una
ArrayList), tras modificar la lista llama aadaptador.notifyDataSetChanged()para refrescar. Con unString[]fijo no puedes añadir elementos. - ListView está considerado legacy: funciona, pero en apps nuevas se usa RecyclerView (ver sección 15).
14 GridView
Como ListView pero en forma de tabla: filas y columnas. Los datos se rellenan por filas.
| Propiedad | Qué hace |
|---|---|
numColumns | Nº de columnas, o auto_fit (lo calcula el sistema) |
columnWidth | Ancho de cada columna |
horizontalSpacing | Espacio horizontal entre celdas |
verticalSpacing | Espacio vertical entre celdas |
stretchMode | Qué hacer con el espacio sobrante: columnWidth (se lo quedan las columnas) o spacingWidth (se lo quedan los huecos) |
<GridView
android:id="@+id/grdOpciones"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="auto_fit"
android:columnWidth="80dp"
android:horizontalSpacing="5dp"
android:verticalSpacing="10dp"
android:stretchMode="columnWidth" />GridView grdOpciones = findViewById(R.id.grdOpciones);
TextView lblMensaje = findViewById(R.id.lblMensaje);
String[] datos = new String[25];
for (int i = 0; i < datos.length; i++) {
datos[i] = "Dato" + (i + 1);
}
ArrayAdapter<String> adaptador = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, datos);
grdOpciones.setAdapter(adaptador);
grdOpciones.setOnItemClickListener((parent, view, position, id) ->
lblMensaje.setText("Pulsado: " + datos[position]));val grdOpciones = findViewById<GridView>(R.id.grdOpciones)
val lblMensaje = findViewById<TextView>(R.id.lblMensaje)
val datos = Array(25) { i -> "Dato${i + 1}" }
grdOpciones.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, datos)
grdOpciones.setOnItemClickListener { _, _, position, _ ->
lblMensaje.text = "Pulsado: ${datos[position]}"
}RecyclerView con GridLayoutManager(this, 3).15 Lo moderno: RecyclerView
No está en los apuntes, pero es lo que sustituye a ListView y GridView. Es más eficiente porque recicla las filas que salen de pantalla (patrón ViewHolder obligatorio).
onCreateViewHolder infla la filaonBindViewHolder pone los datosgetItemCount cuántasLinear (lista) · Grid (tabla)
public class MiAdapter extends RecyclerView.Adapter<MiAdapter.VH> {
public interface OnItemClick { void onClick(int position); }
private final List<String> datos;
private final OnItemClick listener;
public MiAdapter(List<String> datos, OnItemClick listener) {
this.datos = datos;
this.listener = listener;
}
static class VH extends RecyclerView.ViewHolder {
TextView txt;
VH(View v) {
super(v);
txt = v.findViewById(android.R.id.text1);
}
}
@NonNull @Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(android.R.layout.simple_list_item_1, parent, false);
return new VH(v);
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
holder.txt.setText(datos.get(position));
holder.itemView.setOnClickListener(v ->
listener.onClick(holder.getBindingAdapterPosition()));
}
@Override
public int getItemCount() { return datos.size(); }
}
// En la Activity:
RecyclerView rv = findViewById(R.id.rvOpciones);
List<String> datos = Arrays.asList("Elem1", "Elem2", "Elem3");
rv.setLayoutManager(new LinearLayoutManager(this)); // lista (≈ ListView)
// rv.setLayoutManager(new GridLayoutManager(this, 3)); // tabla (≈ GridView)
rv.setAdapter(new MiAdapter(datos, pos ->
Toast.makeText(this, datos.get(pos), Toast.LENGTH_SHORT).show()));class MiAdapter(
private val datos: List<String>,
private val onClick: (Int) -> Unit
) : RecyclerView.Adapter<MiAdapter.VH>() {
class VH(v: View) : RecyclerView.ViewHolder(v) {
val txt: TextView = v.findViewById(android.R.id.text1)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val v = LayoutInflater.from(parent.context)
.inflate(android.R.layout.simple_list_item_1, parent, false)
return VH(v)
}
override fun onBindViewHolder(holder: VH, position: Int) {
holder.txt.text = datos[position]
holder.itemView.setOnClickListener { onClick(holder.bindingAdapterPosition) }
}
override fun getItemCount() = datos.size
}
// En la Activity:
val rv = findViewById<RecyclerView>(R.id.rvOpciones)
val datos = listOf("Elem1", "Elem2", "Elem3")
rv.layoutManager = LinearLayoutManager(this) // o GridLayoutManager(this, 3)
rv.adapter = MiAdapter(datos) { pos ->
Toast.makeText(this, datos[pos], Toast.LENGTH_SHORT).show()
}16 Errores y cosas antiguas corregidas
Todo lo que he cambiado respecto a los apuntes, en una tabla.
| En los apuntes | Correcto / actual | Tipo |
|---|---|---|
android:textcolor | android:textColor | Errata |
android:textoOff | android:textOff | Errata |
android: background | android:background (sin espacio) | Errata |
setImageResorce(...) | setImageResource(...) | Errata |
setOnItemClicListener | setOnItemClickListener | Errata |
onCheckedChange | onCheckedChanged | Errata |
| “recursos/res/drawable” | res/drawable | Errata |
(Button) findViewById(...) | Sin cast (API 26+) o ViewBinding | Antiguo |
new View.OnClickListener(){...} | Lambda v -> {...} | Antiguo (funciona) |
android:onClick en XML | setOnClickListener en código | Desaconsejado |
android:background en Button | app:backgroundTint (tema Material) | Cambio de comportamiento |
android:typeface | android:fontFamily + textStyle | Antiguo |
maxWidth/maxHeight solos | + android:adjustViewBounds="true" | Faltaba |
Sin contentDescription | Añadirlo en ImageView/ImageButton | Accesibilidad |
switch(id){ case R.id.x: } | if / else if (AGP 8+) | Ya no compila |
getResources().getColor() | ContextCompat.getColor() | Deprecated |
| Imágenes PNG en selector | <shape> o vector drawable | Mejora |
| ListView / GridView | RecyclerView + LayoutManager | Legacy |
| SimpleCursorAdapter | Room + RecyclerView | Legacy |
| ToggleButton | MaterialSwitch / SwitchCompat | Alternativa moderna |
17 Chuleta final
| Control | Leer / escribir | Listener | Método que salta |
|---|---|---|---|
| Button / ImageButton | — | setOnClickListener | onClick(View v) |
| ToggleButton / CheckBox | isChecked() / setChecked() | setOnCheckedChangeListener | onCheckedChanged(CompoundButton, boolean) |
| RadioGroup | getCheckedRadioButtonId() / check() / clearCheck() | setOnCheckedChangeListener | onCheckedChanged(RadioGroup, int checkedId) |
| TextView | getText() / setText() | — | — |
| EditText | getText().toString() / setText() | (addTextChangedListener) | afterTextChanged(Editable) |
| ImageView | setImageResource() | — | — |
| Spinner | getSelectedItem() / setSelection() | setOnItemSelectedListener | onItemSelected(...) + onNothingSelected(...) |
| ListView / GridView | setAdapter() | setOnItemClickListener | onItemClick(parent, view, position, id) |
android.R.layout.simple_spinner_item→ Spinner cerradoandroid.R.layout.simple_spinner_dropdown_item→ Spinner abierto (setDropDownViewResource)android.R.layout.simple_list_item_1→ filas de ListView / celdas de GridView
Resumen de “Controls” — DAM M0489, INS Marianao. Código revisado y actualizado (Android Studio actual, AGP 8+, tema Material 3).