• category: swing folder: TabOrScrollButtonHighlightAnimation title: JTabbedPaneのタブ追加位置をハイライト表示する title-en: Highlighting the location where a tab is added to a JTabbedPane tags: [JTabbedPane, JLayer, Animatioin] author: aterai pubdate: 2025-11-03T05:15:42+09:00 description: JTabbedPaneのタブ追加時にそのタブ領域、領域が非表示の場合はスクロールボタンをハイライトするアニメーションで追加位置を知らせるよう設定します。 summary-jp: JTabbedPaneのタブ追加時にそのタブ領域、領域が非表示の場合はスクロールボタンをハイライトするアニメーションで追加位置を知らせるよう設定します。 summary-en: When adding a tab to a JTabbedPane, the added position will be indicated by an animation that highlights the tab area, or if the area is hidden, the scroll button. image: https://drive.google.com/uc?id=1TgONMnx7Ui87bY2DEa8uVA6lClmVIidO

Summary

JTabbedPaneのタブ追加時にそのタブ領域、領域が非表示の場合はスクロールボタンをハイライトするアニメーションで追加位置を知らせるよう設定します。

Source Code Examples

class TabHighlightLayerUI extends LayerUI<JTabbedPane> {
  private static final int MAX = 32;
  private final Rectangle rect = new Rectangle();
  private final Timer animator = new Timer(10, null);
  private transient ActionListener listener;
  private int alpha;

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

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

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (c instanceof JLayer) {
      Graphics2D g2 = (Graphics2D) g.create();
      float a = alpha / 100f;
      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, a));
      g2.setPaint(Color.RED);
      g2.fill(rect);
      g2.dispose();
    }
  }

  @Override protected void processHierarchyEvent(
        HierarchyEvent e, JLayer<? extends JTabbedPane> l) {
    super.processHierarchyEvent(e, l);
    Container parent = e.getChangedParent();
    JTabbedPane tabs = l.getView();
    long flags = e.getChangeFlags();
    if (Objects.equals(parent, tabs) && flags == HierarchyEvent.PARENT_CHANGED) {
      EventQueue.invokeLater(() -> startAnime(l, e.getComponent()));
    }
  }

  private void startAnime(JLayer<? extends JTabbedPane> l, Component c) {
    JTabbedPane tabs = l.getView();
    int idx = tabs.indexOfComponent(c);
    Rectangle tabRect = tabs.getBoundsAt(idx);
    if (tabs.getBounds().contains(tabRect)) {
      rect.setBounds(tabRect);
    } else {
      JButton b = getScrollForwardButton(tabs);
      if (b != null) {
        rect.setBounds(b.getBounds());
      }
    }
    animator.start();
    animator.removeActionListener(listener);
    listener = ae -> {
      if (alpha < MAX) {
        alpha += 1;
      } else {
        alpha = 0;
        animator.stop();
      }
      l.paintImmediately(rect);
    };
    animator.addActionListener(listener);
    animator.start();
  }

  private static JButton getScrollForwardButton(JTabbedPane tabs) {
    JButton button1 = null;
    JButton button2 = null;
    for (Component c : tabs.getComponents()) {
      if (c instanceof JButton) {
        if (Objects.isNull(button1)) {
          button1 = (JButton) c;
        } else if (Objects.isNull(button2)) {
          button2 = (JButton) c;
        }
      }
    }
    int x1 = Objects.nonNull(button1) ? button1.getX() : -1;
    int x2 = Objects.nonNull(button2) ? button2.getX() : -1;
    return x1 > x2 ? button1 : button2;
  }
}
View in GitHub: Java, Kotlin

Description

  • JTabbedPane.SCROLL_TAB_LAYOUTを設定したJTabbedPaneをラップするJLayerを設定
  • LayerUI#installUI(...)をオーバーライドしてJLayer#setLayerEventMask(AWTEvent.HIERARCHY_EVENT_MASK)で階層イベントを取得するよう設定
  • LayerUI#processHierarchyEvent(...)をオーバーライドしてJLayerを設定したJTabbedPaneの子としてタブコンテンツコンポーネントが追加された後にハイライトアニメーションを開始するよう設定
  • JTabbedPane#indexOfComponent(...)で追加されたタブのインデックスを取得し、JTabbedPane#getBoundsAt(...)でその領域を取得
    • 取得したタブ領域がJTabbedPane領域内に含まれる場合はそのタブ領域をハイライトする
    • 取得したタブ領域がJTabbedPane領域内に含まれない場合はスクロールボタンの領域をハイライトする
      • このサンプルではタブは末尾にのみ追加するので、scrollTabsBackwardボタンより右側に配置されたscrollTabsForwardボタンをハイライトする
  • Timerを使用してアルファ値を変更しつつ、JLayer#paintImmediately(Rectangle)でハイライト領域を再描画することでアニメーションを行う

Reference

Comment