Which layout can do this?

2019-02-03 06:22发布

问题:

I'm trying to layout some JLabels in my application as shown in this example:

I always have this JLabel at the middle and the number of the others JLabels is variable it can go from 1 to 30. I have tried Grid layout by choosing a good number of columns/rows and setting some empty JLabels in the white space but i can't get a good result, and can't find how to do it with MigLayout, did any one have a good layouting soulution or any other solution.

PS: I don't want to show the circle it's just to show that the JLabels are in a arranged in a circle.

回答1:

You don't need a layout manager which specifically supports this. You can calculate the x, y positions yourself with some fairly simple trigonometry, then use a regular layout such as SpringLayout.

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SpringLayout;

public class CircleLayout {

  /**
   * Calculate x,y positions of n labels positioned in
   * a circle around a central point. Assumes AWT coordinate
   * system where origin (0,0) is top left.
   * @param args
   */
  public static void main(String[] args) {
    int n = 6;  //Number of labels
    int radius = 100;
    Point centre = new Point(200,200);

    double angle = Math.toRadians(360/n);
    List<Point> points = new ArrayList<Point>();
    points.add(centre);

    //Add points
    for (int i=0; i<n; i++) {
      double theta = i*angle;
      int dx = (int)(radius * Math.sin(theta));
      int dy = (int)(-radius * Math.cos(theta));
      Point p = new Point(centre.x + dx, centre.y + dy);
      points.add(p);
    }

    draw(points);
  }

  private static void draw(List<Point> points) {
    JFrame frame = new JFrame("Labels in a circle");
    frame.setSize(500, 500);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JPanel panel = new JPanel();;
    SpringLayout layout = new SpringLayout();

    int count = 0;
    for (Point point : points) {
      JLabel label = new JLabel("Point " + count);
      panel.add(label);
      count++;
      layout.putConstraint(SpringLayout.WEST, label, point.x, SpringLayout.WEST, panel);
      layout.putConstraint(SpringLayout.NORTH, label, point.y, SpringLayout.NORTH, panel);
    }

    panel.setLayout(layout);

    frame.add(panel);
    frame.setVisible(true);

  }
}



回答2:

I suspect that your requirements are so specialised that there isn't a LayoutManager that can do what you require. Try creating your own!



回答3:

JH labs has a ClockLayout:

This is a very silly layout created for a special purpose. It simply lays out its components in a circle, clockwise from the top.



回答4:

I like @Baqueta's and @sacha's idea:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class CircleLayoutTest {
  public JComponent makeUI() {
    JPanel panel = new JPanel() {
      @Override protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Insets i = getInsets();
        g.translate(i.left, i.top);
        g.setColor(Color.RED);
        int w = getWidth() - i.left - i.right;
        int h = getHeight() - i.top - i.bottom;
        g.drawOval(0, 0, w, h);
        g.translate(-i.left, -i.top);
      }
    };
    panel.setLayout(new FlowLayout() {
      @Override public void layoutContainer(Container target) {
        synchronized(target.getTreeLock()) {
          int nmembers  = target.getComponentCount();
          if(nmembers<=0) return;
          Insets i = target.getInsets();
          double cx = .5 * target.getWidth();
          double cy = .5 * target.getHeight();
          Component m = target.getComponent(0);
          Dimension d = m.getPreferredSize();
          m.setSize(d.width, d.height);
          m.setLocation((int)(cx+.5-.5*d.width),(int)(cy+.5-.5*d.height));
          if(nmembers-1<=0) return;
          double rw = .5 * (target.getWidth() - i.left - i.right);
          double rh = .5 * (target.getHeight() - i.top - i.bottom);
          double x = 0, y = 0, r = 0;
          double radian = 2.0 * Math.PI / (nmembers-1);
          for(int j=1; j<nmembers; j++) {
            m = target.getComponent(j);
            if(m.isVisible()) {
              d = m.getPreferredSize();
              m.setSize(d.width, d.height);
              x = cx + rw * Math.cos(r) - .5 * d.width;
              y = cy + rh * Math.sin(r) - .5 * d.height;
              m.setLocation((int)(x+.5), (int)(y+.5));
              r += radian;
            }
          }
        }
      }
    });
    JPanel p = new JPanel(new BorderLayout());
    p.add(initPanel(panel));
    return p;
  }
  private static JComponent initPanel(JComponent p) {
    p.setBorder(BorderFactory.createEmptyBorder(50,50,50,50));
    for(int i=0; i<6; i++) {
      p.add(new JLabel("No."+i));
    }
    return p;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(new CircleLayoutTest().makeUI());
    f.setSize(320 ,320);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}


回答5:

I am using Windows forms, as I don't have an Java tools installed, but the idea is the same, you will just have to imagine that you are adding the JLabel instead of Buttons and that this is a JFrame or JWindow instead of a .NET Form.

The code should be something like this, if we assume a 800 x 800 pixel area to layout stuff on

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        this.Load += new EventHandler(Form1_Load);
    }

    void Form1_Load(object sender, EventArgs e)
    {
        int numberItems = 18;
        int centreX = 400;
        int centreY = 400;


        double offset = Math.PI;
        double step = Math.PI * 2 / numberItems;


        Button b = null;
        for (int i = 0; i < numberItems; i++)
        {
            b = new Button();
            b.Width = 30;
            b.Height = 30;
            SetPosition(b, 370, offset, i, step);
            this.Controls.Add(b);
        }

        b = new Button();
        b.Width = 30;
        b.Height = 30;
        b.Location = new Point(centreX, centreY);
        this.Controls.Add(b);
    }


    private void SetPosition(Button button, int legLength, double offset, double posOffSet, double step)
    {

        int x = (int)(legLength + Math.Sin(offset + posOffSet * step) * legLength);
        int y = (int)(legLength + Math.Cos(offset + posOffSet * step) * legLength);

        button.Location = new Point(x, y);
    }
}


回答6:

  1. MigLayout can do absolute positioning with "pos x y [x2] [y2]" as a component constraint. MigLayout really is the layout manager to rule them all. Check out the webstart demo on their front page, it shows off absolute positioning well. You'd still have to calculate the positions of the components like with the custom layout manager idea.

  2. Also you could just turn off the layout.

  3. And if you want to get really creative, you could look at JHotDraw.