B Java Swing Framework
Android applications are user-driven graphical applications. In order to become familiar with some of the coding patterns involved in this kind of software, it can be useful to consider how to build simple graphical applications in Java using a different GUI framwork: the Swing library.
This appendix references code found at https://github.com/info448/appendix-java-swing. Note that this tutorial involves Java Programming: you can either do this in Android Studio, or just using a light-weight text editor such as Visual Studio Code or Sublime Text.
The Swing library is a set of Java classes used to specify graphical user interfaces (GUIs). These classes can be found in the javax.swing
package. They also rely on the java.awt
package (the “Advanced Windowing Toolkit”), which is an older GUI library that Swing builds on top of.
- Fun fact: Swing library is named after the dance style: the developers wanted to name it after something hip and cool and popular. In the mid-90s.
Let’s look at an incredibly basic GUI class: MyGUI
found in the src/main/java/
folder. The class subclasses (extends) JFrame
. JFrame
represents a “window” in your operating system, and does all the work of making that window show up and interact with the operating system in a normal way. By subclassing JFrame
, we get that functionality for free! This is how we build all GUI applications using this framework.
Most of the work defining a Swing GUI happens in the JFrame
constructor (called when the GUI is “created”).
We first call the parent constructor (passing in the title for the window), and then call a method to specify what happens when we hit the “close” button.
We then instantiate a
JButton
, which is a class representing a Java Button. Note thatJButton
is the Swing version of a button, building off of the olderjava.awt.Button
class.We then
.add()
this button to theJFrame
. This puts the button inside the window. This process is similar to using jQuery to add an HTML element to web page.Finally, we call
.pack()
to tell the Frame to resize itself to fit the contents, and then.setVisible()
to make it actually appear.We run this program from
main
by just instantiating our specializedJFrame
, which will contain the button.
You can compile and run this program with ./gradlew -q run
. And voila, we have a basic button app!
B.1 Events
If we click the button… nothing happens. Let’s make it print out a message when clicked. We can do this through event-based programming (if you remember handling click
events from JavaScript, this is the same idea).
Most computer systems see interactions with its GUI as a series of events: the event of clicking a button, the event of moving the mouse, the event of closing a window, etc. Each thing you interact with generates and emits these events. So when you click on a button, it creates and emits an “I was clicked!” event. (You can think of this like the button shouting “Hey hey! I was pressed!”) We can write code to respond to this shouting to have our application do something when the button is clicked.
Events, like everything else in Java, are Objects (of the EventObject
type) that are created by the emitter. A JButton
in particular emits ActionEvents
when pressed (the “action” being that it was pressed). In other words, when buttons are pressed, they shout out ActionEvents
.
In order to respond to this shouting, we need to “listen” for these events. Then whenever we hear that there is an event happening, we can react to it. This is like a person manning a submarine radar, or hooking up a baby monitor, or following someone on Twitter.
But this is Java, and everything in Java is based on Objects, we need an object to listen for these events: a “listener” if you will. Luckily, Java provides a type that can listen for ActionEvents
: ActionListener
. This type has an actionPerformed()
method that can be called in response to an event.
We use the Observer Pattern to connect this listener object to the button (button.addActionListener(listener)
). This registers the listener, so that the Button knows who to shout at when something happens. (Again, like following someone on Twitter). When the button is pressed, it will go to any listeners registered with it and call their actionPerformed()
methods, passing in the ActionEvent
it generated.
But look carefully: ActionListener
is not a concrete class, but an abstract interface. This means if we want to make an ActionListener
object, we need to create a class that implements
this interface (and provides the actionPerformed()
method that can be called when the event occurs). There are a few ways we can do this:
We already have a class we’re developing:
MyGUI
! So we can just make that classimplement ActionListener
. We’ll fill in the provided method, and then specify thatthis
object is the listener, and voila.This is my favorite way to create listeners in Java (since it keeps everything self-contained: the
JFrame
handles the events its buttons produce).We’ll utilize a variant of this pattern in Android: we’ll make classes implement listeners, and then “register” that listener somewhere else in the code (often in a nested class).
But what if we want to reuse our listener across different classes, but don’t want to have to create a new
MyGUI
object to listen for a button to be clicked? We can instead use an inner or nested class. For example, create a nested classMyActionListener
that implements the interface, and then just instantiate one of those to register with the button.- This could be a
static
nested class, but then it wouldn’t be able to access instance variables (because it belongs to the class, not the object). So you might want to make it an inner class instead. Of course then you can’t re-use it elsewhere without making theMyGUI
(whose instance variables it referenes anyway)… but at least we’ve organized the functionality.
- This could be a
It seems sort of silly to create a whole new
MyActionListener
class that has one method and is just going to be instantiated once. So what if instead of giving it a name, we just made it an anonymous class? This is similar to how you’ve made anonymous variables by instantiating objects without assigning them to named variables, you’re just doing the same thing with a class that just implements an interface. The syntax looks like:button.addActionListener(new ActionListener() { //class definition (including methods to override) goes in here! public void actionPerformed(ActionEvent event) { //... } });
This is how buttons are often used in Android: we’ll create an anonymous listener object to respond to the event that occurs when they are pressed.
B.2 Layouts and Composites
What if we want to add a second button? If we try to just .add()
another button… it replaces the one we previously had! This is because Java doesn’t know where to put the second button. Below? Above? Left? Right?
In order to have the JFrame
contain multiple components, we need to specify a layout, which knows how to organize items that are added to the Frame. We do this with the .setLayout()
method. For example, we can give the frame a BoxLayout()
with a PAGE_AXIS
orientation to have it lay out the buttons in a vertical row.
container.setLayout(new BoxLayout(container, BoxLayout.PAGE_AXIS));
container.add(theButton);
container.add(otherButton);
- Java has different
LayoutManagers
that each have their own way of organizing components. We’ll see this same idea in Android.
What if we want to do more complex layouts? We could look for a more complex LayoutManager
, but we can actually achieve a lot of flexibility simply by using multiple containers.
For example, we can make a JPanel
object, which is basically an “empty” component. We can then add multiple buttons to this this panel, and add that panel to the JFrame
. Because JPanel
is a Component
(just like JButton
is), we can use the JPanel
exactly as we used the JButton
—this panel just happens to have multiple buttons.
And since we can put any Component
in a JPanel
, and JPanel
is itself a Component… we can create nest these components together into a tree in an example of the Composite Pattern. This allows us to create very complex user interfaces with just a simple BoxLayout
!
- This is similar to how we can create complex web layouts just by nesting lots of
<div>
elements.