Custom Views
Si bien el SDK de Android viene con una gran variedad de elmentos para conformar nuestra aplicación, puede ser que llegado un punto sea necesario que creemos nuestra propia Vista (recordar que las Vistas son los elementos visuales que componen nuestra app).
A veces puede ser porque necesitamos un comportamiento que no está disponible o simplemente porque vamos a re-utilizar varias veces el mismo esquema y de esa manera nos ahorramos tiempo de desarrollo y testing.
Hay dos formas de crear una vista, una es totalmente desde cero y la otra extendiendo de otra vista.
En esta clase vamos a crear una vista, extendiendo de otra. Y particularmente vamos a extender de un layout y vamos a usar otras vistas ya a disposición para crear nuestra propia vista. Y la vista que vamos a crear es como la siguiente:
Dijimos que ibamos a extender de una vista ya existente. Y que ibamos a componer nuestra vista de otras vistas también disponibles.
¿Se dan cuenta cuáles son las que vamos a usar?
Creemos el layout!
Si vamos a usar un RelativeLayout, ¿Por qué no usamos ese ViewGroupo como padre del layout y usamos merge?
Porque si lo hicieramos nos quedaría una vista que tiene como único hijo a un RelativeLayout, en cambio con Merge, quitamos del medio a ese RelativeLayout innecesario y ahora cada una de las "sub"-vistas son hijos directos de nuestra vista
Comencemos extendiendo de un RelativeLayout y definiendo el constructor.
Principalmente, toda vista tiene 2 constructores uno para el XML y otra para JAVA. Es decir, uno para crear un elemento en el layout y otro para crearlo desde el código. Por hoy sólo vamos a ver la del XML.
Dentro del constructor lo que se debe hacer es inicializar la vista, generalmente con el método init() y es ahí donde se infla el layout y se toma referencia a sus elementos
El método init(Context) debe:
http://sebasira.com.ar/cursos/android-experto/imagen_mas.png
http://sebasira.com.ar/cursos/android-experto/imagen_menos.png
baseView = inflate(mContext, R.layout.selector_valor, this);
Nuestra vista no es sólo un elemento visual, sino que tiene un comportamiento que debemos definirle.
Siendo que se trata de un selector de valor, sería interesante que tuviera:
Y finalmente los métodos para cada botón
Con eso ya podemos hacer uso de nuestra propia vista!
Tengan en cuenta que las vistas propias (custom) requieren el uso del nombre completo de la misma en el XML.
Hagamos una app de prueba donde podamos testearla, con todas sus funciones
Seria bueno poder notificarnos cuando el valor cambia. Para eso, es necesario que definamos un "escuchador" (listener) para esos eventos.
Para ello, primero debemos crear una interface.
public interface OnValueChange {
void OnValueChange();
}
La interface define el comportamiento y debemos implementar ese comportamiento en nuestra propia vista.
Además, debemos:
Luego definimos la acción que queremos que suceda cuando ocurre el evento, al igual que lo hacemos con el onClick de un botón
public void setOnValueChange_listener(OnValueChange cb){
this.onValueChange_listener = cb;
}
@Override
public void OnValueChange() {
if (null != this.onValueChange_listener){
this.onValueChange_listener.OnValueChange();
}
}
Una funcionalidad muy útil de las vistas es poder definirle los atributos desde el XML. Nosotros podemos agregarle atributos propios a nuestras vistas.
Los mismos se definen en un archivo XML (values) como el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="SelectorValor">
<attr name="label" format="string"/>
</declare-styleable>
</resources>
Luego al inicializar debemos setear los valores que se hayan establecido en el XML.
TypedArray ta = mContext.obtainStyledAttributes(attrs, R.styleable.SelectorValor, 0, 0);
try {
String label = ta.getString(R.styleable.SelectorValor_label);
txtLabel.setText(label);
} finally {
ta.recycle();
}
Otro detalle importante es trabajar sobre la visualización de la vista, para enriquecer la experiencia de usuario.
Una de las cosas que podríamos hacer es mostrar cuando el botón está presionado.
Nuevamente, y al igual que la clase anterior, podemos hacer esto con un selector.
http://sebasira.com.ar/cursos/android-experto/imagen_mas_presionado.png
http://sebasira.com.ar/cursos/android-experto/imagen_menos_presionado.png
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/imagen_menos_presionado" />
<item android:drawable="@drawable/imagen_menos" />
</selector>
Se trata de un selector de valor, y una funcionalidad interesante es que pueda mantenerse apretado el boton (hold) e ir incrementando/decrementando el valor.
Para ello, vamos a reaccionar frente a dos eventos:
Con ellos vamos a determinar cuando se mantiene presionado y cuan se libera.
Luego, vamos a lanzar una acción que va a correr en segundo plano y que va a estar ligada al hilo de UI (UI Thread). Eso se logra con un Handler
Handler handler = new Handler();
Y luego los Runnables. Un Runnable nos permite ejecutar su tarea run en un hilo separado al hilo principal
private class AutoIncrementador implements Runnable {
@Override
public void run() {
if(botonMasPresionado){
incrementarValor();
handler.postDelayed( new AutoIncrementador(), REPEAT_INTERVAL_MS);
}
}
}
Para ejecutar alguno de estos runnables, debemos postearlo en el handler. Ahi se crea como un pool y se van ejecutando las tareas posteadas
handler.post(new AutoIncrementador());
Vamos a ver como customizar un Toast... Es muy sencillo, sólo basta con definir un layout (una vista), inflarla y setearle esa vista a nuestro Toast
LayoutInflater inflater = getLayoutInflater();
ViewGroup customLayout = (ViewGroup) findViewById(R.id.custom_toast_layout);
View toastLayout = inflater.inflate(R.layout.custom_toast, customLayout);
Toast toast = new Toast(getApplicationContext());
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(toastLayout);
toast.show();