---
category: swing
folder: DashedBorderAnimationForSelectedCells
title: JTableのセル選択領域の縁に破線が移動するアニメーションを表示する
tags: [JTable, Animation, Timer, BasicStroke, JLayer]
author: aterai
pubdate: 2025-10-20T02:10:48+09:00
description: JTableのセル選択領域全体の縁に破線フェーズの異なる破線を交互に切り替えることでその移動アニメーションを描画します。
image: https://drive.google.com/uc?id=1BXeXb29QvuCa0j_OVcBybGOY641IF_ZN
---
* Summary [#summary]
`JTable`のセル選択領域全体の縁に破線フェーズの異なる破線を交互に切り替えることでその移動アニメーションを描画します。

#download(https://drive.google.com/uc?id=1BXeXb29QvuCa0j_OVcBybGOY641IF_ZN)

* Source Code Examples [#sourcecode]
#code(link){{
class TranslucentCellSelectionLayerUI extends LayerUI<JScrollPane> {
  private static final float WIDTH = 2f;
  private static final float MITER = 5f;
  private static final float[] DASH = {4f, 2f};
  private static final Stroke BORDER_STROKE1 = new BasicStroke(
      WIDTH, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, MITER, DASH, 0f);
  private static final Stroke BORDER_STROKE2 = new BasicStroke(
      WIDTH, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, MITER, DASH, 2f);
  private static Stroke borderStroke = BORDER_STROKE1;
  private JTable table;
  private boolean flg;
  private final Timer animator = new Timer(480, e -> {
    if (table != null && !table.isEditing()) {
      borderStroke = flg ? BORDER_STROKE1 : BORDER_STROKE2;
      repaintSelectedArea(table);
      flg = !flg;
    }
  });

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(AWTEvent.FOCUS_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processFocusEvent(
        FocusEvent e, JLayer<? extends JScrollPane> l) {
    table = getTable(l);
    if (e.getID() == FocusEvent.FOCUS_GAINED) {
      animator.start();
    } else {
      animator.stop();
    }
    super.processFocusEvent(e, l);
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    JTable table = getTable(c);
    int cc = table.getSelectedColumnCount();
    int rc = table.getSelectedRowCount();
    if (cc != 0 && rc != 0 && !table.isEditing()) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON);
      Area area = new Area();
      getSelectedArea(table).forEach(r -> {
        Rectangle rect = SwingUtilities.convertRectangle(table, r, c);
        area.add(new Area(rect));
      });
      Dimension ics = table.getIntercellSpacing();
      Color v = table.getSelectionBackground();
      Color sbc = new Color(v.getRed(), v.getGreen(), v.getBlue(), 0x32);
      for (Area a : GeomUtils.singularization(area)) {
        Rectangle r = a.getBounds();
        r.width -= ics.width - 1;
        r.height -= ics.height - 1;
        g2.setPaint(sbc);
        g2.fill(r);
        g2.setPaint(v);
        g2.setStroke(borderStroke);
        g2.draw(r);
      }
      g2.dispose();
    }
  }

  private static List<Rectangle> getSelectedArea(JTable tbl) {
    List<Rectangle> list = new ArrayList<>();
    for (int row : tbl.getSelectedRows()) {
      for (int col : tbl.getSelectedColumns()) {
        if (tbl.isCellSelected(row, col)) {
          list.add(tbl.getCellRect(row, col, true));
        }
      }
    }
    return list;
  }

  private static JTable getTable(Component c) {
    JTable table = null;
    if (c instanceof JLayer) {
      Component c1 = ((JLayer<?>) c).getView();
      if (c1 instanceof JScrollPane) {
        table = (JTable) ((JScrollPane) c1).getViewport().getView();
      }
    }
    return table;
  }

  private static void repaintSelectedArea(JTable tbl) {
    int cc = tbl.getSelectedColumnCount();
    int rc = tbl.getSelectedRowCount();
    if (cc != 0 && rc != 0) {
      Area area = new Area();
      getSelectedArea(tbl).forEach(r -> area.add(new Area(r)));
      tbl.repaint(area.getBounds());
    }
  }
}
}}

* Description [#description]
- [[JTableのセル選択を半透明化して上書き>Swing/TranslucentTableCellSelection]]と同様に`JTable`側の選択背景色は完全に透明、選択文字色は文字色と同一に設定し、セル選択領域やその縁の描画は`JLayer`を適用して`LayerUI#paint(...)`メソッド内で実行するよう設定
- [https://docs.oracle.com/javase/jp/8/docs/api/java/awt/BasicStroke.html#getDashPhase-- 破線フェーズ]のみ`2f`ずらした`BasicStroke`を`2`つ用意し、セル選択領域が存在し、かつ`JTable`にフォーカスが存在する場合は`Timer`を起動してこれらを入れ替えて描画することで破線の移動アニメーションを実行
- [https://docs.oracle.com/javase/jp/8/docs/api/java/awt/BasicStroke.html#getDashPhase-- 破線フェーズ]のみ`2f`ずらした`BasicStroke`を`2`つ用意
-- セル選択領域が存在しかつ`JTable`にフォーカスが存在する場合は`Timer`を起動してこれらを入れ替えて描画することで破線の移動アニメーションを実行

* Reference [#reference]
- [[JTableのセル選択を半透明化して上書き>Swing/TranslucentTableCellSelection]]

* Comment [#comment]
#comment
#comment