Summary
JTable
のセル選択領域全体の縁に破線フェーズの異なる破線を交互に切り替えることでその移動アニメーションを描画します。
Screenshot

Advertisement
Source Code Examples
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());
}
}
}
View in GitHub: Java, KotlinDescription
- JTableのセル選択を半透明化して上書きと同様に
JTable
側の選択背景色は完全に透明、選択文字色は文字色と同一に設定し、セル選択領域やその縁の描画はJLayer
を適用してLayerUI#paint(...)
メソッド内で実行するよう設定 - 破線フェーズのみ
2f
ずらしたBasicStroke
を2
つ用意- セル選択領域が存在しかつ
JTable
にフォーカスが存在する場合はTimer
を起動してこれらを入れ替えて描画することで破線の移動アニメーションを実行
- セル選択領域が存在しかつ