If you ever designed user interfaces for text mode applications, you're familiar with the issues involved. Just take each label, menu, input field, and other decorations and distribute them on the screen, taking into account the size of each one and the screen size. Since all these parameters are known in advance, some simple math does the job.
The same isn't true for graphical applications, however, where the best size and position for each element depend on several factors which, frequently, aren't known at compile time, such as screen resolution and window and font size, to name a few. This makes it impossible to calculate positions and sizes by hand.
Java employs an original solution to this problem in the form of layout managers, which allow you to design complex user interfaces using only Java code (no need for visual tools) that adapt to any screen resolution and size.
Let's take a look at layout managers, how they work, and how to build custom layout managers, so that you can effectively use those that come with the Java Platform or develop your own, if necessary. I'll assume that you're already familiar with AWT and/or Swing.
A layout manager is an object whose only responsibility is to lay out (i.e., determine the size and position of) components inside a container. Every container, has a subclass of java.awt.Container, and provides a method, setLayout, to specify the layout manager to be used.
Usually containers are already set a default layout manager, which can be changed using setLayout if it is not appropriate. Others use a specialized layout manager, which can’t or shouldn’t, be changed, such as java.awt.ScrollPane and javax.swing.JRootPane.
Layout managers must implement the interface java.awt.LayoutManager or java.awt.LayoutManager2. The Java Platform already comes with a number of layout managers, so all you need to do is to instantiate the appropriate class and call the container’s setLayout, passing the newly created layout manager.
You can even use no layout manager at all (e.g., pass null to setLayout), but in this case you must manually specify the position and size of each component using the appropriate methods of java.awt.Component (e.g., setBounds, setLocation and setSize).
Layout managers in action
The best way to understand how layout managers work is through examples. Listing A shows a very simple program that creates a java.awt.Frame with a java.awt.BorderLayout layout manager (i.e., the default for Frame) and populates it with a number of buttons—one at each BorderLayout region, except the BorderLayout.WEST region, which is populated with a java.awt.Panel component. The panel also uses a BorderLayout and is all populated with buttons.
Running the program results in the window shown in Figure A. The method java.awt.Window.pack does the magic. When the pack method is called, it provides dimensions for the window using its preferred size, as returned by the method getPreferredSize.
|A simple window preferred size|
When the getPreferredSize method is invoked on a component, it returns the best size for the component (in the case of a button, it is the size of the button’s label, considering the font, plus some extra space and the border), but when it is called for a container, the layout manager is invoked to calculate its best size.
The layout manager calls getPreferredSize for each component and applies some algorithm, which depends on the layout manager, to calculate the final size. If a component is itself a container, this process is applied recursively. The final result is that every component receives enough space to be displayed appropriately.
Once the window has been given dimensions, the layout manager is called to lay out the components. This time, the size has already been determined, so the layout manager just needs to position and size each component, according its own logic and the space available.
That layout manager is also called to lay out the components again every time the container’s size changes or some component’s attribute that affects its size is changed. If you resize the window of the example program, the buttons are redistributed accordingly.
If you click on a button, its font size is doubled, increasing the button’s label and, consequently, the button itself, causing the window to be laid out again as shown in Figure B.
|Resizing the window and components|
Building a custom layout manager
Using the layout managers provided by the Java Platform, or combinations of them, you can achieve virtually any layout. However, there are still cases when the provided layout managers don’t meet your needs. When this is the case, the solution is to create your own custom layout managers.
Fortunately, creating a layout manager isn't difficult. All you need to do is to implement the java.awt.LayoutManager interface, or java.awt.LayoutManager2 if your layout manager needs constraints. Table A and Table B provide a complete description of each interface.
|addLayoutComponent(String,Component)||Adds the component to the layout manager, associating it with the given string|
|layoutContainer(Container)||Lays out the components of the given container|
|minimumLayoutSize(Container)||Calculates the minimum size of the container, taking into account its components|
|preferredLayoutSize(Container)||Calculates the preferred size of the container, taking into account its components|
|removeLayoutComponent(Component)||Removes the given component from the layout manager|
|addLayoutComponent(Component,Object)||Adds the component to the layout manager, associating it with the given constraints|
|getLayoutAlignmentX(Container)||Returns the horizontal alignment of the container|
|getLayoutAlignmentY(Container)||Returns the vertical alignment of the container|
|invalidateLayout(Container)||Invalidates any information the layout manager has cached about the container|
|maximumLayoutSize(Component)||Calculates the maximum size for the container, taking into account its components|
Listing B shows a simple implementation of a custom layout manager, ProportionalGridLayout. ProportionalGridLayout positions components in a grid of cells, specified at construction time. The space each cell takes is proportional to the size of its row and column.
Listing C is a simple example that shows ProportionalGridLayout in action. It creates a frame managed by a ProportionalGridLayout with three rows and three columns, populated by buttons. Running the example results in Figure C. Since each button is roughly the same size, all rows and columns have the same size.
|A custom layout manager example|
If the window size is increased and a button is clicked (doubling its font size), its row and column receives proportionally more space than the others, as shown in Figure D.
|The custom layout strategy|
The heart of ProportionalGridLayout is the methods getColWidth and getRowHeight, which return the preferred width of a given column and the preferred height of a given row, respectively. The preferred width/height is calculated by iterating over all cells of the given column/height and returning the largest preferred width/height.
Given these methods, implementing preferredLayoutSize is straightforward. It just sums up the preferred size of all columns and rows plus the container borders, and returns the result.
The implementation of the method layoutContainer is a bit larger, but is equally simple. It just distributes the container’s width between the columns proportionally to each column’s preferred width, and the container’s height proportionally to each row’s preferred height. The last column and row is a special case and takes the remaining space to avoid discrepancies caused by floating point imprecision.
Since ProportionalGridLayout does not use constraints, addLayoutComponent and removeLayoutComponent do nothing. To keep the example small, I didn’t implement the minimumLayoutSize method, which always returns (0,0), but its logic is the same as that of the preferredLayoutSize method, but relative to each column/row minimum size.
The concept of layout managers allows the creation of complex user interfaces without the need of visual tools, using just a fair amount of Java code. The resulting user interface looks well independent of platform, screen size, and resolution.
Understanding the inner workings of layout managers is important in developing custom layout managers that address special needs, or just to make better use of the layout managers provided by the Java platform. Take a look at the Java 2 SDK API Specification for more details about layout managers.