En el vídeo de hoy te explico qué son las Custom Views o vistas personalizadas, y qué necesitas saber para implementarlas.
Vamos a ver a fondo un ejemplo que a pesar de ser relativamente sencillo, requiere de bastantes pasos para entenderlo por completo.
Te explicaré:
- Qué es una Custom View
- Cómo definir una Custom View que extienda de una View ya existente
- Cómo modificar su comportamiento
- Cómo incluirla en nuestros diseños
- Cómo añadir propiedades personalizadas directamente en XML
- Cómo recuperar esos valores por código
Aquí tienes el vídeo:
Curso de Android Completo con Kotlin
Ya hemos visto a lo largo de los videos anteriores que tenemos una serie de vistas proporcionadas por el framework de Android que nos ayudan a crear las pantallas con la estructura que nosotros necesitemos, pero habrá ocasiones en las que esas vistas no se ajusten a lo que necesitemos o porque nos interese agrupar un conjunto de vistas para crear una nueva. Vamos a necesitar crear lo que se denomina Custom Views (vistas personalizadas).
Lo que queremos hacer es un par de vistas personalizadas. La primera vista es una imagen.

La imagen anterior es un grid, dividida por filas y columnas, donde le hemos dicho que haya 3 elementos por fila, o sea, tres columnas. Por tanto en función de como sea la pantalla estos elementos serán mas anchos o mas estrechos y no queremos que la imagen crezca mas en alto que en ancho o vice versa, queremos que lo haga de forma proporcional.
Vamos a crear una Custom view con ImageView un poco variado. Esta es una de las opciones que tenemos cuando creamos una vista personalizada que nos basemos en una vista que ya existe y le adaptemos cosas. El básico y esencial sería basarnos en la vista View que es como un lienzo en blanco, y luego modificar ciertas cosas. Nosotros vamos a ir a un nivel de abstracción un poco mas arriba y vamos a sobrescribir el comportamiento habitual que tiene el ImageView.
Lo primero que haremos es crear un archivo que se va a llamar AspectRatioImageView yendo a:


No olvidar que la carpeta se llamara distinto dependiendo del nombre que se le haya dado al momento de crear el proyecto en los pasos anteriores.
ahora crearemos una clase con el nombre del archivo y lo que tenemos que hacer es que extienda de ImageView, es decir, que coja todas las propiedades y funcionalidad de ImageView y que la podamos usar en nuestra clase. Se extiende colocando dos puntos y seguido el nombre de la clase que deseamos extender:

Esto nos va a obligara llamar a un constructor padre, es decir, la forma en la que definimos como se construye este objeto o sino crear varios constructores. Una cosa importante es que cuando estamos trabajando con la librería de AppCompat, que por defecto la vas a usar siempre si estas usando una de las plantillas de Android Studio. En vez de extender de la variante del framework de Android Studio utilicemos la de AppCompat.

Nos recomendara que es lo mas sencillo para usar los constructores y quedaría de la siguiente manera. Con esto ya nos da todas las propiedades que puede necesitar un ImageView o en general cualquier vista cuando la estamos creando. La principal y si la creamos por código es el contexto (context) que es básicamente una acumulación de información que le sirve a la vista para ver como pintarse, que tema usar, este tipo de cosas . El resto solo son necesarias si estamos creando esta vista desde XML. Cuando se crea la vista por el XML algunas de las cosas que definimos se pasaran por el constructor.

Luego de esto tenemos que definir cual va hacer el AspectRatio que vamos a utilizar. Lo podemos poner como una propiedad privada de tipo Float e iniciaremos con el valor de 1. El tipo Float sería un numero en coma flotante que lo que quiere decir es que puede tener decimales, de tal forma que el AspectRatio pueda ser 1×1.5, esto quiere decir que por cada 1 de ancho necesita 1.5 de alto.
Lo siguiente que necesitaríamos hacer para nuestra imagen es sobrescribir onMeasure (sin centrarte en el código que esta escrito sino en las cosas que puedas hacer). El día que necesitas hacer algo parecido vas necesitar informarte mucho mejor en como funcionan las Custom View porque dependen mucho de lo que quieras hacer, también conlleva matemática implicada, es relativamente complejo.
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) }
Lo que haríamos es sobrescribir la función onMeasure, esta función se llama cuando estamos pintando una pantalla y queremos saber cual es el tamaño final que va a tener esa vista, para decírselo a las vistas padre, pasara por este método, además nos dará información de cual es la configuración que tiene esa vista, las vistas padre, definirá cierta información para que puedas definir con mayor fiabilidad cual es el tamaño que le vas a dar a tus vista. A veces podrás utilizar estos argumentos que vienen por parametro en la función onMeasure, los cuales están relacionados con el Match_Parent o el Wrap_Content o si le asignamos un tamaño especifico nos vendrá identificado aquí también. Dejaremos que la vista coja el tamaño que le correspondería en función de todo el resto de configuración y luego modificarle la altura con el AspectRatio en comparación con el ancho. Así que vamos a coger el tamaño de la propia property de la clase measureWidth, como también el alto. Estos son la altura y la anchura una vez se ha medido y luego de esto tenemos que comprobar que si ambos valores son iguales a cero, hara que el codigo de nuestra función no se ejecute porque todavía no se ha terminado de medir la vista y no tenemos los datos para calcular los datos en función del ancho.
class AspectRatioImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatImageView(context, attrs, defStyleAttr) { var ratio: Float = 1f override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) var width = measuredWidth var height = measuredHeight if (width == 0 && height == 0) { return } } }
El siguiente paso es comprobar si alguna de las dos no es cero, cual de ellas es distinto de cero. Porque por ejemplo si el ancho es cero quiere decir que no tiene información suficiente para calcularla y que lo que estamos intentando es calcular la anchura en función de la altura, si es el caso contrario y tenemos el ancho debemos calcular en funcion del ancho el alto.
class AspectRatioImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatImageView(context, attrs, defStyleAttr) { private var ratio: Float = 1f override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) var width = measuredWidth var height = measuredHeight if (width == 0 && height == 0) { return } if(width > 0){ height = (width*ratio).toInt() } else if(height > 0){ width = (height/ratio).toInt() } } }
Ya al final para decirle que hemos decido modificar el alto y el ancho con respecto a lo que nos había dado nuestra clase padre WidthMeasureSpec tenemos que llamar a setMeasuredDimension() y como argumentos le pasamos el width y height.
setMeasuredDimension(width, height)
Ahora nos vamos a ir a la activity_main.xml y borraremos todas las vistas que teniamos, luego vamos añadir una View de tipo AspectRatioImageView. Para añadir nuestra vista personalizada tendremos que hacerlo por codigo. Al escribir las iniciales de la vista que queremos llamar el auto completador nos mostrara cual es, de la siguiente manera.

Tendiendo definida nuestra View ahora le daremos un ancho de 200dp, para el alto lo definiremos como wrap_content y también le asigaremos un id con el nombre de cover.
<com.antonioleiva.androiddesdecero.AspectRatioImageView android:id="@+id/cover" android:layout_width="200dp" android:layout_height="wrap_content"/>
Ahora con el id podemos trabajar nuestra vista desde el archivo MainActivity.kt y nos quedara algo asi:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var cover = findViewById<AspectRatioImageView>(R.id.cover) cover.ratio = 1.5f } }
Ya tenemos nuestra vista creada pero no estamos cargando ninguna imagen en ella, ¿Como podemos hacerlo? Añadiendo un recurso dentro de nuestra libreria de Resources para que esa imagen pueda ser cargada dentro del ImageView.
Podemos buscar una caratula en google y descargarla. Ya con la caratula descargada que no es mas que una imagen .jpg la vamos arrastrar dentro de nuestro proyecto a la carpeta app > res> drawable.

Ahora con nuestra imagen dentro de nuestros recurso podemos ir a nuestro activity_main.xml y seleccionando nuestra vista iremos a las propiedades en busca de una que se llama srcCompat y elegiremos la imagen que hayamos puesto en nuestra carpeta drawable.

Si nos fijamos bien, nuestra imagen no ocupa el cuadro completo, solo ocupa el tamaño de la imagen y tenemos otra opción dentro de los ImageView que es el scaleType y que por defecto su valor sera centerInside, nosotros usaremos el valor de centerCrop que nos permite utilizar todo el ancho y en el alto lo que se salga lo recortara. Al ejecutarlo deberiamos poder ver algo como esto:

Cuando tenemos una vista perzonalizada le podemos definir atributos propios, para ello nos vamos a app > res > values y creamos un archivo attrs.xml y dentro del mismo podemos añadir atributos que definan el estilo de nuestra vista AspectRatioView de la siguente manera
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="AspectRatioImageView"> <attr name="ratio" format="float" /> </declare-styleable> </resources>
Al tener definidos nuestros atributos vamos a nuestro activity_main.xml y agregamos ese atributo que acabo de crear dentro de nuestra vista AspectRatioImageView, el nombre sera ratio y le ponemos nuestro valor 1.5
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity"> <com.antonioleiva.androiddesdecero.AspectRatioImageView android:id="@+id/cover" android:layout_width="200dp" android:layout_height="wrap_content" app:ratio="1.5" app:srcCompat="@drawable/avengers" /> </LinearLayout>
Pare recibir este dato dentro de nuestra vista se hara mediante el contrusctor que se define como init dentro de nuestra clase AspectRatioImageView.kt
class AspectRatioImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatImageView(context, attrs, defStyleAttr) { var ratio: Float = 1f init { val a = context.obtainStyledAttributes(attrs, R.styleable.AspectRatioImageView) ratio = a.getFloat(R.styleable.AspectRatioImageView_ratio, 1f) a.recycle() } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) var width = measuredWidth var height = measuredHeight if (width == 0 && height == 0) { return } if(width > 0){ height = (width*ratio).toInt() } else if(height > 0){ width = (height/ratio).toInt() } setMeasuredDimension(width, height) } }
Luego de esto podremos ver los cambios en el diseñador

0 comentarios