Saltar al contenido principal

Personalización y pintado Personalizado

Look and Feel (LaF)

El Look and Feel (LaF) controla el aspecto visual global de todos los componentes. Swing incluye varios LaF y permite usar librerías externas.

// Cambiar LaF antes de crear la interfaz
try {
// LaF del sistema operativo (recomendado para integración nativa)
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

// LaF de Nimbus (moderno, incluido en Java 6+)
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");

// LaF Metal (el predeterminado de Swing)
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");

// Aplicar a ventanas ya abiertas:
SwingUtilities.updateComponentTreeUI(ventana);
ventana.pack();

} catch (Exception e) {
e.printStackTrace(); // Usar el LaF por defecto si falla
}

LaF disponibles en Java estándar

// Listar todos los LaF instalados
UIManager.LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();
for (UIManager.LookAndFeelInfo info : lafs) {
System.out.println(info.getName() + " → " + info.getClassName());
}

Personalizar propiedades de UIManager

Se pueden cambiar colores, fuentes y tamaños globalmente:

UIManager.put("Button.background",         new Color(70, 130, 180));
UIManager.put("Button.foreground", Color.WHITE);
UIManager.put("Button.font", new Font("Arial", Font.BOLD, 13));
UIManager.put("Button.border", BorderFactory.createEmptyBorder(6, 14, 6, 14));
UIManager.put("Panel.background", new Color(245, 245, 245));
UIManager.put("Label.font", new Font("Arial", Font.PLAIN, 13));
UIManager.put("TextField.selectionColor", new Color(100, 160, 230));
UIManager.put("Table.gridColor", Color.LIGHT_GRAY);
UIManager.put("Table.alternateRowColor", new Color(240, 248, 255));

Colores y fuentes

// Color
Color rojo = Color.RED;
Color verde = new Color(0, 200, 0); // RGB
Color azul = new Color(0, 0, 255, 180); // RGBA (con transparencia)
Color hex = Color.decode("#3498DB"); // Hexadecimal
Color hsb = Color.getHSBColor(0.6f, 0.8f, 0.9f);

// Mezclar colores
Color mezcla = new Color(
(color1.getRed() + color2.getRed()) / 2,
(color1.getGreen() + color2.getGreen()) / 2,
(color1.getBlue() + color2.getBlue()) / 2
);

// Fuentes
Font arial = new Font("Arial", Font.PLAIN, 14);
Font negrita = new Font("Verdana", Font.BOLD, 16);
Font cursiva = new Font("Georgia", Font.ITALIC, 13);
Font ambos = new Font("Tahoma", Font.BOLD | Font.ITALIC, 14);
Font monospace = new Font(Font.MONOSPACED, Font.PLAIN, 12); // Fuente monoespaciada

// Derivar de una fuente existente
Font base = label.getFont();
Font mayor = base.deriveFont(base.getSize() + 4f);
Font bold = base.deriveFont(Font.BOLD);
Font scaled = base.deriveFont(Font.PLAIN, 20f);

// Cargar fuente desde archivo
try (InputStream is = getClass().getResourceAsStream("/fonts/mifuente.ttf")) {
Font custom = Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(14f);
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(custom);
label.setFont(custom);
} catch (Exception e) { e.printStackTrace(); }

Pintado personalizado con paintComponent

Para dibujar formas, imágenes o gráficos personalizados se sobreescribe paintComponent en un JPanel (o cualquier JComponent).

public class MiCanvas extends JPanel {

@Override
protected void paintComponent(Graphics g) {
// 1. SIEMPRE llamar al padre primero (limpia el fondo)
super.paintComponent(g);

// 2. Convertir a Graphics2D para más control
Graphics2D g2 = (Graphics2D) g.create(); // .create() para no modificar el original

// 3. Activar antialiasing (bordes suavizados)
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);

// 4. Dibujar
dibujar(g2);

// 5. SIEMPRE liberar el contexto al final
g2.dispose();
}

private void dibujar(Graphics2D g2) {
int w = getWidth();
int h = getHeight();

// Fondo degradado
GradientPaint gradiente = new GradientPaint(0, 0, Color.LIGHT_GRAY, 0, h, Color.WHITE);
g2.setPaint(gradiente);
g2.fillRect(0, 0, w, h);

// Rectángulo relleno
g2.setColor(new Color(70, 130, 180));
g2.fillRoundRect(20, 20, 100, 60, 15, 15);

// Borde del rectángulo
g2.setColor(Color.DARK_GRAY);
g2.setStroke(new BasicStroke(2f));
g2.drawRoundRect(20, 20, 100, 60, 15, 15);

// Elipse / Círculo
g2.setColor(new Color(220, 80, 80, 180)); // Con transparencia
g2.fillOval(150, 20, 80, 80);

// Línea con grosor
g2.setColor(Color.BLACK);
g2.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.drawLine(20, 120, 250, 120);

// Línea discontinua
float[] patron = {8f, 4f};
g2.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, patron, 0f));
g2.drawLine(20, 140, 250, 140);

// Texto
g2.setFont(new Font("Arial", Font.BOLD, 16));
g2.setColor(Color.DARK_GRAY);
g2.drawString("Hola, Canvas!", 20, 180);

// Texto centrado
String texto = "Centrado";
FontMetrics fm = g2.getFontMetrics();
int xTexto = (w - fm.stringWidth(texto)) / 2;
int yTexto = (h + fm.getAscent()) / 2;
g2.drawString(texto, xTexto, yTexto);

// Polígono
int[] xs = {50, 100, 150, 130, 70};
int[] ys = {200, 170, 200, 240, 240};
g2.setColor(new Color(100, 200, 100, 150));
g2.fillPolygon(xs, ys, 5);
g2.setColor(Color.GREEN.darker());
g2.setStroke(new BasicStroke(2f));
g2.drawPolygon(xs, ys, 5);
}

@Override
public Dimension getPreferredSize() {
return new Dimension(400, 300);
}
}

Graphics2D — formas avanzadas con java.awt.geom

import java.awt.geom.*;

// Path2D — trazados complejos
Path2D path = new Path2D.Double();
path.moveTo(100, 200);
path.lineTo(200, 100);
path.curveTo(250, 50, 300, 150, 350, 100); // Curva Bézier cúbica
path.quadTo(400, 200, 450, 150); // Curva Bézier cuadrática
path.closePath();

g2.setColor(new Color(200, 100, 50));
g2.fill(path);
g2.setColor(Color.BLACK);
g2.draw(path);

// Formas predefinidas
Shape circulo = new Ellipse2D.Double(x, y, diametro, diametro);
Shape rectangulo = new Rectangle2D.Double(x, y, ancho, alto);
Shape arco = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, Arc2D.PIE);
Shape linea = new Line2D.Double(x1, y1, x2, y2);
Shape curva = new CubicCurve2D.Double(x1,y1, cx1,cy1, cx2,cy2, x2,y2);

g2.fill(circulo);
g2.draw(arco);

// Transformaciones
g2.translate(100, 100); // Trasladar
g2.rotate(Math.PI / 4); // Rotar (radianes)
g2.scale(1.5, 1.5); // Escalar
g2.shear(0.3, 0); // Cizallar

// Guardar y restaurar transformación
AffineTransform original = g2.getTransform();
g2.rotate(Math.toRadians(45), cx, cy); // Rotar sobre un punto
g2.drawRect(x, y, w, h);
g2.setTransform(original); // Restaurar

Pintado de imágenes

public class PanelImagen extends JPanel {
private BufferedImage imagen;

public PanelImagen() {
try {
imagen = ImageIO.read(getClass().getResource("/images/foto.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (imagen == null) return;

Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

// Dibujar imagen en posición y tamaño exacto
g2.drawImage(imagen, x, y, ancho, alto, this);

// Imagen escalada al panel manteniendo proporción
double scaleX = (double) getWidth() / imagen.getWidth();
double scaleY = (double) getHeight() / imagen.getHeight();
double scale = Math.min(scaleX, scaleY); // Escala uniforme
int iw = (int) (imagen.getWidth() * scale);
int ih = (int) (imagen.getHeight() * scale);
int ix = (getWidth() - iw) / 2;
int iy = (getHeight() - ih) / 2;
g2.drawImage(imagen, ix, iy, iw, ih, this);

// Imagen con transparencia (Alpha Composite)
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g2.drawImage(imagen, 0, 0, this);

g2.dispose();
}
}

Componentes personalizados

Ejemplo completo: botón redondeado con hover effect.

public class BotonModerno extends JButton {

private Color colorBase;
private Color colorHover;
private Color colorActual;
private int radio = 10;

public BotonModerno(String texto, Color colorBase) {
super(texto);
this.colorBase = colorBase;
this.colorHover = colorBase.brighter();
this.colorActual = colorBase;

setContentAreaFilled(false); // Deshabilitar pintado nativo
setFocusPainted(false); // Sin borde de foco nativo
setBorderPainted(false);
setOpaque(false);
setForeground(Color.WHITE);
setFont(new Font("Arial", Font.BOLD, 14));
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
setPreferredSize(new Dimension(120, 36));

addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
colorActual = colorHover;
repaint();
}
@Override
public void mouseExited(MouseEvent e) {
colorActual = colorBase;
repaint();
}
@Override
public void mousePressed(MouseEvent e) {
colorActual = colorBase.darker();
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
colorActual = colorHover;
repaint();
}
});
}

@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

// Sombra
g2.setColor(new Color(0, 0, 0, 40));
g2.fillRoundRect(2, 4, getWidth() - 4, getHeight() - 4, radio, radio);

// Fondo del botón
g2.setColor(colorActual);
g2.fillRoundRect(0, 0, getWidth() - 2, getHeight() - 2, radio, radio);

// Texto
g2.setFont(getFont());
g2.setColor(getForeground());
FontMetrics fm = g2.getFontMetrics();
int x = (getWidth() - fm.stringWidth(getText())) / 2;
int y = (getHeight() + fm.getAscent() - fm.getDescent()) / 2;
g2.drawString(getText(), x, y);

g2.dispose();
}
}

Reglas de pintado en Swing

  • Siempre llama a super.paintComponent(g) primero (limpia el área, pinta el fondo).
  • Usa g.create() para obtener una copia del contexto y g2.dispose() al acabar.
  • Activa antialiasing con RenderingHints para gráficos suaves.
  • Nunca llames a paint() o paintComponent() directamente; usa repaint() para solicitar un redibujado.
  • repaint() encola el redibujado en el EDT; es seguro llamarlo desde cualquier hilo.
  • paintComponent debe ser rápido: no hagas operaciones costosas aquí; precalcula los datos.