Fundamentos de Android: Recursos de imagen

Fundamentos de Android: Recursos de imagen

Después de examinar la estructura básica de una aplicación para Android con Kotlin, en esta y posteriores entradas vamos a seguir explorando los fundamentos y componentes de cualquier aplicación, y ahora vamos a tratar concretamente sobre los recursos de imagen. Para ello vamos a continuar trabajando con el proyecto 'TirarDados' y le vamos a agregar imágenes a esa aplicación (repasa la entrada Anatomía básica de una app para continuar el proyecto donde se dejó).

Recuerda que dejamos esa aplicación con un botón que simulaba el lanzamiento de un dado y cada vez que se pulsaba aparecía en pantalla el valor del dado (un número de 1 a 6). Nuestro objetivo ahora será cambiar ese número por una imagen de un dado que muestre la puntuación obtenida al lanzarlo. ¡Empezamos!

Abrimos el proyecto y con la vista Android activada, desplegamos la carpeta res. En la carpeta res se archivan los recursos que incluye cualquier aplicación, como imágenes e iconos, diseños asociados a las actividades, y valores de los colores, dimensiones y textos utilizados. Estos recursos se organizan en distintos directorios según su tipo: drawable, layout, mipmap, values. Ya vimos por ejemplo que el archivo de diseño activity_main.xml está en la carpeta layout o que los valores de los textos se guardan en el archivo strings.xml de la carpeta values.

Kotlin Doc: Recursos de imagen

Ahora nos interesa la carpeta drawable, que es donde se guardan todos los recursos de imagen de la aplicación, incluyendo los iconos de inicio de la aplicación. Dentro de esta carpeta tenemos dos archivos: ic_launcher_background.xml y ic_launcher_foreground.xml, que si los abrimos y luego pulsamos sobre la pestaña vertical-izquierda Preview, vemos las imágenes que contienen:

Kotlin Doc: Recursos de imagen

Observa que estos dos archivos de imagen tienen extensión xml y describen el icono como una imagen vectorial. Frente a otros tipos de imágenes y mapas de bits como PNG o GIF, que se deben escalar para diferentes dispositivos, lo que puede conllevar cierta pérdida de calidad, los vectores permiten presentar las imágenes en diferentes escalas y resoluciones.

Para lo que nos ocupa, vamos a utilizar las imágenes vectoriales que Google Developer Training pone a nuestra disposición para practicar y que están disponibles en el archivo comprimido DadoImg.zip, que contiene las imágenes vectoriales de cada lado de un dado.

Para incorporar rápidamente estos archivos a la carpeta drawable del proyecto, seleccionamos la vista Project y navegamos a TirarDados > app > src > main > res > drawable y seleccionamos todos los archivos vectoriales descargados (pero no el archivo comprimido ni la carpeta DadoImg), los arrastramos y soltamos sobre la carpeta drawable y pulsamos Aceptar:



Volvemos a la vista Android y comprobamos que los archivos xml de imágenes vectoriales están en la carpeta drawable y los podemos ver con Preview:

Kotlin Doc: Recursos de imagen

Ahora ya podemos acceder a estos archivos de imagen desde el diseño y el código de la aplicación. Para ello vamos al archivo de diseño activity_main.xml y desde modo texto eliminamos el elemento TextView y en su lugar añadimos un elemento ImageView con capacidad de mostrar imágenes:
<ImageView
    android:id="@+id/dado_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:src="@drawable/dado_1"/>

Observa el atributo src que sirve para indicar el origen del recurso de imagen en res/drawable/dado_1. Pulsando en la pestaña Design vemos como está quedando por ahora el diseño:

Kotlin Doc: Recursos de imagen

Bien, ahora toca la parte del código que se ocupa de la actividad. Abrimos MainActivity y vemos que Android Studio nos advierte de errores con varias señales en rojo y concretamente en la función tirarDado aparece la palabra 'resultado' resaltada en rojo puesto que hace referencia al TextView que hemos eliminado. Entonces necesitamos utilizar una referencia a la nueva ImageView y asignarla a una nueva variable, de este modo:
val dadoImagen: ImageView = findViewById(R.id.dado_image)
// pero ya sabemos que se puede escribir de manera más concisa:
val dadoImagen: ImageView = dado_image
// y mejor así:
val dadoImagen = dado_image

Ahora utilizando una expresión when asignamos a cada número aleatorio obtenido y guardado en la variable randomInt, una imagen específica (utilizando la referencia de cada recurso de imagen con la clase R):
val drawableResource = when (randomInt) {
    1 -> R.drawable.dado_1
    2 -> R.drawable.dado_2
    3 -> R.drawable.dado_3
    4 -> R.drawable.dado_4
    5 -> R.drawable.dado_5
    else -> R.drawable.dado_6
}

Por último actualizamos la fuente del elemento ImageView utilizando el método setImageResource() y el valor obtenido en el bloque when:
dadoImagen.setImageResource(drawableResource)

Ahora al compilar y ejecutar la aplicación en el emulador, cada vez que se pulsa el botón se debe actualizar la imagen del elemento ImageView:



Aunque funciona, si pensamos en el rendimiento en dispositivos más lentos, podemos intentar mejorar la eficiencia del programa cambiando varias cosas (aunque nuestra aplicación es muy pequeña, es buena práctica para proyectos mayores). Por un lado, vemos que cada vez que se pulsa el botón se ejecuta la función tirarDado() y la aplicación entonces tiene que llamar o buscar, cada vez, la referencia del recurso de imagen entre toda la jerarquía de recursos. Esto lo podemos optimizar encontrando una manera de minimizar el número de llamadas, por ejemplo haciéndola una sola vez y almacenando el objeto View en un campo que mantiene esa referencia, lo que permite ser más rápidamente accesible, y la aplicación mejora su rendimiento.

Para ello colocamos en la parte superior de la clase, antes de onCreate(), la variable que almacena la referencia a ImageView, pero ahí no podemos inicializar la variable cuando la declaramos puesto que las vistas del diseño no son accesibles en la memoria hasta después de haber sido activadas (infladas) por el método onCreate cuando se invoca setContentView(). Por tanto, no podemos inicializar la variable dadoImagen hasta entonces. Una opción, aunque no la mejor, es definir la variable dadoImagen como anulable: así cuando se declara se establece como nulo y posteriormente le asignamos un recurso de imagen concreto (no nulo):
var dadoImagen : ImageView? = null

Pero una solución mejor es utilizar la palabra reservada lateinit (recuerda las propiedades inicializadas con demora que vimos al hablar de la Gestión de tipos nulos en Kotlin):
lateinit var dadoImagen: ImageView

Utilizando lateinit prometemos al compilador de Kotlin que la variable se inicializará antes de que el código intente cualquier operación con ella y por tanto no necesitamos inicializar la variable a nula ni tenemos que tratarla como una variable anulable cuando la usamos, por lo que en general es una buena práctica usar este inicio demorado con campos que almacenan vistas de diseño.

Ahora, después del método setContentView(), ya podemos asignar al ImageView la referencia al recurso (y en la función tirarDado() eliminamos la línea que obtiene el ImageView):
dadoImagen = dado_image // dadoImagen = findViewById(R.id.dado_image)

Ejecutamos la aplicación nuevamente para ver que sigue funcionando como antes.

Otra cosa que podemos mejorar en el programa es la imagen inicial del dado, que hace referencia a la imagen del archivo dado_1.xml, pero estaría bien que no se mostrara ningún resultado hasta que se lanza el dado por primera vez. Hay varias maneras de conseguir esto, por ejemplo, en el archivo activity_layout.xml, desde la vista de Text, podemos configurar el elemento ImageView con el atributo src así: android:src="@drawable/dado_vacio". Este archivo no tiene ninguna imagen, y ahora al ejecutar la aplicación en la pantalla de inicio solo aparece el botón, y hasta que no lo pulsamos no aparece una imagen del dado.

La imagen vacía tiene el mismo tamaño que el resto de imágenes de dados y por eso la estamos utilizando como marcador de posición en el diseño, el problema es que tampoco la podemos ver en el vista previa ni en el diseño, y durante el proceso de diseño puede resultar útil ver la distribución de los elementos en la pantalla. Para resolver esto se cambia el espacio de nombres predeterminado 'android' por el espacio de nombres 'tools' para el atributo src, y de esta manera se especifica que el contenido del marcador de posición solo se usa en la vista previa o en el editor de diseño de Android Studio pero será eliminado cuando se compila la aplicación:
android:src="@drawable/dado_vacio"
tools:src="@drawable/dado_1"/>

Ahora volvemos a ver la imagen del dado (dado_1.xml) en la vista previa y en el diseño pero al ejecutar la aplicación la pantalla de inicio está vacía hasta que se pulsa el botón.

A continuación se muestran cómo han quedado finalmente los archivos MainActivity.kt y activity_main.xml:
// MainActivity.kt
package com.android.tirardados

import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*

class MainActivity : AppCompatActivity() {

    lateinit var dadoImagen: ImageView

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

        dadoImagen = dado_image // dadoImagen = findViewById(R.id.dado_image)

        val dadoBoton = dado_boton
        dadoBoton.setOnClickListener { tirarDado() }
    }

    private fun tirarDado() {
        val randomInt = Random().nextInt(6) + 1

        val drawableResource = when (randomInt) {
            1 -> R.drawable.dado_1
            2 -> R.drawable.dado_2
            3 -> R.drawable.dado_3
            4 -> R.drawable.dado_4
            5 -> R.drawable.dado_5
            else -> R.drawable.dado_6
        }

        dadoImagen.setImageResource(drawableResource)
    }
}
<!-------activity_main.xml----->
<?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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="center_vertical"
        tools:context=".MainActivity">

    <ImageView
            android:id="@+id/dado_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/dado_vacio"
            tools:src="@drawable/dado_1"/>

    <Button
            android:id="@+id/dado_boton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="@string/dado"/>

</LinearLayout>

Comentarios

Entradas populares

I/O: entrada y salida de datos en consola

Recursos gratis para aprender Kotlin

Lectura y escritura de archivos