Как вы реализуете позиционное масштабирование внутри JScrollPane? - PullRequest
10 голосов
/ 22 сентября 2008

Я пытаюсь реализовать позиционное масштабирование внутри JScrollPane. JScrollPane содержит компонент с настроенным paint, который будет рисовать себя внутри любого пространства, в котором он выделен - так что масштабирование так же просто, как использование MouseWheelListener, который изменяет размеры внутреннего компонента по мере необходимости.

Но я также хочу увеличить (или уменьшить) точку, чтобы сохранить эту точку как можно более центральной в результирующем увеличенном (или уменьшенном) виде (это я называю «чувствительным к положению» масштабированием). ), аналогично тому, как масштабирование работает в Google Maps. Я уверен, что это было сделано много раз раньше - кто-нибудь знает «правильный» способ сделать это под Java Swing ?. Было бы лучше поиграть с преобразованиями Graphic2D вместо использования JScrollPanes?

Пример кода следующий:

package test;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;

public class FPanel extends javax.swing.JPanel {

private Dimension preferredSize = new Dimension(400, 400);    
private Rectangle2D[] rects = new Rectangle2D[50];

public static void main(String[] args) {        
    JFrame jf = new JFrame("test");
    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jf.setSize(400, 400);
    jf.add(new JScrollPane(new FPanel()));
    jf.setVisible(true);
}    

public FPanel() {
    // generate rectangles with pseudo-random coords
    for (int i=0; i<rects.length; i++) {
        rects[i] = new Rectangle2D.Double(
                Math.random()*.8, Math.random()*.8, 
                Math.random()*.2, Math.random()*.2);
    }
    // mouse listener to detect scrollwheel events
    addMouseWheelListener(new MouseWheelListener() {
        public void mouseWheelMoved(MouseWheelEvent e) {
            updatePreferredSize(e.getWheelRotation(), e.getPoint());
        }
    });
}

private void updatePreferredSize(int n, Point p) {
    double d = (double) n * 1.08;
    d = (n > 0) ? 1 / d : -d;
    int w = (int) (getWidth() * d);
    int h = (int) (getHeight() * d);
    preferredSize.setSize(w, h);
    getParent().doLayout();
    // Question: how do I keep 'p' centered in the resulting view?
}

public Dimension getPreferredSize() {
    return preferredSize;
}

private Rectangle2D r = new Rectangle2D.Float();
public void paint(Graphics g) {
    super.paint(g);
    g.setColor(Color.red);
    int w = getWidth();
    int h = getHeight();
    for (Rectangle2D rect : rects) {
        r.setRect(rect.getX() * w, rect.getY() * h, 
                rect.getWidth() * w, rect.getHeight() * h);
        ((Graphics2D)g).draw(r);
    }       
  }
}

Ответы [ 4 ]

8 голосов
/ 09 апреля 2010

Проверено это, похоже, работает ...

private void updatePreferredSize(int n, Point p) {
    double d = (double) n * 1.08;
    d = (n > 0) ? 1 / d : -d;

    int w = (int) (getWidth() * d);
    int h = (int) (getHeight() * d);
    preferredSize.setSize(w, h);

    int offX = (int)(p.x * d) - p.x;
    int offY = (int)(p.y * d) - p.y;
    setLocation(getLocation().x-offX,getLocation().y-offY);

    getParent().doLayout();
}

Обновление

Вот объяснение: точка p - это расположение мыши относительно FPanel. Поскольку вы масштабируете размер панели, расположение p (относительно размера панели) будет масштабироваться с тем же коэффициентом. Вычитая текущее местоположение из масштабированного местоположения, вы получаете, насколько точка «сдвигается» при изменении размера панели. Тогда это просто вопрос смещения положения панели на панели прокрутки на ту же величину в противоположном направлении, чтобы поместить p обратно под курсор мыши.

2 голосов
/ 09 апреля 2010

Вот небольшой рефакторинг решения @Kevin K:

private void updatePreferredSize(int wheelRotation, Point stablePoint) {
    double scaleFactor = findScaleFactor(wheelRotation);
    scaleBy(scaleFactor);
    Point offset = findOffset(stablePoint, scaleFactor);
    offsetBy(offset);
    getParent().doLayout();
}

private double findScaleFactor(int wheelRotation) {
    double d = wheelRotation * 1.08;
    return (d > 0) ? 1 / d : -d;
}

private void scaleBy(double scaleFactor) {
    int w = (int) (getWidth() * scaleFactor);
    int h = (int) (getHeight() * scaleFactor);
    preferredSize.setSize(w, h);
}

private Point findOffset(Point stablePoint, double scaleFactor) {
    int x = (int) (stablePoint.x * scaleFactor) - stablePoint.x;
    int y = (int) (stablePoint.y * scaleFactor) - stablePoint.y;
    return new Point(x, y);
}

private void offsetBy(Point offset) {
    Point location = getLocation();
    setLocation(location.x - offset.x, location.y - offset.y);
}
1 голос
/ 08 апреля 2010

Я думаю, что SMT, как это должно работать ...


private void updatePreferredSize(int n, Point p) {
    double d = (double) n * 1.08;
    d = (n > 0) ? 1 / d : -d;
    int w = (int) (getWidth() * d);
    int h = (int) (getHeight() * d);
    preferredSize.setSize(w, h);

    // Question: how do I keep 'p' centered in the resulting view?

    int parentWdt = this.getParent( ).getWidth( ) ;
    int parentHgt = this.getParent( ).getHeight( ) ;

    int newLeft = p.getLocation( ).x - ( p.x - ( parentWdt / 2 ) ) ;
    int newTop = p.getLocation( ).y - ( p.y - ( parentHgt / 2 ) ) ;
    this.setLocation( newLeft, newTop ) ;

    getParent().doLayout();
}

EDIT: Поменял пару вещей.

1 голос
/ 22 сентября 2008

Ваш MouseWheelListener также должен найти курсор, переместить его в центр JScrollPane и отрегулировать xmin / ymin и xmax / ymax содержимого для просмотра.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...