Come si implementa lo zoom sensibile alla posizione all'interno di un JScrollPane?
-
02-07-2019 - |
Domanda
Sto cercando di implementare lo zoom sensibile alla posizione all'interno di un JScrollPane
. Il JScrollPane
contiene un componente con un paint
personalizzato che disegna se stesso all'interno dello spazio che viene allocato, quindi lo zoom è facile come usare un MouseWheelListener
che ridimensiona il componente interno come richiesto.
Ma voglio anche ingrandire (o ridurre) un punto per mantenerlo il più centrale possibile nella risultante vista ingrandita (o -out) risultante (questo è ciò che chiamo zoom sensibile alla posizione) ), simile a come funziona lo zoom in google maps. Sono sicuro che questo è stato fatto molte volte prima - qualcuno conosce il "giusto" modo di farlo con Java Swing ?. Sarebbe meglio giocare con le trasformazioni di Graphic2D
invece di usare JScrollPanes
?
Segue il codice di esempio:
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);
}
}
}
Soluzione
Testato, sembra funzionare ...
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();
}
Aggiorna
Ecco una spiegazione: il punto p
è la posizione del mouse rispetto al FPanel
. Dato che stai ridimensionando la dimensione del pannello, la posizione di p
(rispetto alla dimensione del pannello) verrà ridimensionata dello stesso fattore. Sottraendo la posizione corrente dalla posizione in scala, si ottiene quanto il punto "si sposta" quando il pannello viene ridimensionato. Quindi si tratta semplicemente di spostare la posizione del pannello nel riquadro di scorrimento della stessa quantità nella direzione opposta per riportare p
sotto il cursore del mouse.
Altri suggerimenti
Ecco un piccolo refactoring della soluzione di @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);
}
MouseWheelListener deve anche individuare il cursore, spostarlo al centro di JScrollPane e regolare xmin / ymin e xmax / ymax del contenuto da visualizzare.
Penso che dovrebbe funzionare così ...
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: Ho cambiato un paio di cose.