Question

J'essaie d'implémenter un zoom sensible à la position dans un JScrollPane . Le JScrollPane contient un composant avec une peinture personnalisée qui se dessinera à l'intérieur de l'espace alloué, le zoom est donc aussi simple que d'utiliser un MouseWheelListener . qui redimensionne le composant interne selon les besoins.

Mais je souhaite également effectuer un zoom avant (ou en dehors) d'un point afin de garder ce point aussi central que possible dans la vue agrandie résultante (ou sortie) (c'est ce que j'appelle le zoom "sensible à la position" ), similaire au fonctionnement du zoom dans Google Maps. Je suis sûr que cela a déjà été fait de nombreuses fois auparavant - est-ce que quelqu'un connaît le "droit" façon de le faire sous Java Swing?. Serait-il préférable de jouer avec les transformations de Graphic2D plutôt que d'utiliser JScrollPanes ?

Exemple de code suivant:

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);
    }       
  }
}
Était-ce utile?

La solution

Testé, cela semble fonctionner ...

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();
}

Mettre à jour

Voici une explication: le point p est l'emplacement de la souris par rapport au FPanel . Comme vous redimensionnez la taille du panneau, l'emplacement de p (par rapport à la taille du panneau) sera redimensionné avec le même facteur. En soustrayant l'emplacement actuel de l'emplacement redimensionné, vous obtenez combien le point «se déplace» lorsque le panneau est redimensionné. Ensuite, il suffit simplement de déplacer la position du panneau dans le panneau de défilement de la même quantité dans la direction opposée pour placer p sous le curseur de la souris.

Autres conseils

Voici un refactoring mineur de la solution de @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);
}

Votre MouseWheelListener doit également localiser le curseur, le déplacer au centre de JScrollPane et ajuster les contenus xmin / ymin et xmax / ymax du contenu à afficher.

Je pense que smt comme ça devrait marcher ...


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: Changé quelques choses.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top