Wie setzen Sie positionsempfindlichen innerhalb eines JScrollPane Zoomen?
-
02-07-2019 - |
Frage
Ich versuche, positionsempfindliche Zoom innerhalb eines JScrollPane
zu implementieren. Die JScrollPane
enthält eine Komponente mit einem angepassten paint
, die in mich ziehen werden, was auch immer Raum zugeordnet wird -. So Zoomen so einfach ist es, ein MouseWheelListener
als verwenden, die die innere Komponente paßt die Größe je nach Bedarf
Aber ich mag auch das Zoomen in (oder aus) einem Punkt, Punkt zu halten, so zentral wie möglich innerhalb des resultierenden gezoomt-in (oder -out) sehen (das ist, was ich als ‚positionsempfindlichen‘ Zoomen ), ähnlich wie Arbeiten in google Maps zoomen. Ich bin sicher, dass dies schon viele Male zuvor getan wurde - weiß jemand, den „richtigen“ Weg, um es unter Java Swing zu tun ?. Wäre es besser, mit Graphic2D
Verwandlungen zu spielen, anstatt JScrollPanes
verwenden?
Beispielcode folgt:
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);
}
}
}
Lösung
Diese Getestet, scheint zu funktionieren ...
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();
}
Aktualisieren
Hier ist eine Erklärung: der Punkt p
zum FPanel
die Position der Maus relativ ist. Da Sie die Größe des Panels werden die Skalierung wird die Lage von p
(bezogen auf die Größe der Platte) um den gleichen Faktor skaliert werden. Durch den aktuellen Standort aus der skalierten Position subtrahiert, erhalten Sie wie viel der Punkt ‚verschiebt‘, wenn das Panel der Größe verändert wird. Dann ist es nur eine Frage der Verschiebung der Plattenstelle im Bildlauffenster um den gleichen Betrag in der entgegengesetzten Richtung p
zurück unter dem Mauszeiger zu setzen.
Andere Tipps
Hier ist eine kleine Refactoring von @ Kevin K-Lösung:
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);
}
Ihre MouseWheelListener haben auch die Cursor zu finden, um es in der Mitte des JScrollPane bewegen und die xmin / ymin und xmax / ymax des Inhalts zu betracht anzupassen.
Ich denke, smt wie dies sollte arbeiten ...
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: Changed ein paar Dinge.