Dibujar formas geométricas en Android

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.

Ejercicio – Dibujar un círculo

res/layout/main.xml
...
<com.pablomonteserin.Vista android:layout_width="match_parent" android:layout_height="match_parent" />
...
com.pablomonteserin.Vista.java
public class Vista extends View{
	Paint pincel;
	int color;
	public Vista(Context context){
		super(context);
		iniciar();
	}
	public Vista(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		iniciar();
	}
	public Vista(Context context, AttributeSet attrs) {
		super(context, attrs);
		iniciar();
	}

	private void iniciar(){
		pincel = new Paint();
		//Hay varias formas de definir los colores	
		//color = Color.argb(127, 0, 255, 0);
		//color = 0x7F00FF00;
		color = Color.BLUE;
		pincel.setColor(color);
		pincel.setStrokeWidth(8);
		pincel.setStyle(Paint.Style.STROKE);
	}
	protected void onDraw(Canvas canvas){
		super.onDraw(canvas);
		canvas.drawCircle(150, 150, 100, pincel);
	}
}

La clase vista debe tener tres constructores:
El primero es para crear la vista desde código. Los otros dos son para inflar la vista llamándola desde un fichero xml.

Hay que procurar no hacer ninguna instancia e incluso tener el mínimo código posible dentro del método onDraw(), ya que será llamado varias veces por segundo y relentizaría la aplicación.

Ejercicio – Dibujar un Path (trazo)

trazo y pincel son dos objetos de las clases Path y Paint respectivamente.

protected void onDraw(Canvas canvas){
	super.onDraw(canvas);
	trazo.addCircle(150, 150, 100, Path.Direction.CCW);
	pincel.setColor(Color.BLUE);
	pincel.setStrokeWidth(8);	
	pincel.setStyle(Paint.Style.STROKE);
	canvas.drawPath(trazo, pincel);
	pincel.setStrokeWidth(1);
	pincel.setStyle(Paint.Style.FILL);
	pincel.setTextSize(20);
	pincel.setTypeface(Typeface.SANS_SERIF);
	canvas.drawTextOnPath("Pablo Monteserín", trazo, 10, 40, pincel);
}
Dibujar formas geométricas en Android 1

Ejercicio – Dibujar siguiendo un path II

trazo y pincel son dos objetos de las clases Path y Paint respectivamente. Sin embargo, no deben ser instanciados dentro del método onDraw(), ya que este es llamado varias veces por segundo y ralentizaría la aplicación.

protected void onDraw(Canvas canvas){
	super.onDraw(canvas);
	trazo.moveTo(50, 100);
	strazo.cubicTo(60, 70, 150, 90, 200, 110);
	strazo.lineTo(300, 200);
	canvas.drawColor(Color.YELLOW);
	pincel.setColor(Color.BLUE);
	pincel.setStrokeWidth(8);
	pincel.setStyle(Style.STROKE);
	canvas.drawPath(trazo, pincel);
	pincel.setStrokeWidth(1);
	pincel.setStyle(Style.FILL);
	pincel.setTextSize(20);
	pincel.setTypeface(Typeface.SANS_SERIF);
	canvas.drawTextOnPath("Pablo Monteserín", trazo, 10, 40, pincel);
}
Dibujar formas geométricas en Android 2

Ejercicio: Dibujar un rectángulo

protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	pincel.setColor(Color.BLUE);
	pincel.setStrokeWidth(8);
	pincel.setStyle(Paint.Style.STROKE);
	canvas.drawRect(0, 0, 200, 300, pincel);
}

Ejercicio: Dibujar un rectángulo con relleno

protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	pincel.setColor(Color.BLUE);
	pincel.setStrokeWidth(8);
	pincel.setStyle(Paint.Style.STROKE);
	canvas.drawRect(10, 10, 200, 50, pincel);
	pincel.setColor(Color.RED);
	pincel.setStyle(Paint.Style.FILL);
	canvas.drawRect(12,12,198,48, pincel);
}

Recuperar atributos del xml

private void iniciar(Context context, AttributeSet attrs){
	pincel = new Paint();
	TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.Monteserin_circle);
	int color = attributes.getColor(R.styleable.Monteserin_circle_monteserin_color, Color.GREEN);
	pincel.setColor(color);
}
res/values/attrs.xml
<resources>
    <declare-styleable name="Monteserin_circle">
        <attr name="monteserin_color" format="color" />
    </declare-styleable>
</resources>

Definir el tamaño del Canvas

Por defecto, el tamaño de una vista si no definimos otra cosa, será todo el alto de la pantalla. Por tanto, si ponemos dos elementos seguidos, el segundo no se verá porque el primero ocupará toda la pantalla.

La solución es definir una altura específica al escribir el código en el xml, o sobreescribir el siguiente método en el archivo de la vista:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//Estos valores definirán el tamaño de la vista
int desiredWidth = 100;
int desiredHeight = 50;

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

int width;
int height;

//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
	//Must be this size
	width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
	//Can't be bigger than...
	width = Math.min(desiredWidth, widthSize);
} else {
	//Be whatever you want
	width = desiredWidth;
}

//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
	//Must be this size
	height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
	//Can't be bigger than...
	height = Math.min(desiredHeight, heightSize);
} else {
	//Be whatever you want
	height = desiredHeight;
}

//MUST CALL THIS
setMeasuredDimension(width, height);
}

Ejercicio: Repintar con OnTouchEvent

Al pulsar sobre un rectángulo, este deberá repintarse en una posición diferente.

@Override
public boolean onTouchEvent(MotionEvent event) {
	x = event.getX();
	y = event.getY();
	//Fuerzo el repintado
	this.invalidate();
	return super.onTouchEvent(event);
}

El relleno del rectángulo crece con un click del ratón

protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		pincel.setColor(Color.BLUE);
		pincel.setStrokeWidth(8);
		pincel.setStyle(Style.STROKE);
		canvas.drawRect(10, 10, getWidth(), 50, pincel);
		pincel.setColor(Color.RED);
		pincel.setStyle(Style.FILL);
		canvas.drawRect(12,12,getWidth()*this.width-3,48, pincel);
	}

	public void aumentarWidth(){
		if(this.width<1){
			this.width = (float) (this.width+0.1);
			this.postInvalidate();
		}
	}

Este método fuerza a volver a llamar al método onDraw para que el dibujo se repinte. Si no lo llamamos, el repintado no estará asegurado.

Hacer un juego sencillo

Se trata de un juego en el que un monstruo (una clase que hereda de ImageView) va cambiando de posición en la pantalla y cada vez que pulsamos sobre él aumenta un marcador.

Para realizar una tarea cada x tiempo utilizaremos:

private Handler handler;
...
handler = new Handler();
handler.postDelayed(runnable, 1000);

...
private Runnable runnable = new Runnable() {
	@Override
	public void run() {
		setRandomPosition(iv);
		handler.postDelayed(this, 1000);
	}
};

public void setRandomPosition(ImageView a1){
	int x = random.nextInt(width);
	int y = random.nextInt(height);
	a1.setX(x);
	a1.setY(y);
}

Detectar cuando se ha cargado la vista para recuperar tamaños y posiciones

@Override
public void onWindowFocusChanged(boolean hasFocus) {
	super.onWindowFocusChanged(hasFocus);
	final View content = getWindow().findViewById(Window.ID_ANDROID_CONTENT);

	view_width = content.getWidth()-a1.getWidth();
	view_height = content.getHeight()-a1.getHeight()-getStatusBarHeight();
}

public int getStatusBarHeight(){
        Rect rectangle= new Rect();
        Window window= getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rectangle);
        int statusBarHeight = rectangle.top;
        int contentViewTop = window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
        int titleBarHeight= contentViewTop - statusBarHeight;
        return statusBarHeight+titleBarHeight;
}

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.