java.awt.Shape - zmiana położenia

0

Witam,
Proszę o pomoc, ponieważ nie mogę znaleźć odpowiedniego sposobu na ustalenie położenia kształtu (interfejs java.awt.Shape).

Znalazłem jeden sposób, który wymaga wykorzystania klasy java.awt.Graphics/java.awt.Graphics2D, ale taki sposób jest mało pewny i mało precyzyjny. Szukam takiego sposobu, aby można było do funkcji podać argumenty Shape shape i Point2D location.

Nasuwa mi się na myśl także kilka pytań:

  1. Jaką metodę z interfejsu java.awt.Shape wywołują metody klasy java.awt.Graphics/java.awt.Graphics2D do narysowania/wypełnienia w odpowiednim miejscu?
  2. Jak stworzyć swój własny kształt, który będzie implementował interfejs java.awt.Shape?
0

Ad 1) Interfejs Shape nie definiuje jak dany obiekt ma być rysowany. W jakiejś implementacji Graphics, która ma mieć metodę draw(Shape s), i w tejże metodzie znajdziesz zwykle ify w stylu "s instanceof Rectangle". Oczywiście jest to inaczej zorganizowane i tak bardzo zróżnicowane, że trudno się w to zagłębić.

0

Musi być przecież sposób, aby to zrobić. Może jest jakąś klasa, która dostarcza taką możliwość przez załużmy metody statyczne, albo przez opakowanie. Czy biblioteka AWT nie zawiera w sobie takich klas?

Raczej nie sądzę, żeby tam były tego typu ify.

1

Możesz zrobić coś takiego:

Drawable.java:
    public interface Drawable2D extends Shape {
        public void draw(Graphics2D g2);
    }
    
MojKsztalt.java:
    public class MojKsztalt implements Drawable2D {
        // nadpisanie metod z interfejsu Shape, konstruktor
        public void draw(Graphics2D g2) {
            // wywołanie funkcji rysujących
        }
    }


konstruktor JPanelu:
    List<Shape> elementy = new ArrayList<Shape>();
    elementy.add(new Rectangle(..));
    elementy.add(new MojKsztalt(..));

rysowanie:
    paintComponent() {
        for (Shape s : elementy) {
            if (s instanceof Drawable) {
                ((Drawable)s).draw(g2);
            }
            else {
                g2.draw(s);
            }            
        }
    }

Albo stworzyć swoją klasę rozszerzającą JComponent. Ona ma metodę paintComponent, którą możesz nadpisać. Wtedy możesz dodawać utworzony komponent do panelu, tak samo jak dodajesz np. przyciski.

0

Ad 1, metoda translate z klasy Graphics.

0

@Visher dobrze myślisz, ale chodzi o to, aby stworzyć taki obiekt, który jedynie implementuje interfejs Shape i w metodzie setCenterPoint(Point2D center) zmienia lokalizację Shape'a.

To co ty proponujesz będzie z pewnością działać dobrze.

Czytałem dokumentację Shape'a i znalazłem coś takiego:

The contains and intersects methods consider the interior of a Shape to be the area it encloses as if it were filled. This means that these methods consider unclosed shapes to be implicitly closed for the purpose of determining if a shape contains or intersects a rectangle or if a shape contains a point.

Czy to znaczy, że metoda contains wyznacza punkty które będą malowane metodą draw, a metoda intersects określa punkty dla metody fill?

0

Contains oznacza "zawiera", czyli punkt może zawierać się w kwadracie, albo mniejszy kwadrat zawierać się w większym kwadracie. Intersect to natomiast "przecięcie" - czyli kwadrat może przecinać linia, lub inny kwadrat, w sposób taki że nie zawierają się w sobie. Z resztą, te dwie metody zwracają typ boolean, i po prostu służą do sprawdzania, czy coś się w czymś zawiera lub przecina.

0

Wiem co to znaczy, ale może metody draw i fill mogą używać tych metod do sprawdzania punktów, które mają narysować.

1

Ad 2) Z jakiejś strony.

 
public class Draw2DObjects extends JFrame {
  Shape shapes[] = new Shape[5];
  public static void main(String args[]) {
    Draw2DObjects app = new Draw2DObjects();
  }

  public Draw2DObjects() {
    add("Center", new MyCanvas());
    shapes[0] = new Line2D.Double(0.0, 0.0, 100.0, 100.0);
    shapes[1] = new Rectangle2D.Double(10.0, 100.0, 200.0, 200.0);
    shapes[2] = new Ellipse2D.Double(20.0, 200.0, 100.0, 100.0);
    GeneralPath path = new GeneralPath(new Line2D.Double(300.0, 100.0, 400.0, 150.0));
    path.append(new Line2D.Double(25.0, 175.0, 300.0, 100.0), true);
    shapes[3] = path;
    shapes[4] = new RoundRectangle2D.Double(350.0, 250, 200.0, 100.0, 50.0, 25.0);
    setSize(400, 400);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setVisible(true);
  }

  class MyCanvas extends Canvas {
    public void paint(Graphics graphics) {
      Graphics2D g = (Graphics2D) graphics;
      for (int i = 0; i < shapes.length; ++i) {
        if (shapes[i] != null)
          g.draw(shapes[i]);
      }
    }
  }
}

A tu z "implements"
http://onestepback.org/articles/poly/oo-java.html

coś w tym stylu musi być.

0

Znalazłem odpowiedź! :D

Interfejs java.awt.Shape posiada metody getPathIterator(AffineTransform at, double flatness) i getPathIterator(AffineTransform at). Te dwie metody odpowiedzialne są za punkty Shape'a.

Pokażę teraz jak stworzyć Shape'a, aby powstał kwadrat o boku size i współrzędnych (x, y):

1. Tworzymy obiekt QuadratPathIterator, który implementować będzie interfejs PathIterator.

import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;

public class QuadratPathIterator implements PathIterator {
	
	private double x, y, size;
	private AffineTransform affine;
	private int index = 0;

	public QuadratPathIterator(AffineTransform affine, double x, double y, double size) {
		this.affine = affine;
		this.x = x;
		this,y = y;
		this.size = size;
	}

	@Override
	public int getWindingRule() {
		return PathIterator.WIND_NON_ZERO;
	}

	@Override
	public boolean isDone() {
                return index > 5;
        }

	@Override
	public void next() {
                this.index++;
        }

	@Override
        public int currentSegment(float[] coords) {
		if(index == 5) {
			return PathIterator.SEG_CLOSE;
		coords[0] = (float) this.x;
		coords[1] = (float) this.y;
		if(index == 1 || index == 2) {
			coords[0] += (float) size;
		} else if(index == 2 || index == 3) {
			coords[1] += (float) size;
		}
		if(affine != null) {
			affine.transform(coords, 0, coords, 0, 1);
		}
		return (index == 0 ? PathIterator.SEG_MOVETO : PathIterator.SEG_LINETO);
	}

	@Override
        public int currentSegment(double[] coords) {
		if(index == 5) {
			return PathIterator.SEG_CLOSE;
		coords[0] = this.x;
		coords[1] = this.y;
		if(index == 1 || index == 2) {
			coords[0] += size;
		} else if(index == 2 || index == 3) {
			coords[1] += size;
		}
		if(affine != null) {
			affine.transform(coords, 0, coords, 0, 1);
		}
		return (index == 0 ? PathIterator.SEG_MOVETO : PathIterator.SEG_LINETO);
	}

	public double getX() {
		return this.x;
	}

	public double getY() {
		return this.y;
	}

	public double getSize() {
		return this.size;
	}

	// można dodać settery, ale ja nie polecam :)
}

Przedstawię teraz po krótce co robią poszczególne metody:

  • getWindingRule() - zwraca jedną z wartości: PathIterator.WIND_EVEN_ODD i PathIterator.WIND_NON_ZERO. Określają one jakimi regułami ma ją być "nawijane" segmenty (segment - tu: sposób rysowania linii, wyrażany w int i przyjmujący wartość: PathIterator.SEG_MOVETO, PathIterator.SEG_LINETO, PathIterator.SEG_QUADTO, PathIterator.SEG_CUBICTO lub PathIterator.SEG_CLOSE;
  • isDone() - zwraca true jeśli iterator zakończył działanie a false, kiedy jeszcze zwraca elementy. Należy ją tak napisać, aby zwracała true, gdy w iteratorze nie ma już segmentów;
  • next() - zwiększa index iteratora, co powoduje, że każde nastęne odwołanie do obiektu dotyczyć będzie innego segmentu;
  • currentSegment(float[] coords) - funkcja zwraca używany teraz segment i jako argument podawana jest tablica float[6], która zawierać może maksymalnie trzy pary współrzędnych x i y (każda para to inny punkt). Dla segmentu PathIterator.SEG_MOVETO i PathIterator.SEG_LINETO tablica coords zawiera dwa punkty, dla PathIterator.SEG_QUADTO - trzy, a dla PathIterator.SEG_CUBICTO zawiera cztery. Gdy jest segment PathIterator.SEG_CLOSE, wówczas tablica coords powinna być pusta.
  • currentSegment(double[] coords) - powinna wywoływać ten sam efekt co metoda currentSegment(float[] coords)

2. Tworzymy klasę implementującą piękny interfejs Shape:

import java.awt.Shape;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.Rectangle2D;

public class Quadrat2D implements Shape {

	private double x, y, size;

	public Quadrat(double x, double y, double size) {
		this.x = x;
		this.y = y;
		this.size = size;
	}

	@Override
    	public boolean contains(double x, double y) {
        	throw new UnsupportedOperationException("Nie chce mi się myśleć nad tą metodą. Napiszcie ją sobie sami :)");
    	}

    	@Override
    	public boolean contains(Point2D p) {
        	return contains(p.getX(), p.getY());
    	}

    	@Override
    	public boolean contains(Rectangle2D r) {
        	return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
    	}

    	@Override
    	public boolean contains(double x, double y, double w, double h) {
        	throw new UnsupportedOperationException("Nie chce mi się myśleć nad tą metodą. Napiszcie ją sobie sami :)");
    	}

    	@Override
    	public boolean intersects(double x, double y, double w, double h) {
        	throw new UnsupportedOperationException("Nie chce mi się myśleć nad tą metodą. Napiszcie ją sobie sami :)");
    	}

    	@Override
    	public boolean intersects(Rectangle2D r) {
        	return this.intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
    	}

    	@Override
    	public Rectangle getBounds() {
        	return this.getBounds2D().getBounds();
    	}

    	@Override
    	public Rectangle2D getBounds2D() {
        	return new Rectangle2D.Double(this.x, this.y, this.size, this.size);
    	}

    	@Override
    	public PathIterator getPathIterator(AffineTransform at) {
        	return new QuadratPathIterator(at, this.x, this.y, this.size);
    	}

	@Override
	public PathIterator getPathIterator(final AffineTransform at, double flatness) {
		return new FlatteningPathIterator(getPathIterator(at), flatness);
	}
}

  • tego mi się zdaje już nie muszę tłumaczyć :)

Możemy teraz cieszyć się z nowego kształtu :D.

1 użytkowników online, w tym zalogowanych: 0, gości: 1