Summary

JLabelの文字列をLinearGradientPaintで作成した光沢が移動するアニメーション付きで描画します。

Source Code Examples

class ShimmerLayerUI extends LayerUI<JLabel> {
  private static final int FPS = 16;
  private static final int BAND_WIDTH = 100;
  private static final float SPEED = 4f;
  private static final int ALPHA = 180;
  private static final Color TRANSPARENT = new Color(0x0, true);
  private final Timer timer = new Timer(FPS, null);
  private final float[] fractions = {0f, .5f, 1f};
  private float animX;
  private transient BufferedImage buffer;

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    timer.addActionListener(ev -> {
      animX += SPEED;
      if (animX > c.getWidth() + BAND_WIDTH) {
        animX = -BAND_WIDTH;
      }
      c.repaint();
    });
    timer.start();
  }

  @Override public void uninstallUI(JComponent c) {
    timer.stop();
    super.uninstallUI(c);
  }

  @Override public void paint(Graphics g, JComponent c) {
    // super.paint(g, c);
    @SuppressWarnings("unchecked")
    JLayer<? extends JLabel> layer = (JLayer<? extends JLabel>) c;
    JLabel label = layer.getView();
    if (label == null || label.getText() == null || label.getText().isEmpty()) {
      super.paint(g, c);
    } else {
      int w = Math.max(1, c.getWidth());
      int h = Math.max(1, c.getHeight());
      ensureBuffer(w, h);
      paintLayerToBuffer(c, w, h);
      BufferedImage shimBuf = createShimmerBuffer(label, c, w, h);
      paintShimmerBuffer(shimBuf);
      g.drawImage(buffer, 0, 0, c);
    }
  }

  private void ensureBuffer(int w, int h) {
    if (buffer == null || buffer.getWidth() != w || buffer.getHeight() != h) {
      buffer = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    }
  }

  private void paintLayerToBuffer(JComponent c, int w, int h) {
    Graphics2D buf = buffer.createGraphics();
    buf.setComposite(AlphaComposite.Clear);
    buf.fillRect(0, 0, w, h);
    buf.setComposite(AlphaComposite.SrcOver);
    c.paint(buf);
    buf.dispose();
  }

  private BufferedImage createShimmerBuffer(JLabel label, JComponent c, int w, int h) {
    BufferedImage shimBuf = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    Graphics2D sg = shimBuf.createGraphics();

    AbstractShimmerLabel.applyRenderingHints(sg);
    Rectangle[] rects = ShimmerLayout.layoutLabel(label, sg);
    Rectangle textRect = rects[1];
    FontMetrics fm = sg.getFontMetrics(label.getFont());
    Point labelOrigin = SwingUtilities.convertPoint(label, 0, 0, c);
    float x = labelOrigin.x + textRect.x;
    float y = labelOrigin.y + textRect.y + fm.getAscent();

    // 1. Draw the text outline to the shimmer buffer (to serve as a mask).
    sg.setPaint(label.getForeground());
    FontRenderContext frc = sg.getFontRenderContext();
    new TextLayout(label.getText(), label.getFont(), frc).draw(sg, x, y);

    // 2. Compose the gradient using SrcAtop (masked by the text's alpha channel).
    sg.setComposite(AlphaComposite.SrcAtop);
    Color[] colors = {
        TRANSPARENT,
        ShimmerColors.shimmerBright(label.getForeground(), ALPHA),
        TRANSPARENT,
    };
    float endX = animX + BAND_WIDTH;
    sg.setPaint(new LinearGradientPaint(animX, 0f, endX, 0f, fractions, colors));
    sg.fillRect(0, 0, w, h);
    sg.dispose();

    // Clear the text area painted
    Graphics2D buf = buffer.createGraphics();
    buf.setPaint(label.getBackground());
    buf.fill(textRect);
    buf.dispose();

    return shimBuf;
  }

  private void paintShimmerBuffer(BufferedImage shimBuf) {
    Graphics2D buf = buffer.createGraphics();
    buf.setComposite(AlphaComposite.SrcOver);
    buf.drawImage(shimBuf, 0, 0, null);
    buf.dispose();
  }
}
View in GitHub: Java, Kotlin

Description

  • Standard Area Shimmer
    • JLabel#paintComponent(...)をオーバーライドしてJLabel全体にLinearGradientPaintで作成した半透明の光沢を上書き
    • JLabelのテキストだけではなく、アイコンや背景にも光沢が描画される
    • ダークモードには対応していない
  • Text-Only Shimmer (Clipping)
    • Standard Area Shimmer同様、JLabel#paintComponent(...)をオーバーライドしてLinearGradientPaintで作成した半透明の光沢を描画しているが、テキストをアウトラインでクリップしてアイコンや背景は光沢を描画しない
    • クリップ領域はアンチエイリアスで滑らかにできないので、文字の縁にジャギーが表示される
    • Fontのアウトラインを取得して文字列の内部を修飾する
  • JLayer Shimmer Label
    • Text-Only Shimmer (Composite)と同様の光沢付き文字列描画をJLayer上で描画

Reference

Comment