Usando Java Graphics 2D - App Desktop

Olá galera,

Seguinte, eu preciso desenhar usando o Java, eu já estive pesquisando e implementando alguma coisa com o Graphics 2D e tals, nada muito complexo, então preciso de uma ajuda.

Preciso desenhar em um JPanel algo como uma “planta baixa” de construção, mas acredito que a que eu preciso seja até menos complexa, no meu caso eu preciso que seja assim:

  • Desenho inicial, que tem a área geral que será utilizada como base para o restante, esse desenho inicial tem o “eixo x” e o “eixo y” e o desenho do retângulo e suas dimensões, conforme imagem:

desenho_inicial

  • Tendo aquele desenho de base aí preciso desenhar dentro do retângulo mais retângulos, conforme a necessidade e respeitando o tamanho do mesmo, conforme abaixo:

Desenho_Final

Nesta última imagem, o retângulo que está em vermelho seria desenhado dentro do retângulo inicial e possivelmente poderia desenhar mais retãngulos tanto dentro do inicial quanto do que está em vermelho.

Alguém teria alguma ideia ou exemplo de como eu posso implementar isto?

Desde já agradeço.

Que tipo de ajuda você quer ou que dificuldade você está tendo?

O Java2D permite desenha linhas e retângulos facilmente, a parte mais complicada seria controlar os parâmetros para as funções de desenho. Você pode por exemplo armazenar as coordenadas e tamanho dos retângulos em um List e percorrer esse List quando for desenhar. Ou mesmo usar uma classe pronta, como Rectangle2D.

Para manter os demais retângulos dentro de um outro, basta manter a referência das coordenadas mínima e máxima, e verificar quando criar um novo retângulo, para que ele não ultrapasse essas coordenadas.

Abraço.

Olá TerraSkilll,

Então, vou exemplificar mais ou menos como estou pensando em fazer inicialmente e aí exponho minhas dúvidas iniciais.

Tô pensando em fazer uma Classe para representar o “plano base”, ou seja, aquele retângulo da primeira imagem, que no caso vai ser realmente o “plano base” para o restante do desenho, a instância desta classe irá conter então as informações de largura e altura que serão usadas para desenhar o retângulo inicial. Também farei uma Classe de “sub-plano”, esta irá representar cada retângulo que será desenhado dentro do “plano base”. Aí na hora de fazer o desenho, uso os dados do “plano base” e a lista de “sub-planos” que estará contida no “plano base”.

Classe Plano.java:

public class Plano {
    
    private double altura;
    private double largura;
    private List<SubPlano> subPlanos;

    public Plano(double altura, double largura) {
        this.altura = altura;
        this.largura = largura;
    }
    
    public void acionarSubPlanos(SubPlano subPlano) {
        if (subPlanos == null) {
            subPlanos = new ArrayList<>();
        }
        subPlanos.add(subPlano);
    }

    public void removerSubPlanos(SubPlano subPlano) {
        if (subPlanos != null) {
            subPlanos.remove(subPlano);
        }
    }
    
    public List<SubPlano> getSubPlanos() {
        return subPlanos;
    }

    public double getAltura() {
        return altura;
    }

    public void setAltura(double altura) {
        this.altura = altura;
    }

    public double getLargura() {
        return largura;
    }

    public void setLargura(double largura) {
        this.largura = largura;
    }
}

Classe SubPlano.java:

public class SubPlano {
    
    private double altura;
    private double largura;

    public SubPlano(double altura, double largura) {
        this.altura = altura;
        this.largura = largura;
    }

    public double getAltura() {
        return altura;
    }

    public void setAltura(double altura) {
        this.altura = altura;
    }

    public double getLargura() {
        return largura;
    }

    public void setLargura(double largura) {
        this.largura = largura;
    }
}

Fiz o seguinte teste, por enquanto, crio um “plano base” e adiciono à ele um “sub-plano”:

Plano plano = new Plano(400.0, 500.0);
SubPlano subPlano1 = new SubPlano(100.0, 200.0);
plano.acionarSubPlanos(subPlano1);

Fiz também uma Classe para ser o “painel” onde será desenhado:

public class DrawingPane extends JPanel {
    
    private Graphics2D g2;
    private Plano planoBase;
    
    public DrawingPane(Plano plano) {
        this.planoBase = plano;
    }
    
    public void paintComponent(Graphics g) {
        g2 = (Graphics2D) g;
        g2.drawRect(100, 50, (int) planoBase.getLargura(), (int) planoBase.getAltura());
        for (SubPlano subPlano : planoBase.getSubPlanos()) {
            paintSubPlanos(subPlano);
        }
    }
    
    public void paintSubPlanos(SubPlano subPlano) {
        g2.drawRect(100, (((int) planoBase.getAltura()) - 50), (int) subPlano.getLargura(), (int) subPlano.getAltura());
    }
}

Claro que este teste, tem apenas um “sub-plano” então fiz o cálculo manual das coordenadas de onde ele será desenhado:

No g2.drawRect(x, y, largura, altura) usei para a coordenada x o 100 que é a mesma coordenada do “plano base”, para o y peguei a largura do “plano base” e diminui os 50 que era a coordenada y do “plano base” (desta forma vai desenhar na parte de baixo do “plano base”):

teste

Aí já entra minha primeira dúvida, mas e se ao invés de um único “sub-plano” eu tiver dois, três ou quantos for possível colocar dentro do “plano base”, como automatizar este cálculo das coordenadas?

Não sei se entendi bem seu problema, mas um jeito é criar métodos de desenho dentro das classes plano e subplano, passando o objeto Graphics2D como parâmetro, e quando for desenhar os subplanos você adicionar às coordenadas do subplano a coordenada do plano. Algo assim (pseudocódigo):

class Plano{
 int x, y, altura, largura;

 public void desenhar(Graphics2D g2){
   g2.drawRect(this.x, this.y, this.largura, this.altura);

    for (SubPlano subPlano : planoBase.getSubPlanos()) {
            subPlano.desenhar(g2, this.x, this.y); // passa para os subplanos as coordenadas do plano
        }
  }
}

class SubPlano{
 int x, y, altura, largura;

 public void desenhar(Graphics2D g2, int x_plano, int y_plano){
   g2.drawRect(this.x + x_plano, this.y +  y_plano, this.largura, this.altura);
  }
}

Aí, quando for desenhar, cada classe é responsável por desenha a si própria. Isso é bom porque, se você quiser modificar o desenho de um único plano ou subplano (por exemplo, mudando a cor da linha), pode fazer isso direto no plano ou subplano. Quando for desenhar tudo, basta chamar o desenho do plano.

protected void paintComponent(Graphics g) {
  g2 = (Graphics2D) g;

  planoBase.desenhar(g2);
}

Assim, as coordenadas dos subplanos serão sempre relativas à coordenada do plano. Você não precisa se preocupar muito com as coordenadas do plano, somente quando for criar os subplanos. Quando for criar os subplanos, você pode descontar as coordenadas do plano que o contém.

Abraço.

Entendi, quanto a essa parte tranquilo.

O que eu estou “penando” um pouco é na questão de automatizar o cálculo da coordenada de cada subplano que será desenhado. Por exemplo:

Coordenadas plano base:
y = 50
x = 100

Coordenadas do primeiro sub plano:
y = "altura do plano base" - "coordenada y do plano base"
x = "coordenada x do plano base"

Coordenadas do segundo sub plano:
y = "altura do plano base" - "coordenada y do plano base" - "altura do primeiro sub plano"
x = "coordenada x do plano base"

Não sei se a lógica que pensei e exemplifiquei acima está correta, de qualquer forma, como faço agora para calcular a coordenada de um terceiro, quarto ou “n” sub planos?

Acho que agora entendi melhor seu problema. Um subplano pode estar dentro de outro subplano, que pode estar dentro de outro subplano, e assim sucessivamente, até chegar no plano base.

O que você pode fazer é criar uma classe única Plano (nada de subplanos) e cada plano criado referenciar o plano que o contém (um plano pai, por assim dizer). Assim, ao desenhar um plano, ele só precisa saber quais as coordenadas do plano pai, não de todos os outros planos até o plano base (que é um plano igual aos demais).

Algo assim (simplificado):

class Plano{
  int x, y;
  List<Plano> subplanos;
  Plano planoPai;

  public void getX(){ return x; }
  public void getY(){ return y; }

  public void setPlanoPai(Plano plano){  this.planoPai = plano; }

  public void adicionaPlanoFilho(Plano plano){
    subplanos.add(plano);
    plano.setPlanoPai(this);
  }

  public void desenhar(Graphics2D g2){
    if (planoPai != null){
      g2.drawRect(this.x + planoPai.getX(), this.y + planoPai.getY(), this.largura, this.altura);
      }else{
      g2.drawRect(this.x, this.y, this.largura, this.altura);
      }
    }
  }
}

Alternativamente, você pode passar as coordenadas para o método desenhar dos planos filhos, como no exemplo da minha resposta anterior.

Desse modo, você precisa manter somente a referência ao plano pai, e usar o método adicionaPlanoFilho quando criar os demais planos. Algo assim:

Plano planoBase;

void main(blablabla){
  planoBase = new Plano();

  Plano filho1 = new Plano();
  planoBase.adicionaPlanoFilho(filho1);
  
  Plano filho2 = new Plano();
  planoBase.adicionaPlanoFilho(filho2);

  Plano neto1 = new Plano();
  filho1.adicionaPlanoFilho(neto1); // note que está sendo adicionado em filho1, não em planobase

  Plano neto2 = new Plano();
  filho1.adicionaPlanoFilho(neto2);
  
  Plano neto3 = new Plano();
  filho2.adicionaPlanoFilho(neto3);

}

Você teria de criar métodos acessórios (ex: getPlanoFilhos, setX, setY, setLargura, setAltura, construtores) para acessar os filhos de um plano e poder adicionar diretamente neles.

Abraço.

Olá TerraSkilll,

Obrigada por sua resposta, esclareceu bem e até o momento está se encaixando no que eu queria mesmo. :wink:

Só uma dúvida, implementei, no JPanel, o seguinte código para chamar método desenhar da classe Plano:

public void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D) g;

    g2.setColor(Color.BLACK);
    planoBase.desenhar(g2);

    if (planoBase.getSubPlanos() != null) {
       for (Plano subPlano : planoBase.getSubPlanos()) {
          g2.setColor(Color.RED);
          subPlano.desenhar(g2);
          if (subPlano.getSubPlanos() != null) {
             for (Plano subPlanoTwo : subPlano.getSubPlanos()) {
                g2.setColor(Color.RED);
                subPlanoTwo.desenhar(g2);
             }
          }
       }
    }
}

Funcionou, mas me parece que não é a maneira correta, pois digamos que no fim vai “virar em ifs” … porque suponhamos que tenha um plano base que tenha vários subplanos e estes tenham outros subplanos e assim sucessivamente, claro que isso será limitado pela altura e largura para encaixar dentro de cada subplano, mas mesmo assim … então haveria uma maneira diferente de fazer isto?

Também pensei em por estas"verificações" dentro do método desenhar na classe Plano, seria o mais correto?

Att.

Para resolver isso, você precisa colocar o código que faz o loop nos subplanos e os desenha dentro do método desenhar da classe Plano, eliminando esses loops e a chamada ao método getSubPlanos no paintComponent. É uma combinação do que sugeri na minha primeira resposta e na segunda:

// na classe JPanel
protected void paintComponent(Graphics g) {
  g2 = (Graphics2D) g.create();

  planoBase.desenhar(g2); // planoBase é um plano, com X filhos

  g2.dispose();
}

//na classe plano:
public void desenhar(Graphics2D g2){
    if (planoPai != null){
        g2.drawRect(this.x + planoPai.getX(), this.y + planoPai.getY(), this.largura, this.altura);
      }else{
        g2.drawRect(this.x, this.y, this.largura, this.altura);
      }

      for (Plano p : this.subplanos) {
        p.desenhar(g2); // desenha os subplanos desse plano
      }
    }
  }

Isso vai cuidar de chamar o método desenhar nos subplanos, e nos subplanos dos subplanos , e nos subplanos dos subplanos dos subplanos, e assim sucessivamente, até um subplano que não tenha subplanos filhos.

Abraço.