概要

JScrollBar上へのマウスカーソルの出入りをJLayerで取得してその幅を拡大・縮小します。

サンプルコード

JScrollPane scroll = new JScrollPane(makeList());
scroll.setHorizontalScrollBarPolicy(
    ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

JPanel controls = new JPanel();
Timer animator = new Timer(10, e -> controls.revalidate());
controls.setLayout(new BorderLayout(0, 0) {
  private int controlsWidth = MIN_WIDTH;

  @Override public Dimension preferredLayoutSize(Container target) {
    Dimension ps = super.preferredLayoutSize(target);
    int controlsPreferredWidth = ps.width;
    if (animator.isRunning()) {
      if (willExpand) {
        if (controls.getWidth() < controlsPreferredWidth) {
          controlsWidth += 1;
        }
      } else {
        if (controls.getWidth() > MIN_WIDTH) {
          controlsWidth -= 1;
        }
      }
      if (controlsWidth <= MIN_WIDTH) {
        controlsWidth = MIN_WIDTH;
        animator.stop();
      } else if (controlsWidth >= controlsPreferredWidth) {
        controlsWidth = controlsPreferredWidth;
        animator.stop();
      }
    }
    ps.width = controlsWidth;
    return ps;
  }
});
controls.add(scroll.getVerticalScrollBar());

JPanel p = new JPanel(new BorderLayout());
p.add(controls, BorderLayout.EAST);
p.add(scroll);

JPanel pp = new JPanel(new GridLayout(1, 2));
pp.add(new JLayer<>(p, new LayerUI<JPanel>() {
  private boolean isDragging;

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

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

  @Override protected void processMouseMotionEvent(
      MouseEvent e, JLayer<? extends JPanel> l) {
    int id = e.getID();
    Component c = e.getComponent();
    if (c instanceof JScrollBar && id == MouseEvent.MOUSE_DRAGGED) {
      isDragging = true;
    }
  }

  @Override protected void processMouseEvent(
      MouseEvent e, JLayer<? extends JPanel> l) {
    if (e.getComponent() instanceof JScrollBar) {
      switch (e.getID()) {
      case MouseEvent.MOUSE_ENTERED:
        if (!animator.isRunning() && !isDragging) {
          willExpand = true;
          animator.setInitialDelay(0);
          animator.start();
        }
        break;
      case MouseEvent.MOUSE_EXITED:
        if (!animator.isRunning() && !isDragging) {
          willExpand = false;
          animator.setInitialDelay(500);
          animator.start();
        }
        break;
      case MouseEvent.MOUSE_RELEASED:
        isDragging = false;
        if (!animator.isRunning()
            && !e.getComponent().getBounds().contains(e.getPoint())) {
          willExpand = false;
          animator.setInitialDelay(500);
          animator.start();
        }
        break;
      default:
        break;
      }
      l.getView().repaint();
    }
  }
}));
pp.add(new JLayer<>(
    makeTranslucentScrollBar(makeList()),
    new ScrollBarOnHoverLayerUI()));
View in GitHub: Java, Kotlin

解説

  • 左:
    • JPanelJScrollPaneと縦JScrollBarを分けて配置
    • JPanelに縦JScrollBarの幅をTimerを使用して拡大・縮小するレイアウトマネージャを設定
    • JPanelJLayerでラップして縦JScrollBarへのマウスカーソルの出入りなどを検知
      • MouseEvent.MOUSE_ENTEREDMouseEvent.MOUSE_EXITEDで出入りを検知してTimerを起動する
      • ただしマウスドラッグ中でもMouseEvent.MOUSE_ENTEREDMouseEvent.MOUSE_EXITEDイベントが発生する場合があるのでMouseEvent.MOUSE_DRAGGED中はTimerを起動しない
      • MouseEvent.MOUSE_RELEASEDイベントが発生したとき縦JScrollBar内にマウスカーソルがある場合はTimerを起動しない(幅を拡大した状態を維持する)
    • JScrollBarの幅を縮小する場合500ミリ秒ウェイトを入れてすぐにTimerを起動しないよう設定
  • 右:

参考リンク

コメント