---
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 [#summary]
JTabbedPaneのタブ追加時にそのタブ領域、領域が非表示の場合はスクロールボタンをハイライトするアニメーションで追加位置を知らせるよう設定します。
`JTabbedPane`のタブ追加時にそのタブ領域、領域が非表示の場合はスクロールボタンをハイライトするアニメーションで追加位置を知らせるよう設定します。

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

* Source Code Examples [#sourcecode]
#code(link){{
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;
  }
}
}}

* Description [#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 [#reference]
- [[JTabbedPaneのタブをドラッグ&ドロップ>Swing/DnDTabbedPane]]

* Comment [#comment]
#comment
#comment