2008-09-22 5 views
10

Estoy intentando implementar el zoom sensible a la posición dentro de un JScrollPane. El JScrollPane contiene un componente con un paint personalizado que se dibujará dentro de cualquier espacio que esté asignado, por lo que el zoom es tan fácil como usar un MouseWheelListener que redimensiona el componente interno según sea necesario.¿Cómo se implementa el zoom sensible a la posición dentro de un JScrollPane?

Pero también quiero acercarme (o alejarme) de un punto para mantener ese punto lo más central posible dentro de la vista ampliada (o salida) resultante (esto es a lo que me refiero como 'sensible a la posición' zoom), similar a cómo funciona el zoom en google maps. Estoy seguro de que esto ya se ha hecho muchas veces antes: ¿alguien sabe la forma "correcta" de hacerlo en Java Swing ?. ¿Sería mejor jugar con las transformaciones Graphic2D en lugar de usar JScrollPanes?

Código de la muestra de la siguiente manera:

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); 
    }  
    } 
} 
+0

Agregado de recompensas para ver si puedo conseguir una respuesta completa (lo ideal es: el fragmento de código que, cuando se añade anterior, responde a la pregunta). – tucuxi

Respuesta

8

Probado esto, parece que funciona ...

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

actualización

Aquí es una explicación: el punto p es la ubicación del ratón relativa al FPanel. Dado que está escalando el tamaño del panel, la ubicación de p (relativa al tamaño del panel) se escalará por el mismo factor. Al restar la ubicación actual de la ubicación escalada, obtienes cuánto cambia el punto cuando se cambia el tamaño del panel. Entonces es simplemente una cuestión de cambiar la ubicación del panel en el panel de desplazamiento por la misma cantidad en la dirección opuesta para poner p de nuevo bajo el cursor del mouse.

+0

Exactamente lo que estaba buscando. ¡Gracias! – tucuxi

1

Su MouseWheelListener también tiene que ubicar el cursor, moverlo al centro de la JScrollPane y ajustar el xmin/ymín y xmax/ymáx del contenido que desea ver.

+0

Sí, eso es cierto, también es lo que estaba tratando de hacer en primer lugar (el cursor está ubicado en 'p', y ajustar esos límites correctamente es lo que no sé cómo hacer). – tucuxi

1

creo SMT como este debería estar trabajando ...


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: cambiado un par de cosas.

+0

Su código no mantiene el punto 'p' centrado en la vista resultante (por ejemplo, intente acercarse a la esquina inferior derecha), incluso después de cambiar 'int newTop = py - w/2;' a 'int newTop = py - h/2; '. Por favor, prueba el código antes de proponerlo como una respuesta. – tucuxi

+0

El código actualizado parece acercarse a la esquina superior izquierda (java 6 en XP), no a donde apunta el puntero del mouse. Estoy usando el mismo programa de prueba que figura en la pregunta. – tucuxi

2

Aquí hay una menor refactorización de la solución de K @ Kevin:

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); 
} 
Cuestiones relacionadas