Android SDK: Crea una aplicación de dibujo: Funcionalidad esencial
Spanish (Español) translation by CYC (you can also view the original English article)
En esta serie, crearemos una aplicación en Android que permita pintar con los dedos utilizando la interacción táctil. El usuario podrá seleccionar desde una paleta de colores, elegir un tamaño de pincel, borrar, crear un nuevo dibujo o guardar su dibujo existente en la galería del dispositivo.
Formato de la serie
Esta serie sobre Crear una aplicación de dibujo se dividirá en tres partes:
En la primera parte de la serie, creamos la interfaz de usuario. En la segunda parte implementamos la opción para dibujar en el lienzo y elegir colores. En esta parte final de la serie presentaremos la capacidad de borrar, crear nuevos dibujos y guardar un dibujo en la galería del dispositivo del usuario. Veremos las opciones que puedes usar para mejorar esta aplicación en futuros tutoriales, incluidos los rellenos de patrones y la opacidad.
Vista previa del final



1. Elegir los tamaños del pincel
Paso 1
La última vez implementamos el dibujo en el lienzo, ahora podemos dejar que el usuario elija un tamaño de pincel. Las opciones del tamaño del pincel aparecerán cuando el usuario presione el botón del pincel que añadimos a la interfaz. Para responder a esto, amplía la línea de apertura de tu declaración de clase Activity principal para implementar la interfaz OnClickListener:
1 |
|
2 |
public class MainActivity extends Activity implements OnClickListener |
Necesitarás las siguientes declaraciones de importación agregadas a la clase para este tutorial:
1 |
|
2 |
import java.util.UUID; |
3 |
import android.provider.MediaStore; |
4 |
import android.app.AlertDialog; |
5 |
import android.app.Dialog; |
6 |
import android.content.DialogInterface; |
7 |
import android.view.View.OnClickListener; |
8 |
import android.widget.Toast; |
Agrega las siguientes variables de instancia a la clase para almacenar los tres valores de dimensión que definimos la última vez:
1 |
|
2 |
private float smallBrush, mediumBrush, largeBrush; |
Crear una instancia en onCreate:
1 |
|
2 |
smallBrush = getResources().getInteger(R.integer.small_size); |
3 |
mediumBrush = getResources().getInteger(R.integer.medium_size); |
4 |
largeBrush = getResources().getInteger(R.integer.large_size); |
Usaremos esto más tarde. Ya deberías tener una variable de instancia ImageButton en la clase principal llamada "currPaint": amplía esa línea para agregar otra ahora para el botón de dibujo:
1 |
|
2 |
private ImageButton currPaint, drawBtn; |
En onCreate, recupera una referencia al botón del diseño:
1 |
|
2 |
drawBtn = (ImageButton)findViewById(R.id.draw_btn); |
Establece la clase como receptor del clic para el botón:
1 |
|
2 |
drawBtn.setOnClickListener(this); |
Paso 2
Agrega un método onClick a la clase:
1 |
|
2 |
@Override
|
3 |
public void onClick(View view){ |
4 |
//respond to clicks
|
5 |
}
|
Dentro del método, verifica si hay clics en el botón de dibujo:
1 |
|
2 |
if(view.getId()==R.id.draw_btn){ |
3 |
//draw button clicked
|
4 |
}
|
Agregaremos bloques condicionales al método onClick para los otros botones más adelante.
Paso 3
Cuando el usuario haga clic en el botón, se mostrará un cuadro de diálogo con los tres tamaños de botón. Dentro del bloque if, crea un cuadro de diálogo y establece el título:
1 |
|
2 |
final Dialog brushDialog = new Dialog(this); |
3 |
brushDialog.setTitle("Brush size:"); |
Definamos el diseño del cuadro de diálogo en XML: agrega un nuevo archivo en la carpeta "res/layout" de tu aplicación y asígnale el nombre "brush_chooser.xml" e ingresa el siguiente esquema:
1 |
|
2 |
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android" |
3 |
android:layout_width="match_parent" |
4 |
android:layout_height="match_parent" |
5 |
android:gravity="center" |
6 |
android:orientation="vertical" > |
7 |
|
8 |
</LinearLayout>
|
Dentro del diseño lineal, agrega un botón para cada tamaño:
1 |
|
2 |
<ImageButton
|
3 |
android:id="@+id/small_brush" |
4 |
android:layout_width="wrap_content" |
5 |
android:layout_height="wrap_content" |
6 |
android:layout_weight="1" |
7 |
android:contentDescription="@string/sml" |
8 |
android:src="@drawable/small" /> |
9 |
|
10 |
<ImageButton
|
11 |
android:id="@+id/medium_brush" |
12 |
android:layout_width="wrap_content" |
13 |
android:layout_height="wrap_content" |
14 |
android:layout_weight="1" |
15 |
android:contentDescription="@string/med" |
16 |
android:src="@drawable/medium" /> |
17 |
|
18 |
<ImageButton
|
19 |
android:id="@+id/large_brush" |
20 |
android:layout_width="wrap_content" |
21 |
android:layout_height="wrap_content" |
22 |
android:layout_weight="1" |
23 |
android:contentDescription="@string/lrg" |
24 |
android:src="@drawable/large" /> |
Cada botón tiene un id para la identificación de Activity en código Java. Notarás que cada uno tiene un atributo de descripción de contenido: agrega las cadenas especificadas a tu archivo XML de strings "res/values":
1 |
|
2 |
<string name="sml">Small</string> |
3 |
<string name="med">Medium</string> |
4 |
<string name="lrg">Large</string> |
Como puedes ver en el archivo de diseño, cada botón también tiene un archivo 'drawable' como su atributo fuente. Ahora crea nuevos archivos para cada uno de estos en tu(s) carpeta(s) "res/drawables", comenzando con "small.xml" e ingresa el siguiente contenido:
1 |
|
2 |
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
3 |
android:dither="true" |
4 |
android:shape="oval" > |
5 |
|
6 |
<size
|
7 |
android:height="@dimen/small_brush" |
8 |
android:width="@dimen/small_brush" /> |
9 |
|
10 |
<solid android:color="#FF666666" /> |
11 |
|
12 |
</shape>
|
Ten en cuenta que usamos los valores de dimensión que definimos. A continuación, agrega "medium.xml" a la carpeta de los drawables, e ingresa la siguiente forma:
1 |
|
2 |
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
3 |
android:dither="true" |
4 |
android:shape="oval" > |
5 |
|
6 |
<size
|
7 |
android:height="@dimen/medium_brush" |
8 |
android:width="@dimen/medium_brush" /> |
9 |
|
10 |
<solid android:color="#FF666666" /> |
11 |
|
12 |
</shape>
|
Finalmente agrega "large.xml" con el siguiente contenido:
1 |
|
2 |
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
3 |
android:dither="true" |
4 |
android:shape="oval" > |
5 |
|
6 |
<size
|
7 |
android:height="@dimen/large_brush" |
8 |
android:width="@dimen/large_brush" /> |
9 |
|
10 |
<solid android:color="#FF666666" /> |
11 |
|
12 |
</shape>
|
De vuelta a tu clase principal Activity en el método OnClick, después de crear el cuadro de diálogo y establecer el título, ahora puedes establecer el diseño:
1 |
|
2 |
brushDialog.setContentView(R.layout.brush_chooser); |
Paso 4
Antes de continuar con el cuadro de diálogo, modifiquemos la clase de vista de dibujo personalizado para usar diferentes tamaños de pincel. En tu clase DrawingView, agrega las siguientes instrucciones de importación para este tutorial:
1 |
|
2 |
import android.graphics.PorterDuff; |
3 |
import android.graphics.PorterDuffXfermode; |
4 |
import android.util.TypedValue; |
Agrega dos variables de instancia a la clase:
1 |
|
2 |
private float brushSize, lastBrushSize; |
Utilizaremos la primera variable para el tamaño del pincel y la segunda para realizar un seguimiento del último tamaño del pincel utilizado cuando el usuario cambie al borrador, de modo que podamos volver al tamaño correcto cuando decidan volver al dibujo. En el método setupDrawing, antes del código que ya está allí, agrega lo siguiente para crear una instancia de estas variables:
1 |
|
2 |
brushSize = getResources().getInteger(R.integer.medium_size); |
3 |
lastBrushSize = brushSize; |
Para empezar, usamos el valor de dimensión para el pincel de tamaño mediano. Ahora puedes actualizar la línea en el método donde estableciste el ancho de trazo con un valor codificado para usar este valor de variable en su lugar:
1 |
|
2 |
drawPaint.setStrokeWidth(brushSize); |
Agrega el siguiente método a la clase para establecer el tamaño del pincel:
1 |
|
2 |
public void setBrushSize(float newSize){ |
3 |
//update size
|
4 |
}
|
Dentro del método, actualiza el tamaño del pincel con el valor pasado:
1 |
|
2 |
float pixelAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, |
3 |
newSize, getResources().getDisplayMetrics()); |
4 |
brushSize=pixelAmount; |
5 |
drawPaint.setStrokeWidth(brushSize); |
Pasaremos el valor del archivo de las dimensiones cuando llamemos a este método, por lo que debemos calcular su valor de dimensión. Actualizamos la variable y el objeto Paint para usar el nuevo tamaño. Ahora agrega métodos para obtener y establecer la otra variable del tamaño que creamos:
1 |
|
2 |
public void setLastBrushSize(float lastSize){ |
3 |
lastBrushSize=lastSize; |
4 |
}
|
5 |
public float getLastBrushSize(){ |
6 |
return lastBrushSize; |
7 |
}
|
Llamaremos a estos métodos desde la clase principal Activity.
Paso 5
De vuelta a tu clase principal Activity, completemos el código de diálogo en el método onClick. Después de configurar la vista de contenido en el objeto Dialog, recibe los clics en los tres tamaños de los botones, comenzando por el más pequeño:
1 |
|
2 |
ImageButton smallBtn = (ImageButton)brushDialog.findViewById(R.id.small_brush); |
3 |
smallBtn.setOnClickListener(new OnClickListener(){ |
4 |
@Override
|
5 |
public void onClick(View v) { |
6 |
drawView.setBrushSize(smallBrush); |
7 |
drawView.setLastBrushSize(smallBrush); |
8 |
brushDialog.dismiss(); |
9 |
}
|
10 |
});
|
Configuramos el tamaño utilizando los métodos que agregamos a la clase personalizada View tan pronto como el usuario hace clic en un botón de tamaño de pincel, y luego descarta inmediatamente el Diálogo. Luego haz lo mismo con los botones medianos y grandes:
1 |
|
2 |
ImageButton mediumBtn = (ImageButton)brushDialog.findViewById(R.id.medium_brush); |
3 |
mediumBtn.setOnClickListener(new OnClickListener(){ |
4 |
@Override
|
5 |
public void onClick(View v) { |
6 |
drawView.setBrushSize(mediumBrush); |
7 |
drawView.setLastBrushSize(mediumBrush); |
8 |
brushDialog.dismiss(); |
9 |
}
|
10 |
});
|
11 |
|
12 |
ImageButton largeBtn = (ImageButton)brushDialog.findViewById(R.id.large_brush); |
13 |
largeBtn.setOnClickListener(new OnClickListener(){ |
14 |
@Override
|
15 |
public void onClick(View v) { |
16 |
drawView.setBrushSize(largeBrush); |
17 |
drawView.setLastBrushSize(largeBrush); |
18 |
brushDialog.dismiss(); |
19 |
}
|
20 |
});
|
Completa la sección de botón de dibujo de onClick mostrando el cuadro de diálogo:
1 |
|
2 |
brushDialog.show(); |
El cuadro de diálogo se mostrará hasta que el usuario haga una selección o vuelva a la actividad.



Paso 6
Usa el nuevo método para establecer el tamaño inicial del pincel en onCreate:
1 |
|
2 |
drawView.setBrushSize(mediumBrush); |
2. Borrando
Paso 1
Ahora agreguemos el borrado a la aplicación. En la clase drawing View, agrega una variable de instancia booleana para que actúe como indicador de si el usuario actualmente está borrando o no:
1 |
|
2 |
private boolean erase=false; |
Inicialmente asumiremos que el usuario está dibujando, no borrando. Agrega el siguiente método a la clase:
1 |
|
2 |
public void setErase(boolean isErase){ |
3 |
//set erase true or false
|
4 |
}
|
Dentro del método, primero actualiza la variable booleana:
1 |
|
2 |
erase=isErase; |
Ahora modifica el objeto Paint para borrar o que se cambie para dibujar:
1 |
|
2 |
if(erase) drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); |
3 |
else drawPaint.setXfermode(null); |
Si estás buscando un tema avanzado para explorar, echa un vistazo a las opciones de PorterDuff.Mode.
Paso 2
De vuelta a la clase principal Activity, agrega otro ImageButton a la lista de variables de instancia:
1 |
|
2 |
private ImageButton currPaint, drawBtn, eraseBtn; |
En onCreate, recupera una referencia al botón y configura la clase para recibir los clics:
1 |
|
2 |
eraseBtn = (ImageButton)findViewById(R.id.erase_btn); |
3 |
eraseBtn.setOnClickListener(this); |
Agrega una declaración condicional para el botón en onClick después del condicional para el botón de dibujo:
1 |
|
2 |
else if(view.getId()==R.id.erase_btn){ |
3 |
//switch to erase - choose size
|
4 |
}
|
Al igual que con el botón Dibujar, le dejaremos elegir un tamaño de borrador al usuario desde un Diálogo. Dentro del bloque condicional para el botón borrar, crea y prepara el cuadro de diálogo como antes:
1 |
|
2 |
final Dialog brushDialog = new Dialog(this); |
3 |
brushDialog.setTitle("Eraser size:"); |
4 |
brushDialog.setContentView(R.layout.brush_chooser); |
Usamos el mismo diseño que el cuadro de diálogo Dibujar. Configura los receptores para seleccionar el tamaño de los botones, igual que antes. Esta vez llamando al nuevo método de borrado que agregamos a la clase View:
1 |
|
2 |
ImageButton smallBtn = (ImageButton)brushDialog.findViewById(R.id.small_brush); |
3 |
smallBtn.setOnClickListener(new OnClickListener(){ |
4 |
@Override
|
5 |
public void onClick(View v) { |
6 |
drawView.setErase(true); |
7 |
drawView.setBrushSize(smallBrush); |
8 |
brushDialog.dismiss(); |
9 |
}
|
10 |
});
|
11 |
ImageButton mediumBtn = (ImageButton)brushDialog.findViewById(R.id.medium_brush); |
12 |
mediumBtn.setOnClickListener(new OnClickListener(){ |
13 |
@Override
|
14 |
public void onClick(View v) { |
15 |
drawView.setErase(true); |
16 |
drawView.setBrushSize(mediumBrush); |
17 |
brushDialog.dismiss(); |
18 |
}
|
19 |
});
|
20 |
ImageButton largeBtn = (ImageButton)brushDialog.findViewById(R.id.large_brush); |
21 |
largeBtn.setOnClickListener(new OnClickListener(){ |
22 |
@Override
|
23 |
public void onClick(View v) { |
24 |
drawView.setErase(true); |
25 |
drawView.setBrushSize(largeBrush); |
26 |
brushDialog.dismiss(); |
27 |
}
|
28 |
});
|
Llamamos al método para establecer el tamaño del pincel como con el botón de dibujar, esta vez primero configurando el indicador de borrado en true. Finalmente, muestra el Diálogo.
1 |
|
2 |
brushDialog.show(); |



El usuario podrá borrar utilizando la interacción táctil como con el dibujo:



Paso 3
Cuando el usuario hace clic en el botón Dibujar y elige un tamaño de pincel, debemos volver al dibujo en caso de que haya sido borrado previamente. En los tres click's receptores que agregaste para los botones pequeños, medianos y grandes en la sección de botón de dibujo de OnClick, llama al método de borrado con un parámetro falso - agrega esto en cada onClick antes de ocultar al objeto "brushDialog":
1 |
|
2 |
drawView.setErase(false); |
Cuando el usuario haya estado borrando y haga clic en un botón de color de pintura, asumiremos que quiere volver a dibujar. En el método paintClicked, antes del código existente, llama al método de borrado, pasándolo a false:
1 |
|
2 |
drawView.setErase(false); |
Todavía dentro de paintClicked, establece el tamaño del pincel de nuevo al último utilizado al dibujar antes de borrar:
1 |
|
2 |
drawView.setBrushSize(drawView.getLastBrushSize()); |
Este tipo de procesamiento está motivado por suposiciones sobre lo que el usuario quiere hacer en función de sus acciones: podrías mejorar esta aplicación en estas líneas, así que ten esto en cuenta si deseas seguir trabajando en la aplicación más adelante.
3. Nuevos dibujos
Paso 1
Agregaremos un botón para que el usuario comience un nuevo dibujo, así que vamos a implementarlo ahora. Agrega otro a la lista de variables de instancia de ImageButton en tu clase de actividad principal:
1 |
|
2 |
private ImageButton currPaint, drawBtn, eraseBtn, newBtn; |
Crea una instancia con una referencia al botón que se muestra en el diseño, en onCreate, también recibiendo los clics:
1 |
|
2 |
newBtn = (ImageButton)findViewById(R.id.new_btn); |
3 |
newBtn.setOnClickListener(this); |
Paso 2
En onClick, agrega otro bloque condicional para el nuevo botón:
1 |
|
2 |
else if(view.getId()==R.id.new_btn){ |
3 |
//new button
|
4 |
}
|
En tu clase personalizada drawing View agrega un método para comenzar un nuevo dibujo:
1 |
|
2 |
public void startNew(){ |
3 |
drawCanvas.drawColor(0, PorterDuff.Mode.CLEAR); |
4 |
invalidate(); |
5 |
}
|
El método simplemente borra el lienzo y actualiza la pantalla.
De vuelta a la clase principal Activity haz un condicional para el nuevo botón en onCreate, verifiquemos que el usuario definitivamente quiera comenzar un nuevo dibujo:
1 |
|
2 |
AlertDialog.Builder newDialog = new AlertDialog.Builder(this); |
3 |
newDialog.setTitle("New drawing"); |
4 |
newDialog.setMessage("Start new drawing (you will lose the current drawing)?"); |
5 |
newDialog.setPositiveButton("Yes", new DialogInterface.OnClickListener(){ |
6 |
public void onClick(DialogInterface dialog, int which){ |
7 |
drawView.startNew(); |
8 |
dialog.dismiss(); |
9 |
}
|
10 |
});
|
11 |
newDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener(){ |
12 |
public void onClick(DialogInterface dialog, int which){ |
13 |
dialog.cancel(); |
14 |
}
|
15 |
});
|
16 |
newDialog.show(); |
El cuadro de diálogo le permite al usuario cambiar de opinión y llamar al nuevo método si decide continuar y comenzar un nuevo dibujo, en cuyo caso se borrará el dibujo actual.



4. Guardar dibujos
Paso 1
La única parte restante de la funcionalidad de la aplicación es la capacidad de guardar dibujos en el dispositivo. Agrega el botón Guardar como el último en la secuencia de variables de instancia de ImageButton en la clase principal Activity:
1 |
|
2 |
private ImageButton currPaint, drawBtn, eraseBtn, newBtn, saveBtn; |
Crea una instancia y recibe los clics en onCreate:
1 |
|
2 |
saveBtn = (ImageButton)findViewById(R.id.save_btn); |
3 |
saveBtn.setOnClickListener(this); |
Agrega un condicional para ello en onClick:
1 |
|
2 |
else if(view.getId()==R.id.save_btn){ |
3 |
//save drawing
|
4 |
}
|
Usemos un algoritmo similar al que usamos para crear dibujos nuevos, y verifiquemos que el usuario quiera continuar y guardar:
1 |
|
2 |
AlertDialog.Builder saveDialog = new AlertDialog.Builder(this); |
3 |
saveDialog.setTitle("Save drawing"); |
4 |
saveDialog.setMessage("Save drawing to device Gallery?"); |
5 |
saveDialog.setPositiveButton("Yes", new DialogInterface.OnClickListener(){ |
6 |
public void onClick(DialogInterface dialog, int which){ |
7 |
//save drawing
|
8 |
}
|
9 |
});
|
10 |
saveDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener(){ |
11 |
public void onClick(DialogInterface dialog, int which){ |
12 |
dialog.cancel(); |
13 |
}
|
14 |
});
|
15 |
saveDialog.show(); |



Si el usuario elige continuar y guardar, necesitamos mostrar la vista que se muestra actualmente como una imagen. En el método onClick para el cuadro de diálogo Guardar, comienza por habilitar la memoria caché de dibujo en la Vista personalizada:
1 |
|
2 |
drawView.setDrawingCacheEnabled(true); |
Ahora intenta escribir la imagen en un archivo:
1 |
|
2 |
String imgSaved = MediaStore.Images.Media.insertImage( |
3 |
getContentResolver(), drawView.getDrawingCache(), |
4 |
UUID.randomUUID().toString()+".png", "drawing"); |
Toma un momento para revisar este código. Utilizamos el método de inserción de imagen para intentar escribir la imagen en el almacenamiento de medios en el dispositivo, en pocas palabras deberá quedar guardada en la galería del usuario. Pasamos el solucionador de contenido ,el caché de dibujo para la Vista mostrada, una cadena UUID generada aleatoriamente para el nombre de archivo con extensión PNG y una breve descripción. El método devuelve la URL de la imagen creada, o nulo si la operación no fue exitosa; esto nos permite brindar retroalimentación a los usuarios:
1 |
|
2 |
if(imgSaved!=null){ |
3 |
Toast savedToast = Toast.makeText(getApplicationContext(), |
4 |
"Drawing saved to Gallery!", Toast.LENGTH_SHORT); |
5 |
savedToast.show(); |
6 |
}
|
7 |
else{ |
8 |
Toast unsavedToast = Toast.makeText(getApplicationContext(), |
9 |
"Oops! Image could not be saved.", Toast.LENGTH_SHORT); |
10 |
unsavedToast.show(); |
11 |
}
|
Destruye la caché de dibujo para que los futuros dibujos guardados no usen la caché existente:
1 |
|
2 |
drawView.destroyDrawingCache(); |
Finalmente, agrega el siguiente permiso al archivo de manifiesto de tu proyecto:
1 |
|
2 |
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |



Al navegar a la galería del dispositivo, el usuario ahora debería poder ver su dibujo:



Conclusión
¡Este tutorial completa la funcionalidad básica de nuestra aplicación de dibujo! Cuando ejecutas la aplicación, deberías poder dibujar, elegir colores, elegir tamaños de pincel y borrador, comenzar nuevos dibujos y guardar dibujos en la galería. Hemos trabajado en el proceso básico de facilitar el dibujo mediante la interacción con la pantalla táctil en Android, pero hay muchas maneras en que puedes mejorar la aplicación, así que intenta experimentar con ella para incorporar tu propia funcionalidad adicional. En tutoriales futuros, cubriremos el uso de rellenos de patrón en lugar de simplemente dibujar con colores sólidos, usar opacidad y dejar que el usuario elija un nivel de opacidad. También cubriremos el dibujo en dispositivos Android donde el modelo de interacción no sea una pantalla táctil, por ejemplo, con un mouse o bola de seguimiento.



