Archivo de la etiqueta: gráficos

svgexample_svg

Dibujando SVG en Android

¿Qué es una fichero SVG (y por qué debería importarme)?

SVG son las siglas de Scalable Vector Graphics, es decir, gráficos vectoriales escalables (bien por el inglés).

Mientras las imágenes bitmaps (PNG, JPEG o GIF) se definen, simplificando mucho, en una matriz bidimensional en la que cada una de las posiciones guarda un color, las imágenes vectoriales están basadas en los pasos que se han de seguir para el dibujado de una imagen.

Por ejemplo: para definir un círculo rojo en un PNG, debemos rellenar de color rojo las posiciones necesarias de una matriz MxN, hasta crear la forma circular; para definirlo en un archivo SVG, debemos guardar la orden “dibuja un círculo con relleno rojo”.

Por supuesto, estas órdenes de dibujado SVG no están definidas con lenguaje natural, sino a través de ese lenguaje de marcado que siempre anda sacando de apuros a los informáticos: XML (Hey, ¿y si usamos XML?).

Por lo tanto, un archivo SVG es, a fin de cuentas, un XML. Texto. Tiene el siguiente aspecto:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
<svg version="1.1" baseProfile="basic" id="Layer_1"
	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100px" height="100px"
	 viewBox="0 0 100 100" xml:space="preserve">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="11.4727" y1="49.5879" x2="84.9736" y2="49.5879" gradientTransform="matrix(1.2603 0.0058 0.0058 1.2812 -11.7118 -14.1918)">
	<stop  offset="0" style="stop-color:#FF0000"/>
	<stop  offset="1" style="stop-color:#00FFFF"/>
</linearGradient>
<path fill="url(#SVGID_1_)" stroke="#000000" d="M92.189,83.321c-4.57-6.148-15.499-15.174-16.274-3.967
	c-9.521-13.716-14.235-17.953-16.272-3.964c2.037-13.989-4.004-1.625-12.318,11.351c-1.481-3.95-12.075,10.907-12.319,11.349
	C35.25,97.648,39,82.605,33.548,81.164c4.225-12.798,9.245-23.24-1.459-16.927c10.704-6.313-1.625-6.348-14.532-8.5
	c0.467-5.125-14.353-8.505-14.531-8.5c0.178-0.005,16.997-2.998,15.372-6.495c14.312-1.863,28.292-1.723,15.371-6.493
	c12.92,4.77,10.648-2.184,3.336-16.604c9.6,3.036,6.071-11.455,3.336-16.603c2.734,5.148,12.803,21.806,10.96,12.911
	c8.365,16.579,12.011,24.736,10.96,12.912c1.051,11.824,4.672,9.713,16.595-1.761c-4.377,10.673,6.851,5.054,16.593-1.763
	c-9.742,6.817-19.512,15.521-8.599,14.475c-14.709,7.438-18.141,9.899-8.6,14.474c-9.541-4.575-4.651,1.088,6.919,15.515
	C77.463,61.823,87.619,77.173,92.189,83.321z"/>
</svg>

Ventajas de SVG

La principal ventaja de los SVG respecto a otros formatos está, como su propio nombre indica, en que es escalable.

Como muestra la imagen, la calidad de la imagen mostrada en SVG no varía con el tamaño, mientras que la del PNG disminuye al aumentar el tamaño de la imagen original. ¿Por qué?

En el PNG, al agrandar las dimensiones de la imagen, expandimos la matriz contenedora de colores. Si antes teníamos una imagen de 50×50, teníamos 2.500 píxeles, cada uno de un color. Si ahora escalamos hasta tener una imagen de 100×100, tenemos que rellenar 10.000 píxeles. Y partimos de 2.500 píxeles. Los otros 7.500 hay que inventárselos, a través de un algoritmo, y de tal manera que el resultado siga representando la misma imagen. Por muy bueno que sea el algoritmo, el resultado dejará que desear.

En el SVG, sin embargo, tenemos guardados los trazos que debemos realizar para reconstruir la imagen, el gradiente que debemos utilizar para el rellenado, y el color del borde. Ahora, sólo debemos ajustar la escala de cada uno al tamaño que nos indiquen.

SVG es mucho más que un formato de imágenes. También puede definir animaciones y eventos, y es el “perfecto” sustituto para las composiciones animadas creadas con Adobe Flash. Sin embargo, lleva dando vueltas más de diez años y aún no tiene el calado que, en teoría, debería tener.

Desventajas de SVG

Por permitir la ejecución de scripts, SVG plantea problemas de seguridad, que, en principio, con buenas prácticas de programación, podrían ser evitados.

Aunque su principal desventaja, diría yo, es su falta de popularidad. Si en diez años y con sus posibilidades no ha sido capaz de tomar la web, ahora con la aparición de HTML5 y el Canvas, parece que el SVG embebido como alternativa a Flash nunca llegará.

Esto podría explicar por qué la mayoría de librerías que existen para usar SVG desde Java o Android sólo implementan la parte de dibujado, pero se olvidan de otros módulos importantes, como el de animación.

También podemos considerar una desventaja, aunque menor en los teratiempos que corren, su eficiencia: un bitmap siempre será más rápido en el dibujado que un gráfico vectorial, que requerirá una serie de cálculos adicionales.

Dibujando SVG en Android

Para el dibujado de SVG en Android, he utilizado la librería svg-android, utilizada en Androidify, una aplicación que permite crear pequeños androides con el aspecto que queramos.

La librería, tras probarla, parece estar desarrollada específicamente para cubrir las necesidades de Androidify, y apenas implementa los módulos básicos de SVG. Aún así, es suficiente para hacer un par de experimentos. Su uso es muy sencillo:

    SVG svg = SVGParser.getSVGFromResource(getResources(), R.raw.filename);
    PictureDrawable picture = svg.createPictureDrawable()

He creado una aplicación sencilla (puedes descargar el código y el apk al final de la entrada) con una intención doble: probar el dibujado de SVG en Android, y ver cómo escala Android los bitmaps de manera nativa, puesto que su primo Java (a través del Graphics2D de Java) lo hace fatal.

Bitmap y Picture

Android tiene dos clases que sirven para ejemplificar perfectamente las diferencias entre imágenes bitmaps e imágenes vectoriales. Mientras Bitmap representa una matriz bidimensional con información de colores (PNG o JPEG), Picture graba las llamadas de dibujado (dibuja línea, dibuja rectángulo, rellena de verde…) y luego es capaz de reproducirlas.

La clase Picture es perfecta para la representación de gráficos vectoriales, puesto que implementa la filosofía vectorial de guardar los pasos realizados en la construcción de una imagen. De hecho, svg-android utiliza esta clase para representar SVGs.

La aplicación

La aplicación no tiene gran misterio. Un RadioGroup para elegir la imagen a visualizar (un SVG o un PNG de 50×50), y una SeekBar para cambiar la escala de la imagen mostrada. Para resolver cualquier duda de la aplicación, puedes consultar el código adjuntado al final de la entrada.

Como puede comprobarse en las capturas (además de descubrir que svg-android no renderiza el borde negro de la figura), la distorsión del PNG es considerable en la escala máxima.

Conclusiones y archivos fuentes

A pesar de todo, los gráficos vectoriales pueden ser más efectivos en el desarrollo de aplicaciones que hacen uso intensivo de figuras 2D (como juegos), sobre todo en dispositivos Android, en dónde existen cada vez mayor número de resoluciones de pantalla.

El formato SVG es una buena alternativa para la definición de este tipo de gráficos. Existen algunas librerías interesantes, como Batik, para su procesamiento. Aunque no todas ofrezcan el procesamiento de módulos complejos, como el de animación, el renderizado escalable sigue siendo una funcionalidad muy interesante que no podremos conseguir con otros formatos.

Archivos de la aplicación

Otras entradas que podrían interesarte

javaoutlined

Dibujar el contorno de un texto en Java con Graphics2D

La clase Graphics2D es una gran desconocida para la mayoría de los programadores Java. La mayoría nos limitamos a crear ventanas y ventanas con componentes prefabricados, y a pelearnos con los Layouts, pero nos exploramos las enormes posibilidades que la API 2D nos ofrece.

Hoy vengo con un truco muy rápido y sencillo para dibujar el contorno de un texto. El resultado:

A saber:

1) En Java, Shape es una forma. Algo que se puede dibujar.

2) La clase Graphics2D tiene dos métodos: fill(Shape s) y draw(Shape s). El primero, para rellenar formas, y el segundo, para delinear formas.

3) Sólo necesitamos la forma que define el texto para poder dibujarlo. Esto suena complicado, pero en verdad Java nos lo da resuelto.

El código para dibujar el ejemplo de la figura:


			protected void paintComponent( Graphics g ){
				Graphics2D g2 = (Graphics2D) g;
				Font f = new Font( "Arial", Font.BOLD, 200 );
				g2.setFont(f);

				Shape s = f.createGlyphVector(g2.getFontRenderContext(), "JAVA").getOutline(280, 300);

				g2.setPaint(new GradientPaint( 0.0f, 200.0f, Color.RED, 0.0f, 400.0f, Color.WHITE ));
				g2.fill(s);

				g2.setColor(Color.BLACK);
				g2.setStroke( new BasicStroke( 3.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, new float[]{ 5.0f, 5.0f, 15.0f, 5.0f}, 0.0f ));
				g2.draw(s);

			}

Por suerte, contamos con el método createGlyphVector de la clase Font, que nos lo da todo hecho.

A continuación, un código ejecutable con su main y con su todo. Para aquel que quiera probarlo, y experimentar un poco.


import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;

import javax.swing.JComponent;
import javax.swing.JFrame;

public class Main {

	public static void main( String[] args ){
		JFrame frame = new JFrame( "Font outline");
		frame.setSize(600, 600);
		frame.getContentPane().setLayout(new BorderLayout( ));
		frame.getContentPane().add( new JComponent( ){

			protected void paintComponent( Graphics g ){
				Graphics2D g2 = (Graphics2D) g;
				Font f = new Font( "Arial", Font.BOLD, 200 );
				g2.setFont(f);

				Shape s = f.createGlyphVector(g2.getFontRenderContext(), "JAVA").getOutline(280, 300);

				g2.setPaint(new GradientPaint( 0.0f, 200.0f, Color.RED, 0.0f, 400.0f, Color.WHITE ));
				g2.fill(s);

				g2.setColor(Color.BLACK);
				g2.setStroke( new BasicStroke( 3.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, new float[]{ 5.0f, 5.0f, 15.0f, 5.0f}, 0.0f ));
				g2.draw(s);

			}

		}, BorderLayout.CENTER);
		frame.setVisible(true);
	}

}

En verdad, esto es sólo la punta del iceberg. Se puede jugar mucho más con Shape. Si tengo algo de tiempo, intentaré crear algún efecto más impactante.

Otras entradas que podrían interesarte

“CanvasView”: dibujando libremente en Android

Para algunos de los tutoriales con Android, vamos a necesitar una clase más o menos genérica que nos permita dibujar los resultados. Más que nada para asegurarnos de que lo hemos hecho bien. Para ello, he creado un “CanvasView”, una View de Android basada en su SurfaceView. Representa un área en la que podemos dibujar lo que queramos.

Objetivo

Para entender lo que buscamos, un ejemplo de uso (y que vamos a explicar en este tutorial) es el siguiente:

El CanvasView ocupa toda la pantalla. Cada vez que se pulsa sobre ella, se añade un cuadrado azul. Sencillo. Vamos a explicar este ejemplo y de paso entender cómo funciona esta nueva vista.

Renderables y Updateables

Para esta vista personalizada, vamos a utilizar dos interfaces:

package asl.mi.android.canvas;

import android.graphics.Canvas;

public interface Renderable {

	void render( Canvas c );
}

Tiene un único método render. Esta interfaz deberá ser implementada por todas aquellas clases que vayan a ser dibujadas dentro del CanvasView.

package asl.mi.android.canvas;

public interface Updateable {
	
	void update( long elapsed );

}

Implementada por todos aquellos elementos que tengan que actualizarse con el tiempo. Es el caso de las animaciones. El método update se encargará de esa actualización.

CanvasView

El código de nuestra vista personalizada:

package asl.mi.android.canvas;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class CanvasView extends SurfaceView implements SurfaceHolder.Callback {

	public class CanvasThread extends Thread {

		private SurfaceHolder mSurfaceHolder;

		private boolean mRun;

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

		public void run() {
			long now = System.currentTimeMillis();
			long lastTime = now;
			while (mRun) {
				Canvas c = null;
				now = System.currentTimeMillis();
				update(now - lastTime);
				lastTime = now;
				try {
					c = mSurfaceHolder.lockCanvas(null);
					doDraw(c);
				} finally {
					if (c != null) {
						mSurfaceHolder.unlockCanvasAndPost(c);
					}
				}
			}
		}

		public void setRunning(boolean running) {
			mRun = running;
		}

	}

	private CanvasThread thread;

	private List<Renderable> renderables;
	private List<Updateable> updateables;

	public CanvasView(Context context) {
		super(context);

		SurfaceHolder holder = getHolder();
		holder.addCallback(this);

		thread = new CanvasThread(holder);

		renderables = new ArrayList<Renderable>();
		updateables = new ArrayList<Updateable>();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {

	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		thread.setRunning(true);
		thread.start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		boolean retry = true;
		thread.setRunning(false);
		while (retry) {
			try {
				thread.join();
				retry = false;

			} catch (InterruptedException e) {
			}
		}

	}

	public void addRenderable(Renderable r) {
		synchronized (renderables) {
			renderables.add(r);
		}
	}

	public void addUpdateable(Updateable u) {
		synchronized (updateables) {
			updateables.add(u);
		}
	}

	private void doDraw(Canvas c) {
		c.drawARGB(255, 0, 0, 0);
		synchronized (renderables) {
			for (Renderable r : renderables) {
				r.render(c);
			}
		}
	}

	private void update(long elapsed) {
		synchronized (updateables) {
			for (Updateable u : updateables) {
				u.update(elapsed);
			}
		}
	}

}

No quiero entrar mucho en la explicación exacta de todo el código, porque el método está inspirado en la vista utilizada en LunarLander, ejemplo de código que viene en el SDK de Android.

A destacar:

  1. Tenemos dos listas: una de Renderables y otra de Updateables. Y métodos de adición para ambas, que utilizaremos a conveniencia desde las clases contenedoras.
  2. CanvasThread es un hilo que se ejecuta en concurrencia con el resto de la aplicación. Es este thread quién llama al dibujado con doDraw y a la actualización de los elementos a través de update. La necesidad de un thread aparte obedece a nuestra intención de incluir elementos animados dentro de esta vista. De no ser así, hubiera sido suficiente con sobrescribir el método onDraw( Canvas c ) de la clase padre, y meter dentro de ese método el recorrido y dibujado de la lista de Renderable.

SquaresActivity: Nuestra actividad principal

Antes de nada, necesitamos la clase que define el cuadrado azul que queremos dibujar en pantalla cada vez que la pulsemos. Su código:

package asl.mi.android.drawables;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import asl.mi.android.canvas.Renderable;

public class SquareDrawable extends Drawable implements Renderable {

	private int color;

	public SquareDrawable(String c) {
		color = Color.parseColor(c);
	}

	@Override
	public void draw(Canvas canvas) {
		Paint p = new Paint();
		p.setColor(color);
		canvas.drawRect(this.getBounds(), p);

	}

	@Override
	public int getOpacity() {
		return 0;
	}

	@Override
	public void setAlpha(int alpha) {

	}

	@Override
	public void setColorFilter(ColorFilter cf) {

	}

	@Override
	public void render(Canvas c) {
		draw( c );
		
	}

}

Heredamos de Drawable, una clase Android, que nos da resuelto el posicionamiento del objeto (su x, su y, su ancho y su alto) a través del método setBounds, que utilizaremos cuándo creemos el cuadrado, y getBounds, que es el que utilizamos a la hora de dibujar para determinar el área del dibujo.

Como dijimos, implementa Renderable porque queremos que sea dibujado en el CanvasView. Así, el método render simplemente llama al método draw heredado de Drawable.

Y ahora, el código de la actividad que lanzaremos en nuestro Android:

package asl.mi.android.canvas;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import asl.mi.android.drawables.SquareDrawable;

public class SquaresActivity extends Activity {

	private CanvasView canvasView;


	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		canvasView = new CanvasView(this.getApplicationContext());
		canvasView.setOnTouchListener(new MyTouchListener());
		this.setContentView(canvasView);

	}

	public class MyTouchListener implements OnTouchListener {

		@Override
		public boolean onTouch(View v, MotionEvent event) {
			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				SquareDrawable square = new SquareDrawable("#0000FF");
				square.setBounds((int) event.getX(), (int) event.getY(),
						(int) event.getX() + 20, (int) event.getY() + 20);
				canvasView.addRenderable(square);
				return true;

			}
			return false;
		}
	}
}

Hemos añadido un listener a la vista: cada vez que alguien toque, añadimos un SquareDrawable al CanvasView. Y ya estaría.

Códigos:

Otras entradas que podrían interesarte

Personalizando Swing: JButton a nuestro gusto

Cuándo uno ha trasteado lo suficiente con el Swing de Java, pronto se cansa de la aburrida apariencia que ofrece, además de algunas limitaciones de funcionalidad. En esta serie Personalizando Swing vamos a intentar suplir estas carencias de una manera sencilla y práctica.

La solución oficial a este problema pasa por pelearnos con el UIManager o con los Look and Feel. Esta solución probablemente sea la más correcta, pero cuándo nuestra intención es crear algunos componentes personalizados para nuestras aplicaciones, tirar por ese camino es, básicamente, matar moscas a cañonazos.

Objetivo

En esta entrada vamos a programar unos botones de  manera muy sencilla. Éste es el resultado:

JButton vs AbstractButton vs JComponent

Supongo que cuándo uno se plantea este  problema por primera vez, como yo hice en su día, lo más probable es que intente aplicar los razonamientos de Programación Orientada a Objetos y llegue a la conclusión de que lo lógico en este caso sea, puesto que queremos una variación de JButton, extender la clase JButton, o, si me apuras, su clase padre, AbstractButton.

Esta es una aproximación correcta y se puede hacer sin mayor problema. Sin embargo, yo voy a optar por otra: extender de la clase padre de AbstractButton (y de la mayoría de componentes de Swing): JComponent. ¿Por qué?

Primero, porque esta solución es general (algo que nos excita sobremanera a los informáticos) y nos permitirá crear, no sólo botones personalizados, sino cualquier componente que podamos imaginar.

Y segundo, un JButton tiene un montón de funcionalidad que seguramente no vayamos a utilizar en nuestra aplicación, y parece innecesario arrastrar todo ese código inútil. Lo que haremos será ir añadiendo al JComponent las funcionalidades que necesitemos.

MiniCloseButton

Empecemos con el código. Si dominas el tema y prefieres saltarte las explicaciones, al final de la entrada puedes encontrar todo el código que voy a explicar a continuación.

El primer paso es extender la clase JComponent:

package asl.mi.swing.custom;

public class MiniCloseButton extends JComponent implements MouseListener {
	private static final long serialVersionUID = 2890038925323894079L;

	@Override
	public void mouseClicked(MouseEvent e) {

	}

	@Override
	public void mouseEntered(MouseEvent e) {

	}

	@Override
	public void mouseExited(MouseEvent e) {

	}

	@Override
	public void mousePressed(MouseEvent e) {

	}

	@Override
	public void mouseReleased(MouseEvent e) {

	}
}
  • Como hemos comentado, extendemos de JComponent. Esta clase nos ofrece la funcionalidad esencial para hacer nuestro botón.
  • MouseListener: esta interfaz implica la implementación de todos los métodos mouseXxxxxxx( MouseEvent e ). A través de estos métodos, podremos definir un comportamiento por defecto en el botón (lo veremos con más detalle después)
  • serialVersionUID: Este atributo probablemente sea uno de los misterios más extendidos que rodean a Java. Sólo sabes que, si no lo pones, provoca un Warning. No estoy seguro de su funcionamiento, sólo sé que está relacionado con un control de versiones de la serialización (el método para guardar en binario las clases de Java). Nunca he hecho un uso real de él. Aunque no dudo de su utilidad, sólo lo pongo para evitar el Warning.
Constructor

Antes de nada, vamos a definir un par de atributos en la clase:

	private boolean mouseOver;

	private static final int SIZE = 15;

	private static final Dimension PREFERRED_DIMENSION = new Dimension(SIZE,
			SIZE);
  • mouseOver: un atributo que nos indicará si el ratón está sobre el botón, y en base a ello, poder jugar con su apariencia.
  • SIZE y PREFERRED_DIMENSION: dos constantes que determinarán el tamaño por defecto de nuestro botón.

Ahora, el código del constructor sería:

	public MiniCloseButton() {
		enableInputMethods(true);
		addMouseListener(this);
		mouseOver = false;
		setPreferredSize(PREFERRED_DIMENSION);
		setMinimumSize(PREFERRED_DIMENSION);
		setMaximumSize(PREFERRED_DIMENSION);
		setSize(PREFERRED_DIMENSION);
	}
  • enableInputMethods(true): habilita el componente para recibir eventos de ratón, de teclado… Esta llamada en verdad es innecesaria, puesto que esta propiedad está activada por defecto.
  • addMouseListener(this): le decimos que sea él mismo quien esté atento a los eventos de ratón. Es decir, cuándo haya un nuevo evento de ratón, se ejecutarán los métodos mouseXxxxx( MouseEvent e ) que implementamos antes.
  • El resto de métodos con PREFERRED_DIMENSION son el tamaño ideal, mínimo, máximo y actual (en ese orden) para el componente. Realmente estas indicaciones no siempre serán obedecidas, dependerá del Layout que contenga al componente.
Eventos de ratón

Esta parte en verdad es muy sencilla. El código:


	@Override
	public void mouseEntered(MouseEvent e) {
		mouseOver = true;
		repaint();

	}

	@Override
	public void mouseExited(MouseEvent e) {
		mouseOver = false;
		repaint();
	}

Cuándo el ratón entra en el componente, se dispara el mouseEntered. Ahí, ponemos el valor correspondiente al atributo mouseOver, y repintamos el componente con repaint( ) porque seguramente este cambio de atributo vaya a afectar a su apariencia. Es análogo para el mouseExited.

Pintado del componente

Y por último, el pintado del componente. JComponent tiene dos métodos de dibujado: paint( Graphics g ) y paintComponent( Graphics g ). Nosotros vamos reescrbir el método paintComponent( Graphics g ), puesto que es lo que recomienda la documentación oficial. El paint( Graphics g ) suele reescribirse cuándo tenemos un cambio de apariencia intensivo en el componente.

El objeto Graphics que nos llega como parámetro es, básicamente, un lienzo 2D para dibujar lo que queramos dentro de los límites del componente. Estos límites van desde ( 0, 0 ) a ( getWidth(), getHeight() ).

Podemos dibujar líneas, rectángulos, círculos, imágenes… Y podemos hacerlo con diferentes tipos de líneas, colores, degradados, etc.

No pretendo cubrir en este tutorial la amplia funcionalidad de esta clase, pero explico a continuación la que yo he utilizado en mi código del botón.

Para más información, puedes visitar la documentación oficial de Java: Graphics y Graphics2D.

Antes de nada, definimos un par de constantes más:

	private static final int STROKE_WIDTH = SIZE / 5;

	private static final int MARGIN = SIZE / STROKE_WIDTH;

Ahora el código de pintado:

	public void paintComponent(Graphics g) {
		Graphics2D g2 = (Graphics2D) g.create();
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setStroke(new BasicStroke(STROKE_WIDTH));
		g2.setPaint(new GradientPaint( 0, 0, Color.GRAY, SIZE, 0, Color.DARK_GRAY));
		g2.drawLine(MARGIN, MARGIN, getWidth() - MARGIN, getHeight() - MARGIN);
		g2.drawLine(getWidth() - MARGIN, MARGIN, MARGIN, getHeight() - MARGIN);

		if (mouseOver) {
			g2.setStroke(new BasicStroke(1));
			g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
					0.3f));
			g2.setColor(Color.GRAY);
			g2.fillRoundRect(1, 1, getWidth() - 1,
					getHeight() - 1, SIZE, SIZE);
		}
	}
  • Graphics2D g2 = (Graphics2D) g.create():
    Con g.create() obtenemos una copia del objeto Graphics. Esto lo hacemos para que nuestros cambios en el color o en los tipos de línea, no afecten a subsecuentes dibujados de componentes. Además, hacemos un casting a la clase Graphics2D, con mucha más funcionalidad, puesto que, aún siendo un Graphics la clase llegada desde el método, en Swing ese objeto es siempre un Graphics2D.
  • g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    Este es un truco para que lo que dibujes tenga mejor aspecto. En esencia, estamos activando el Antialiasing en el dibujado. Más info sobre el Antialiasing.
  • g2.setStroke(new BasicStroke(STROKE_WIDTH));
    Con este método, especificamos el tipo de trazado que queremos utilizar cuándo dibujemos líneas. En este caso, un BasicStroke que es un trazado uniforme, al que le pasamos el ancho en píxeles.
  • g2.setPaint(new GradientPaint( 0, 0, Color.GRAY, SIZE, 0, Color.DARK_GRAY)):
    Especificamos el tipo de relleno que queremos utilizar cuándo rellenemos trazados. En este caso, un gradiente. Más información sobre los gradientes en Java.
  • g2.drawLine( x1, y1, x2, y2):
    Dibujado de una línea desde (x1, y1) hasta (x2, y2 ).
  • g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)):
    Con esto hacemos que todo lo que se dibuje a continuación sea semitransparente. 0.3f es el factor de transparencia, su rango va de 1.0f, totalmente opaco, a 0.0f, totalmente transparente (invisible).
  • g2.setColor(Color.GRAY):
    Una variación del setPaint (en verdad el setPaint es una variación del setColor). Especificamos, de nuevo, el relleno de nuestros trazados. En este caso, un color uniforme.
  • g2.fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) :
    En Graphics2D tenemos dos tipos de métodos, los que empiezan por draw que dibujan contornos, y los que empiezan por fill, que rellenan contornos. En este caso, rellenamos un cuadrado un rectángulo de bordes redondeados. Los dos últimos parámetros especifican la esquina redondeada.

Y ya tenemos el código de nuestro botón. A continuación os dejo el código final de este MiniCloseButton y otro ejemplo, un poco más elaborado, que intenta imitar el botón de cierre las ventanas de Windows XP.

Códigos:

Sugerencias, dudas, peticiones de otros botones y sobre todo, errores y quejas, aquí abajo.

Otras entradas que podrían interesarte