Saltar la navegación

3.4.1. Drawables

En la carpeta de recursos "res", que los proyectos Android tienen dentro del directorio "app", podemos encontrar la carpeta "drawable".

En esa carpeta, el proyecto guardará las imágenes como recursos drawables que la app utilizará en layouts, gráficos, diálogos e iconos.

Un recurso drawable es una abstracción del SDK para manejar imágenes que puedan ser mostradas en pantalla. Los drawables pueden cargarse mediante funciones de la API como getDrawable o ser incluidas en otros recursos XML, como layouts, con algún atributo como android:drawable o android:icon.

Imagina que se desea mostrar una imagen en uno de nuestros layouts. En ese caso, podemos utilizar el componente ImageView, que es un contenedor de imágenes. El código sería algo como:

<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/imagen" />

Si necesitamos modificar la imagen desde el código, primero cargaríamos el recurso drawable así:

val drawable: Drawable?= ResourcesCompat.getDrawable(resources, R.drawable.imagen, null)
...
imageView.setImageDrawable(drawable)

ShapeDrawable

La clase ShapeDrawable es una subclase de Drawable, y es una buena opción cuando pretendemos dibujar un gráfico bidimensional relativamente sencillo. En el código de nuestra app, podremos codificar formas básicas, como cuadrados o círculos, utilizando las líneas y colores que deseemos. En los objetos ShapeDrawable podemos sobrescribir su método draw para personalizar el aspecto de la imagen. Veamos un  ejemplo:

mport android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.OvalShape
import android.util.AttributeSet
import android.view.View
//Constructor que se llamará cuando la vista se llame desde código
class CustomDrawableView(context: Context) : View(context) {
constructor(context: Context, attributeSet: AttributeSet): this(context) {
//Constructor que se llamará cuando la vista se utilice en un layout
}
private val drawable: ShapeDrawable = run {
val x = 0
val y = 0
val width = 100
val height = 100
contentDescription = "Descripción de la imagen!"
ShapeDrawable(OvalShape()).apply {
// Si no establecemos el color, será negro por defecto.
paint.color = 0xff74AC23.toInt()
// Debemos establecer los límites del gráfico.
setBounds(x, y, x + width, y + height)
}
}
override fun onDraw(canvas: Canvas) {
drawable.draw(canvas)
}
}

Se ha creado una subclase de View con dos constructores, uno que se llamará cuando se incruste la vista en un layout y el otro para ser usado directamente en una activity o un fragment. Dentro de la clase hemos creado un ShapeDrawable con OvalShape, que dibujará un óvalo. Como el ancho y el alto son 100, será un círculo. Para personalizar nuestra vista, sobrescribimos el método onDraw de View. Aquí solo tendremos que llamar a la función draw del drawable. Veamos cómo podemos utilizar esta vista:

class SecondFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return CustomDrawableView(requireContext())
}

En este caso, la vista ocupará todo el fragment, pero también podríamos utilizar la vista dentro de un layout junto con otros componentes gráficos:

<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".NuestroFragment">
<com.ilernaonline.miapp.CustomDrawableView
android:id="@+id/view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_second" />

Otra opción para crear un drawable sería heredar directamente de Drawable:

import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import kotlin.math.min
class NuestroDrawable : Drawable() {
private val redPaint: Paint = Paint().apply { setARGB(255, 255, 0, 0) }
override fun draw(canvas: Canvas) {
// Obtenemos los márgenes que nos han dejado
val width: Int = bounds.width()
val height: Int = bounds.height()
val radius: Float = min(width, height).toFloat() / 2f
// Dibujamos un circulo rojo en mitad de la vista
canvas.drawCircle((width/2).toFloat(), (height/2).toFloat(), radius, redPaint)
}
override fun setAlpha(alpha: Int) {
// Sobrescribir este método es obligatorio aunque no lo uses
}
override fun setColorFilter(colorFilter: ColorFilter?) {
// Sobrescribir este método es obligatorio aunque no lo uses
}
override fun getOpacity(): Int =
// Puede ser PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
PixelFormat.OPAQUE
}

NuestroDrawable hereda de Drawable. Crea un objeto Pain de color rojo, que utilizará en el método draw que ya conocemos. En el método draw utilizamos el objeto Canvas que nos pasan por parámetro para dibujar un círculo con el método drawCircle. La clase Canvas es la responsable de dibujar todas las primitivas de imagen, se trata de un lienzo virtual que contiene las llamadas a métodos de dibujo para finalmente presentar una imagen. Los objetos drawables pueden utilizar un bitmap, es decir, una imagen corriente para definir su apariencia, y/o utilizar la clase Canvas para definir formas básicas como óvalos y rectángulos. La clase ShapeDrawable que vimos anteriormente no hace más que utilizar las funciones del Canvas para definir su aspecto.

Continuemos con nuestro código. En el layout, podríamos definir un componente ImageView:

<ImageView
android:id="@+id/imageView"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:contentDescription="Nuestra imagen" />

Y en el código lo iniciaríamos con:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
imageView.setImageDrawable(NuestroDrawable())
...
}

Creado con eXeLearning (Ventana nueva)