sep 082012
 
Artículo Android

Dibujando sobre un View

El sistema android incluye la clase Canvas que implementa numerosos métodos (drawLine, drawRect, drawCircle,…) para dibujar gráficos en pantalla. La manera más sencilla de dibujar gráficos en Android es dibujarlos sobre el Canvas (Lienzo) asociado a un View.

Esta manera es válida para aplicaciones que dibujan gráficos estáticos o de movimientos lentos. Pero para algunos juegos y otro tipo de aplicaciones en donde el rendimiento gráfico es fundamental, android ofrece una clase especializada llamada SurfaceView, que comentamos en el siguiente apartado.

Cada objeto View de una aplicación tiene asociado un objeto Canvas. Este objeto es pasado como argumento en las llamadas onDraw que realiza el sistema cuando es necesario presentar el View. Por lo tanto, para dibujar en el Canvas basta con hacer “override” del método onDraw. El siguiente código es un sencillo ejemplo:

package com.openalfa.testgraphics;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.Menu;
import android.view.View;
import android.view.Window;

public class TestGraphics extends Activity {
    Paint paint;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(1);
        paint.setTextSize(20);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(new Panel(this));
    }

    class Panel extends View {
        public Panel(Context context) {
            super(context);
        }

        @Override
        public void onDraw(Canvas canvas) {
            canvas.drawColor(Color.WHITE);
            canvas.drawCircle(50, 80, 30, paint);
            canvas.drawLine(80, 80, 80, 200, paint);
            canvas.drawText(""+canvas.getWidth()+", "+canvas.getHeight(), 0, 200,paint);
            }
    }

}

En la llamada onCreate del programa principal TestGraphics, inicializamos un objeto paint con la configuración deseada de color, ancho de línea, etc… A continuación, en la línea 24 asociamos el programa a un View al que llamamos Panel, que definimos más adelante, a partir de la línea 27.

En la implementación del View “Panel”, simplemente hacemos override del método onDraw, para dibujar un círculo, una línea y un texto con las dimensiones del canvas. Al ejecutar el programa en el emulador, el resultado es:

Si durante la ejecución de la aplicación queremos volver a dibujar sobre el Canvas, debemos llamar al método “invalidate()” del View, lo que provoca que el sistema realice una nueva llamada al método onDraw.

Nota: Para solicitar el refresco del gráfico desde un thread distinto, en lugar de llamar a invalidate() se debe llamar a postInvalidate().

 

 

Dibujando sobre un SurfaceView

 

SurfaceView es una subclase de la clase View optimizada para el rendimiento de operaciones gráficas. Una instancia de esta clase debe ser manejada desde un hilo de ejecución secundario, de modo que las operaciones que efectúan no tengan que esperar al proceso de la jerarquía de Views que realiza el sistema periódicamente.

Comenzamos creando una clase “Superficie” que extiende de SurfaceView , y que implementa el interfaz SurfaceHolder.Callback. Este interfaz consiste en tres funciones “surfaceCreated”, “surfaceDestroyed” y “surfaceChanged”, que son llamadas por el sistema cada vez que la superficie de dibujo es creada, destruida, o modificada de algún modo.

En el constructor de la clase Superficie hay que llamar a SurfaceHolder.addCallback(this) para indicar que deseamos procesar los eventos de creación, destrucción y modificación generados por la superficie de dibujo.

public class Superficie extends SurfaceView implements SurfaceHolder.Callback {
    // Variables globales
    private Boolean _run;
    public Boolean isDrawing = true;
    protected HiloDibujo thread;
    private Bitmap mBitmap;
    ...
    // Constructor de la clase Superficie
    public Superficie(Context context, AttributeSet attrs) {
        super(context, attrs);
        getHolder().addCallback(this);
        ...
    }

    public void surfaceCreated(SurfaceHolder holder) {
        ...
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        ...
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width,  int height) {
        ...
    }

}

También necesitamos una clase “HiloDibujo” que extienda de Thread para efectuar las operaciones gráficas en el SurfaceView. El código fuente de esta clase se incluye normalmente en el interior de la clase SurfaceView. Al ser un Thread, su ejecución es independiente y se realiza en paralelo a otros procesos que puede estar ejecutando la aplicación.

El thread es creado mediante una llamada a “new HiloDibujo()” realizada desde el constructor de la clase Superficie. La ejecución del thread comienza con la llamada a thread.start() realizada desde el método “surfaceCreated”.

En el interior de la clase Thread, el acceso al área de dibujo se realiza a través de una instancia SurfaceHolder que se obtiene mediante una llamada a getHolder() en el constructor del View Superficie.

Para comenzar a dibujar, obtenemos una referencia al canvas con una llamada al método  lockCanvas() del SurfaceHolder. Con ella, llamamos a los métodos de dibujo deseados (drawLine, drawText,…). Para finalizar y presentar el resultado en pantalla, llamamos a unlockCanvasAndPost():

    // Constructor de la clase Superficie
    public Superficie(Context context, AttributeSet attrs) {
        super(context, attrs);
        // Indica que esta clase procesa los eventos de creación,
        // destruccion y cambio del surfaceHolder.
        getHolder().addCallback(this);
        // Crea el thread, y pásale la referencia al SurfaceHolder
        thread = new HiloDibujo(getHolder());
    }
    class HiloDibujo extends  Thread {
        // Variable local para almacenar la referencia al surfaceHolder
        private SurfaceHolder mSurfaceHolder;

        // Constructor del Thread HiloDibujo
        public HiloDibujo(SurfaceHolder surfaceHolder){
            // Guarda en una variable local la referencia al surfaceHolder.
            mSurfaceHolder = surfaceHolder;
        }

        // Método llamado por los callbacks surfaceCreated y surfaceDestroyed
        public void setRunning(boolean run) {
            _run = run;
        }

        @Override
        public void run() {
            ...

            while (_run){
                if(isDrawing == true){
                    try{
                        canvas = mSurfaceHolder.lockCanvas(null);
                        // Realiza operaciones gráficas sobre el canvas
                    } finally {
                        mSurfaceHolder.unlockCanvasAndPost(canvas);
                    }
                }
            }
        }
    }

 

Normalmente, comenzamos el dibujo inicializando el área completa con color de fondo mediante una llamada a  drawColor() o bien con una imagen de fondo mediante una llamada a drawBitmap().

El código completo de la clase Superficie es:

package com.openalfa.testgraphics;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class Superficie extends SurfaceView implements SurfaceHolder.Callback {
    // Variables globales
    private Boolean _run;
    public boolean isDrawing = true;
    protected HiloDibujo thread;
    private Bitmap mBitmap;

    // Constructor
    public Superficie(Context context, AttributeSet attrs) {
        super(context, attrs);
        getHolder().addCallback(this);
        thread = new HiloDibujo(getHolder());
    }

    class HiloDibujo extends  Thread {
        private SurfaceHolder mSurfaceHolder;

        public HiloDibujo(SurfaceHolder surfaceHolder){
            mSurfaceHolder = surfaceHolder;
        }

        public void setRunning(boolean run) {
            _run = run;
        }

        @Override
        public void run() {
            Canvas canvas = null;

            Paint paint = new Paint();
            paint.setColor(Color.RED);
            paint.setStrokeWidth(1);
            paint.setTextSize(20);

            while (_run){
                if(isDrawing == true){
                    try{
                        canvas = mSurfaceHolder.lockCanvas(null);
                        if(mBitmap == null){
                            mBitmap =  Bitmap.createBitmap (1, 1, Bitmap.Config.ARGB_8888);
                        }
                        final Canvas c = new Canvas (mBitmap);

                        c.drawColor(Color.WHITE);
                        c.drawCircle(80,80, 30, paint);
                        c.drawLine(80, 80, 80, 200, paint);
                        c.drawText(""+canvas.getWidth()+", "+canvas.getHeight(), 0, 200,paint);

                        canvas.drawBitmap (mBitmap, 0,  0,null);
                    } finally {
                        mSurfaceHolder.unlockCanvasAndPost(canvas);
                    }
                }
            }
        }
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width,  int height) {
        // Crea un bitmap con las dimensiones del view
        mBitmap =  Bitmap.createBitmap (width, height, Bitmap.Config.ARGB_8888);;
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // Comienza la ejecución del thread
        thread.setRunning(true);
        thread.start();
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // Finaliza la ejecución del thread
        boolean retry = true;
        thread.setRunning(false);
        while (retry) {
            try {
                thread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }
    }

}

 

 Publicado por en 8:00 pm

 Deja un comentario

(requerido)

(requerido)


ocho + 7 =