---
category: swing
folder: KineticScrolling
title: JScrollPaneでキネティックスクロール
tags: [JScrollPane, Animation, MouseListener, JViewport]
author: aterai
pubdate: 2010-08-16T13:34:26+09:00
description: JScrollPaneにキネティックスクロール(慣性スクロール)風の動作をするマウスリスナーを設定します。
image: https://lh4.googleusercontent.com/_9Z4BYR88imo/TQTO32D08pI/AAAAAAAAAdE/TpuoGrYo-Q0/s800/KineticScrolling.png
hreflang:
    href: https://java-swing-tips.blogspot.com/2010/08/kinetic-scrolling-jscrollpane.html
    lang: en
---
* 概要 [#summary]
`JScrollPane`にキネティックスクロール(慣性スクロール)風の動作をするマウスリスナーを設定します。

#download(https://lh4.googleusercontent.com/_9Z4BYR88imo/TQTO32D08pI/AAAAAAAAAdE/TpuoGrYo-Q0/s800/KineticScrolling.png)

* サンプルコード [#sourcecode]
#code(link){{
class KineticScrollingListener2 extends MouseAdapter implements HierarchyListener {
  private static final int SPEED = 4;
  private static final int DELAY = 10;
  private static final double D = 0.8;
  private final JComponent label;
  private final Point startPt = new Point();
  private final Point delta = new Point();
  private final Cursor dc;
  private final Cursor hc = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
  private final Timer inside = new Timer(DELAY, new ActionListener() {
    @Override public void actionPerformed(ActionEvent e) {
      JViewport vport = (JViewport) SwingUtilities.getUnwrappedParent(label);
      Point vp = vport.getViewPosition();
      // System.out.format("s: %s, %s%n", delta, vp);
      vp.translate(-delta.x, -delta.y);
      vport.setViewPosition(vp);
      if (Math.abs(delta.x) > 0 || Math.abs(delta.y) > 0) {
        delta.setLocation((int) (delta.x * D), (int) (delta.y * D));
        // Outside
        if (vp.x < 0 || vp.x + vport.getWidth() - label.getWidth() > 0) {
          delta.x = (int) (delta.x * D);
        }
        if (vp.y < 0 || vp.y + vport.getHeight() - label.getHeight() > 0) {
          delta.y = (int) (delta.y * D);
        }
      } else {
        inside.stop();
        if (!isInside(vport, label)) {
          outside.start();
        }
      }
    }
  });
  private final Timer outside = new Timer(DELAY, new ActionListener() {
    @Override public void actionPerformed(ActionEvent e) {
      JViewport vport = (JViewport) SwingUtilities.getUnwrappedParent(label);
      Point vp = vport.getViewPosition();
      // System.out.format("r: %s%n", vp);
      if (vp.x < 0) {
        vp.x = (int) (vp.x * D);
      }
      if (vp.y < 0) {
        vp.y = (int) (vp.y * D);
      }
      if (vp.x + vport.getWidth() - label.getWidth() > 0) {
        vp.x = (int) (vp.x - (vp.x + vport.getWidth() - label.getWidth()) * (1d - D));
      }
      if (vp.y + vport.getHeight() > label.getHeight()) {
        vp.y = (int) (vp.y - (vp.y + vport.getHeight() - label.getHeight()) * (1d - D));
      }
      vport.setViewPosition(vp);
      if (isInside(vport, label)) {
        outside.stop();
      }
    }
  });
  protected static final int SPEED = 4;
  protected static final int DELAY = 10;
  protected static final double D = .8;
  protected final JComponent label;
  protected final Point startPt = new Point();
  protected final Point delta = new Point();
  protected final Cursor dc;
  protected final Cursor hc = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
  protected final Timer inside = new Timer(DELAY, null);
  protected final Timer outside = new Timer(DELAY, null);

  private static boolean isInside(JViewport vport, JComponent comp) {
    Point vp = vport.getViewPosition();
    return vp.x >= 0 && vp.x + vport.getWidth()  - comp.getWidth()  <= 0
        && vp.y >= 0 && vp.y + vport.getHeight() - comp.getHeight() <= 0;
  protected static boolean isInside(JViewport viewport, JComponent comp) {
    Point vp = viewport.getViewPosition();
    return vp.x >= 0 && vp.x + viewport.getWidth() - comp.getWidth() <= 0
        && vp.y >= 0 && vp.y + viewport.getHeight() - comp.getHeight() <= 0;
  }

  public KineticScrollingListener2(JComponent comp) {
  protected KineticScrollingListener2(JComponent comp) {
    super();
    this.label = comp;
    this.dc = comp.getCursor();
    inside.addActionListener(e -> dragInside());
    outside.addActionListener(e -> dragOutside());
  }

  private void dragInside() {
    JViewport viewport = (JViewport) SwingUtilities.getUnwrappedParent(label);
    Point vp = viewport.getViewPosition();
    // System.out.format("s: %s, %s%n", delta, vp);
    vp.translate(-delta.x, -delta.y);
    viewport.setViewPosition(vp);
    if (Math.abs(delta.x) > 0 || Math.abs(delta.y) > 0) {
      delta.setLocation((int) (delta.x * D), (int) (delta.y * D));
      // Outside
      if (vp.x < 0 || vp.x + viewport.getWidth() - label.getWidth() > 0) {
        delta.x = (int) (delta.x * D);
      }
      if (vp.y < 0 || vp.y + viewport.getHeight() - label.getHeight() > 0) {
        delta.y = (int) (delta.y * D);
      }
    } else {
      inside.stop();
      if (!isInside(viewport, label)) {
        outside.start();
      }
    }
  }

  private void dragOutside() {
    JViewport viewport = (JViewport) SwingUtilities.getUnwrappedParent(label);
    Point vp = viewport.getViewPosition();
    // System.out.format("r: %s%n", vp);
    if (vp.x < 0) {
      vp.x = (int) (vp.x * D);
    }
    if (vp.y < 0) {
      vp.y = (int) (vp.y * D);
    }
    if (vp.x + viewport.getWidth() - label.getWidth() > 0) {
      vp.x = (int) (vp.x - (vp.x + viewport.getWidth() - label.getWidth()) * (1d - D));
    }
    if (vp.y + viewport.getHeight() > label.getHeight()) {
      vp.y = (int) (vp.y - (vp.y + viewport.getHeight() - label.getHeight()) * (1d - D));
    }
    viewport.setViewPosition(vp);
    if (isInside(viewport, label)) {
      outside.stop();
    }
  }

  @Override public void mousePressed(MouseEvent e) {
    e.getComponent().setCursor(hc);
    startPt.setLocation(e.getPoint());
    inside.stop();
    outside.stop();
  }

  @Override public void mouseDragged(MouseEvent e) {
    Point pt = e.getPoint();
    JViewport vport = (JViewport) SwingUtilities.getUnwrappedParent(label);
    Point vp = vport.getViewPosition();
    JViewport viewport = (JViewport) SwingUtilities.getUnwrappedParent(label);
    Point vp = viewport.getViewPosition();
    vp.translate(startPt.x - pt.x, startPt.y - pt.y);
    vport.setViewPosition(vp);
    viewport.setViewPosition(vp);
    delta.setLocation(SPEED * (pt.x - startPt.x), SPEED * (pt.y - startPt.y));
    startPt.setLocation(pt);
  }

  @Override public void mouseReleased(MouseEvent e) {
    e.getComponent().setCursor(dc);
    JViewport vport = (JViewport) SwingUtilities.getUnwrappedParent(label);
    if (isInside(vport, label)) {
    JViewport viewport = (JViewport) SwingUtilities.getUnwrappedParent(label);
    if (isInside(viewport, label)) {
      inside.start();
    } else {
      outside.start();
    }
  }

  @Override public void hierarchyChanged(HierarchyEvent e) {
    Component c = e.getComponent();
    if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0
        && !c.isDisplayable()) {
      inside.stop();
      outside.stop();
    }
  }
}
}}

* 解説 [#explanation]
- `scrollRectToVisible`
-- マウスがリリースされたらタイマーを起動し、`JComponent#scrollRectToVisible(Rectangle)`メソッドでスクロール
- `setViewPosition`
-- マウスがリリースされたらタイマーを起動し、`JViewport#setViewPosition(Point)`メソッドでスクロール
-- `View`として設定されている`JLabel`の外で移動が止まった(またはマウスがリリースされた)場合は、別のタイマーで`JLabel`の縁まで戻る

* 参考リンク [#reference]
- [[JScrollPaneのViewportをマウスで掴んでスクロール>Swing/HandScroll]]
- [[JScrollPaneのオートスクロール>Swing/AutoScroll]]

* コメント [#comment]
#comment
- 慣性(モーメンタム)スクロール、フリックスクロール(フリック+慣性スクロール?)、・・・でもやっぱり猫の掌スクロールを最初に思い出してしまう。 -- &user(aterai); &new{2010-08-16 (月) 13:41:47};
- `JDK 1.7.0`では、`JViewport#setViewPosition(Point)`を使って右下外部に移動できなくなっているので、[[JScrollPaneのViewportをマウスで掴んでスクロール>Swing/HandScroll]]と同じ対応をしてソースを更新。 -- &user(aterai); &new{2011-10-03 (月) 18:08:23};

#comment