En artículos anteriores de la serie hemos conocido y aprendido a utilizar muchos de los controles que proporciona Android en su SDK. Con la ayuda de estos controles podemos diseñar interfaces gráficas de lo más variopinto pero en ocasiones, si queremos dar un toque especial y original a nuestra aplicación, o simplemente si necesitamos cierta funcionalidad no presente en los componentes estándar de Android, nos vemos en la necesidad de crear nuestros propios controles personalizados, diseñados a la medida de nuestros requisitos.
Android admite por supuesto crear controles personalizados (custom views), y permite hacerlo de diferentes formas:
- Extendiendo la funcionalidad de un control ya existente.
- Combinando varios controles para formar otro más complejo.
- Diseñando desde cero un nuevo control.
En este primer artículo sobre el tema vamos a hablar de la primera opción, es decir, vamos a ver cómo podemos crear un nuevo control partiendo de la base de un control ya existente. A modo de ejemplo, vamos a extender el control EditText
(cuadro de texto) para que muestre en todo momento el número de caracteres que contiene a medida que se escribe en él.
En la esquina superior derecha del cuadro de texto vamos a mostrar el número de caracteres del mensaje de texto introducido, que ira actualizándose a medida que modificamos el texto.
Para empezar, vamos a crear una nueva clase kotlin que extienda del control que queremos utilizar como base, en este caso EditText
.
class ExtendedEditText : EditText { //... }
Tras esto, sobrescribiremos siempre los tres constructores heredados, donde por el momento nos limitaremos a llamar al mismo constructor de la clase padre.
constructor(ctx: Context) : super(ctx) constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) constructor(ctx: Context, attrs: AttributeSet, defStyleAttr: Int) : super(ctx, attrs, defStyleAttr)
Por último el paso más importante. Dado que queremos modificar el aspecto del control para añadir el contador de caracteres tendremos que sobrescribir el evento onDraw()
, que es llamado por Android cada vez que hay que redibujar el control en pantalla. Este método recibe como parámetro un objeto Canvas
, que no es más que el «lienzo» sobre el que podemos dibujar todos los elementos extra necesarios en el control. El objeto Canvas
, proporciona una serie de métodos para dibujar cualquier tipo de elemento (lineas, rectángulos, elipses, texto, bitmaps, …) sobre el espacio ocupado por el control. En nuestro caso tan sólo vamos a necesitar dibujar sobre el control un rectángulo que sirva de fondo para el contador y el texto del contador con el número de caracteres actual del cuadro de texto. No vamos a entrar en muchos detalles sobre la forma de dibujar gráficos, pero vamos a ver al menos las acciones principales.
En primer lugar definiremos como miembros de la clase los «pinceles» (objetos Paint
) que utilizaremos para dibujar, uno de ellos (p1
) de color negro y relleno sólido para el fondo del contador, y otro (p2
) de color blanco para el texto. Para configurar los colores, el estilo de fondo y el tamaño del texto utilizaremos las propiedades color
, style
y textSize
respectivamente:
val p1 = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.BLACK style = Paint.Style.FILL } val p2 = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.WHITE textSize = 30f }
Una vez definidos los diferentes pinceles necesarios, dibujaremos el fondo y el texto del contador mediante los métodos drawRect()
y drawText()
, respectivamente, del objeto canvas
recibido en el evento.
Lo único a tener en cuenta es que todos estos métodos de dibujo reciben las unidades en píxeles y por tanto si utilizamos valores fijos tendremos problemas al visualizar los resultados en pantallas con distintas densidades de píxeles. Para evitar esto en lo posible, tendremos que convertir nuestros valores de píxeles a algún valor dependiente de la densidad de la pantalla, lo que en Android podemos conseguir multiplicando siempre nuestros píxeles por un factor de escala que podemos obtener mediante la propiedad resources.displayMetrics.density
. Tras obtener este valor, multiplicaremos por él todas nuestras unidades en píxeles para conseguir los mismos efectos en cualquier pantalla. Veamos cómo quedaría el código completo:
val escala = resources.displayMetrics.density; //... override fun onDraw(canvas: Canvas) { //Llamamos al método de la clase base (EditText) super.onDraw(canvas) //Dibujamos el fondo negro del contador canvas.drawRect(width - 30*escala, 5 * escala, width - 5*escala, 25*escala, p1) //Dibujamos el número de caracteres sobre el contador canvas.drawText("" + text.toString().length, width - 28*escala, 17*escala, p2) }
Como puede comprobarse, a estos métodos se les pasa como parámetro las coordenadas del elemento a dibujar relativas al espacio ocupado por el control y el pincel a utilizar en cada caso.
Hecho esto, ya tenemos finalizado nuestro cuadro de texto personalizado con contador de caracteres. Para añadirlo a la interfaz de nuestra aplicación lo incluiremos en el layout XML de la ventana tal como haríamos con cualquier otro control, teniendo en cuenta que deberemos hacer referencia a él con el nombre completo de la nueva clase creada (incluido el paquete java), que en mi caso particular sería net.sgoliver.android.controlpers1.ExtendedEditText
.
<net.sgoliver.android.controlpers1.ExtendedEditText android:layout_width="match_parent" android:layout_height="wrap_content" />
Para finalizar, veamos cómo quedaría nuestro control ejecutando la aplicación de ejemplo en el emulador:
En el siguiente artículo veremos cómo crear un control personalizado utilizando la segunda de las opciones expuestas, es decir, combinando varios controles ya existentes. Comentaremos además como añadir eventos y propiedades personalizadas a nuestro control y cómo hacer referencia a dichas propiedades desde su definición XML.
Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del curso en GitHub.
19 comentarios
genial el articulo, de a poco voy aprendiendo sobre el desarrollo en plataforma android y con tus aportes me terminan de cerrar varias cosas.
Jorge-.
Estuve algo ocupado por el trabajo pero fue grato entrar a revisar el blog y encontrar nueva información. Espero mas adelante puesdas tratar algo de almacenamiento de información y comunicacion de aplicaciones andorid con servidores.
Saludos
Sinceramente cuando empecé a revisar manuales y tutoriales de Android se me hacía un mundo. Había cosas que «hacía» porque se debían hacer pero no llegaba a entenderlas… Sicneramente muchas gracias por hacer estos manuales y sobretodo por explicarlos de una manera clara y concisa.
Mil gracias y espero que continúes con estos magníficos tutoriales.
Akru96
[…] tres formas diferentes de crear controles personalizados para nuestras aplicaciones y dedicamos el artículo anterior a comentar la primera de las posibilidades, que consistía en extender la funcionalidad de un […]
[…] Interfaz de usuario en Android: Controles personalizados (I) […]
[…] las posibles vías que tenemos para crear controles personalizados en Android: la primera de ellas extendiendo la funcionalidad de un control ya existente, y como segunda opción creando un nuevo control compuesto por otros más […]
Fantástico tutorial. Este y todos los demás. Gracias.
Genial amigo!, es justo lo que estaba buscando :)
salu2
Yo repito que están muy bien los tutoriales pero me parece que dejas mucho código sin explicación.
necesito extender los atributos de un image button y ya lo consegui pero no puedo crearle un evento que es lo que tendria que hacer para que un botton extended que tiene texto y imagen se pudiera ejecutar
ExtendedImageButton BtnAbono = (ExtendedImageButton)findViewById(R.id.BtnAbono1);
BtnAbono.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
//Creamos el Intent
Intent intent1 = new Intent(FrmClientes.this,FrmAbono.class);
//Creamos la información a pasar entre actividades
startActivity(intent1);
}
});
me urge resolverlo
Buenas,
He probado este codigo y me da el siguiente error: NullPointerException, he intentado analizar todo lo que mi conocimiento me ha permitido y parece que las variables P1 y P2 llegan nulas ¿sabrias decirme porque?
Pego el codigo por si hay algun error:
public class ExtendedEditText extends EditText {
private Paint p1;
private Paint p2;
private float escala;
public ExtendedEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
inicializacion();
}
public ExtendedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
inicializacion();
}
public ExtendedEditText(Context context) {
super(context);
inicializacion();
}
private void inicializacion() {
Paint p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
p1.setColor(Color.BLACK);
p1.setStyle(Style.FILL);
Paint p2 = new Paint(Paint.ANTI_ALIAS_FLAG);
p2.setColor(Color.WHITE);
p2.setTextSize(20);
escala = getResources().getDisplayMetrics().density;
}
@Override
public void onDraw(Canvas canvas) {
// Llamamos al método de la clase base (EditText)
super.onDraw(canvas);
// Dibujamos el fondo negro del contador
canvas.drawRect(this.getWidth() – 30 * escala, 5 * escala,
this.getWidth() – 5 * escala, 20 * escala, p1);
// Dibujamos el número de caracteres sobre el contador
canvas.drawText(«» + this.getText().toString().length(),
this.getWidth() – 28 * escala, 17 * escala, p2);
}
}
Muchas gracias.
Juanfra, seguro que ya lo has solucionado, pero según veo has creado unos atributos privados para p1 y p2 en la clase ExtendedEditText , pero en el método «inicializacion» vuelves a declarar variables con esa misma nomenclatura, así que lo que finalmente le estas pasando al canvas (los atributos de la clase), no ha sido inicializado realmente.
Espero que haberte sido de ayuda.
Salu2.
Habéis hecho un excelente trabajo. Gracias a vosotros aprender Android es sencillo y divertido! :D
Hola amigos buenas tardes, primero que todo agradecerte por los tutoriales, son los mejores!, no me queda muy claro la parte de los constructores, porque 3?, quisiera que me explicaras un poco mejor esta parte del codigo:
public ExtendedEditText(Context context, AttributeSet attrs, int defStyle){
super(context, attrs,defStyle);
}
public ExtendedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExtendedEditText(Context context) {
super(context);
}
Gracias!
[…] tres formas diferentes de crear controles personalizados para nuestras aplicaciones y dedicamos el artículo anterior a comentar la primera de las posibilidades, que consistía en extender la funcionalidad de un […]
[…] las posibles vías que tenemos para crear controles personalizados en Android: la primera de ellas extendiendo la funcionalidad de un control ya existente, y como segunda opción creando un nuevo control compuesto por otros más […]
[…] Interfaz de usuario en Android: Controles personalizados (I) [v3] [Actualizado] […]
[…] tres formas diferentes de crear controles personalizados para nuestras aplicaciones y dedicamos el artículo anterior a comentar la primera de las posibilidades, que consistía en extender la funcionalidad de un […]
[…] las posibles vías que tenemos para crear controles personalizados en Android: la primera de ellas extendiendo la funcionalidad de un control ya existente, y como segunda opción creando un nuevo control compuesto por otros más […]