Programación Android, Creación de un control personalizado

Share if you like...Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0

Control personalizado
creación de un control personalizado

En Android existe una gran cantidad de controles que podemos utilizar para conseguir realizar las interfaces que necesitemos, pero una de las posibilidades que nos ofrece es la de crear nuestros propios controles personalizados, de manera que en un único control, creado por nosotros mismos, podamos crear múltiples controles. El clásico ejemplo es el de la gestión de una ventana de login, en la que existen dos campos en los que escribir texto (usuario y contraseña), y un botón para aceptar el login.

En Internet pueden encontrarse muchos tutoriales sobre la creación de un control personalizado, y muchos de ellos utilizando el propio ejemplo del login para explicarlo. Sin embargo siempre hemos echado en falta un elemento que nos gustaría explicar en este tutorial. Esto es la posibilidad de configurar los eventos onClick de los elementos internos de nuestro control personalizado utilizando atributos en xml. Como sabéis, desde xml podemos configurar el atributo android:onClick=””, dándole como valor el nombre del método que debe lanzarse, y que posteriormente tenemos que codificar en nuestra Activity en Java. Sin embargo este evento, si lo escribimos tal cual sobre nuestro control, se lanzará cuando se pulse sobre cualquier zona del control, y no tendríamos la capacidad de lanzarlo únicamente cuando se pulsara, por ejemplo, el botón para aceptar el login antes citado.

Creación de un control personalizado

Como el ejemplo del login está muy utilizado, nosotros vamos a crear un control diferente, en el que pondremos un botón a cada lado, y entre ambos botones posicionaremos un texto, que irá cambiando a medida que se pulse uno u otro botón. En primer lugar crearemos el xml con el layout que dará forma a nuestro control personalizado:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="horizontal">

    <Button
        android:id="@+id/btPREV_SLIDER_CUSTOMER_DETAILS"
        android:background="@android:color/transparent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="<"/>

    <TextView
        android:id="@+id/tvSliderText_SLIDER_CUSTOMER_DETAILS"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:maxLines="1"
        android:gravity="center"/>

    <Button
        android:id="@+id/btNext_SLIDER_CUSTOMER_DETAILS"
        android:background="@android:color/transparent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=">"/>

</LinearLayout>

Como hemos dicho, tenemos un botón en cada lado, a los que hemos dado un fondo transparente, y entre ambos botones hemos colocado un TextView que contendrá el texto que queramos mostrar.

Crear una clase para el control personalizado

Una vez que tenemos diseñado nuestro control personalizado, el siguiente paso es crear una clase en Java que va a representar en código a nuestro control. Como lo que hemos utilizado para crear nuestro control ha sido un LinearLayout, la clase que vamos a crear debe heredar de LinearLayout también, para poder utilizar sus propiedades de forma correcta:

public class SliderLayout extends LinearLayout {

}

Al hacer esto, el código nos va a obligar a crear un constructor de entre los que tiene la clase LinearLayout. En nuestro caso, para poder recoger los atributos que definamos en el xml, necesitamos específicamente un constructor, que es el que internamente utiliza Android, y al que se le pasa ya el conjunto de atributos que se definen en el fichero xml:

public Slider(Context context, AttributeSet attrs) {
    super(context, attrs);
}

En el constructor vemos que vienen dos parámetros, el Context de donde se ha utilizado este control, y un objeto de la clase AttributeSet, que contiene todos los atributos que hemos definido en xml. Al pasarle ambos al super (llamando así al constructor de la clase de la que estamos heredando), estamos haciendo que automáticamente Android se encargue de recoger los atributos estándar definidos en xml y leerlos. Sin embargo con esto no tendríamos control sobre aquellos atributos que necesitemos definir por nosotros mismos para darle un determinado valor a uno de los componentes de nuestro control personalizado, por ejemplo el texto inicial que mostrar en nuestro TextView.

Definir los atributos personalizados

Para poder manejar nuestros propios atributos, en primer lugar debemos crear un nuevo archivo xml, que contendrá nuestros atributos personalizados, a los que posteriormente podremos dar valor en el archivo xml en el que hagamos uso de nuestro control personalizado. Este fichero que contendrá los atributos personalizados es un archivo de recursos, por lo que lo crearemos en la carpeta values, y sería así:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="SliderAtributes">
        <attr name="android:entries" />
        <attr name="onClickPrevButton" format="string" />
        <attr name="onClickNextButton" format="string" />
    </declare-styleable>

</resources>

Como vemos en el código, hemos creado un objeto declare-styleable y lo hemos nombrado SliderAtributes. En su interior contiene una serie de atributos, a los que se les ha dado un nombre y un formato, excepto para el primero que sólo tiene un nombre. Esto es porque para el primer caso vamos a utilizar un atributo que ya existe en Android, que es android:entries, que nos permite añadir un listado definido en un recurso en xml. Veremos la utilidad de esto, y el motivo de haberlo usado en este ejemplo, más tarde. Los siguientes atributos hacen referencia a los eventos onClick de los botones, y el atributo format indica que como valor sólo podremos utilizar un string. Existen otros tipos de formato, como pueden ser integer, boolean, color, dimension, etc, pero en nuestro caso necesitamos que sea un string, para dar nombre al método que se deberá lanzar al pulsar sobre nuestros botones.

Leer los valores de los atributos personalizados

Al tener nuestros propios atributos definidos, el siguiente paso es analizarlos en la clase de nuestro control personalizado, para recoger estos valores y poder utilizarlos en el código. En primer lugar vamos a añadir a la clase los objetos que contiene nuestro control personalizado, para posteriormente poder darles los valores deseados, después, en el constructor, inicializaremos todos estos objetos:

	private TextView tvText;
 	private Button btPrev, btNext;
        // Almacenará los textos que mostraremos en nuestro control personalizado.
        private CharSequence[] entries;

public Slider(Context context, AttributeSet attrs) {
	super(context, attrs);

	init();
}

private void init() {
        // En primer lugar inflamos la vista de nuestro control personalizado. Al método iniflate 
        // le pasamos el layout de nuestro control, el ViewGroup al que pertenecerá la vista (this)
        // y si se debe añadir a este ViewGroup (en este caso sí).
	((LayoutInflater) getContext().getSystemService(
			Context.LAYOUT_INFLATER_SERVICE)).inflate(
			R.layout.slider_customer_details, this, true);

        // Inicializamos nuestros controles.
	tvText = (TextView) findViewById(R.id.tvSliderText_SLIDER_CUSTOMER_DETAILS);
	btPrev = (Button) findViewById(R.id.btPREV_SLIDER_CUSTOMER_DETAILS);
cbtNext = (Button) findViewById(R.id.btNext_SLIDER_CUSTOMER_DETAILS);

}

Una vez que ya tenemos nuestros controles inicializados, vamos a recoger los atributos que se pueden configurar en el xml, para asignárselos a los controles que contiene nuestro control personalizado. Para hacer esto haremos uso de un objeto TypedArray, que contendrá la referencia de todos los atributos que se han definido en el xml, además de los nombres de los que hemos definido nosotros mismos. Este objeto TypedArray lo crearemos después de la llamada al método que ha inicializado todos nuestros controles:

TypedArray a = context.obtainStyledAttributes(attrs,
			R.styleable.SliderAtributes);

Cuando tengamos creado este objeto, lo recorreremos con un bucle for, para recoger aquellos atributos que hemos definido nosotros mismos:

for (int i = 0; i < a.getIndexCount(); i++) {
        switch (a.getIndex(i)) {
        case R.styleable.SliderAtributes_android_entries:
                
                break;
        case R.styleable.SliderAtributes_onClickPrevButton:
                
                break;
         case R.styleable.SliderAtributes_onClickNextButton:
                
                break;
        }
}

Ahora que tenemos identificados nuestros propios atributos, en cada bloque case haremos la tarea propia de ese atributo. En primer lugar vamos a centrarnos en el caso más sencillo, el primero de los bloques case que hemos definido. Para este atributo lo que vamos a coger es un array de CharSequence, que luego iremos recorriendo para mostrar cada una de sus entradas en el TextView. Para ello, a partir del objeto TypedArray vamos a pedirle un array de textos, y le indicaremos el id del atributo que queremos:

// Recogemos la colección.
entries = a.getTextArray(R.styleable.SliderAtributes_android_entries);
// Si se han obtenido datos (se han definido en xml), establecemos el texto inicial 
// en el TextView.
if(entries.length > 0) {
    tvText.setText(entries[0]);
}

Configurar el método personalizado

A continuación, vamos a recoger el nombre de uno de los métodos onClick que se han definido desde xml (los configuraremos más tarde en el layout de la activity), y vamos a hacer que se ejecute desde la Activity en la que se implemente nuestro control personalizado. En primer lugar lo haremos con prevButton:

// Comprobamos que el context no este restringido, de estarlo no podríamos usar
// nuestro método, por lo que lanzamos la exepción.
if (context.isRestricted()) {
    throw new IllegalStateException();
}
// Recogemos el nombre de nuestro método.
final String handlerNamePrevButton = a
        .getString(R.styleable.SliderAtributes_onClickPrevButton);
// Si se ha definido un método, configuramos el evento onClick de nuestro botón.
if (handlerNamePrevButton != null) {
    btPrev.setOnClickListener(new OnClickListener() {
        // Manejador para llamar al método en la Activity.
        private Method mHandler;

        @Override
        public void onClick(View v) {
            if (mHandler == null) {
                try {
                    // Tratamos de llamar al método con el nombre que hemos definido.
                    // A partir del contexto recogemos la clase, y posteriormente
                    // recogemos la referencia al método que tiene el nombre que 
                    // definimos en xml.
                    mHandler = getContext().getClass()
                            .getMethod(handlerNamePrevButton,
                                    View.class);
                } catch (NoSuchMethodException e) {
                    // Si el método no existe, lanzamos excepción.
                    throw new IllegalStateException();
                }
            }

            try {
                // Intentamos ejecutar el método.
                mHandler.invoke(getContext(), Slider.this);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException();
            } catch (InvocationTargetException e) {
                throw new IllegalStateException();
            }
        }
    });
}

Y con esto ya tendríamos definido el método onClick del primer botón, a continuación haremos lo mismo para definir el método del segundo botón, con la diferencia de que preguntamos por el objeto styleable que corresponde al segundo botón:

if (context.isRestricted()) {
    throw new IllegalStateException();
}

final String handlerNameNextButton = a
        .getString(R.styleable.SliderAtributes_onClickNextButton);
if (handlerNameNextButton != null) {
    btNext.setOnClickListener(new OnClickListener() {
    private Method mHandler;

    @Override
    public void onClick(View v) {
        if (mHandler == null) {
            try {
                mHandler = getContext().getClass()
                        .getMethod(handlerNameNextButton,
                                 View.class);
                } catch (NoSuchMethodException e) {
                    throw new IllegalStateException();
                }
            }

            try {
                mHandler.invoke(getContext(), Slider.this);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException();
            } catch (InvocationTargetException e) {
                 throw new IllegalStateException();
            }
        }
    });
}

Utilizar y configurar el control personalizado en xml

Lo último que nos falta ya es configurar nuestro control personalizado en xml, para hacer esto en primer lugar vamos a crear nuestro array de textos en un recurso xml, crearemos un nuevo archivo de recursos en la carpeta values cuyo contenido serán los textos que queremos mostrar:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="customEntries">
        <item>Texto nº 1</item>
        <item>Texto nº 2</item>
        <item>Texto nº 3</item>
        <item>Texto nº 4</item>
        <item>Texto nº 5</item>
    </string-array>
</resources>

Y a continuación pasamos a configurar los atributos que hemos personalizado en nuestro control, agregándolo al layout de nuestra Activity:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${packageName}.${activityClass}">
    <!-- Prestar especial atención a la línea en la que se declara es espacio de nombres custom.
         En esta línea es en la que recogemos la posibilidad de declarar más abajo nuestros propios
         atributos. -->
    
    <com.proyectosimio.customcontrol.Slider
        android:id="@+id/slider"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentLeft="true"
        android:entries="@array/customEntries"
        custom:onClickPrevButton="onClickPrevButton"
        custom:onClickNextButton="onClickNextButton"/>
    <!-- Como se puede ver, para configurar el array de Strings hemos utilizado el atributo android:entries
         en lugar de custom:entries, esto es porque cuando definimos los atributos recordemos que lo nombramos
         como android:entries, para reutilizar el campo que ya nos ofrece Android -->

</RelativeLayout>

En este momento, si decidiéramos ejecutar nuestra aplicación, daría una excepción en cuanto pulsáramos uno de los dos botones, ya que no hemos definido aún los métodos en nuestra Activity, por lo tanto, este va a ser nuestro próximo paso. Al igual que cuando definimos cualquier otro método onClick de los controles que tenemos predefinidos en Android, la firma siempre tendrá la estructura public void nombreMetodo(View v), por lo tanto esta será la firma que utilicemos para definir nuestros métodos en la Activity, además de declarar una nueva variable de la clase que hemos creado para nuestro control personalizado:

public class MainActivity extends Activity {
    private Slider slider;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        slider = (Slider)findViewById(R.id.slider);
    }

    public void onClickPrevButton(View v) {
        slider.showPrevText();
    }

    public void onClickNextButton(View v) {
        slider.showNextText();
    }
}

Y con esto ya casi habremos acabado, tan sólo nos falta definir los nuevos métodos en nuestra clase Slider que actualicen el texto a mostrar en el TextView. Para poder hacerlo, definiremos una variable de tipo int, que almacenará el índice del texto que actualmente se está mostrando, y a partir de este índice iremos actualizando al texto que toque. Con esto, las variables que tendremos en nuestra clase Slider quedarían así:

private TextView tvText;
private Button btPrev, btNext;
private CharSequence[] entries;
private int index = 0;

Y los métodos serían así:

public void showNextText() {
    index++;
    if (index >= entries.length) {
        index = 0;
    }

    tvText.setText(entries[index]);
}

public void showPrevText() {
    index--;
    if (index < 0) {
        index = entries.length - 1;
    }

    tvText.setText(entries[index]);
}

Evidentemente el código de este ejemplo no se habría utilizado en un proyecto real, ya que habría sido más eficiente haber llamado directamente a los métodos next y prev cuando se configuraron los eventos onClick de los botones, en lugar de hacer las llamadas a los métodos de la Activity, sin embargo al hacer eso no habríamos podido ver cómo podemos configurar nuestros propios métodos personalizados.

Descargas

Puedes descargar el proyecto completo aquí.

Share if you like...Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0
The following two tabs change content below.
Reborn as IT Developer. Desarrollador Android y fundador de Proyecto Simio. "En realidad, yo no puedo enseñar nada a nadie, sólo puedo hacerles pensar." - Sócrates.

4 thoughts on “Programación Android, Creación de un control personalizado

Deja un comentario