Java Tips Weblog

  • Blog Stats

    • 1,302,098 hits
  • Categories

  • Archives

Motion Using the Keyboard

Posted by Rob Camick on June 9, 2013

A simple form of animation is to have a component move across the screen. This is generally done by using one of the four arrow keys to control the direction of movement. There are two common approaches to listen for the pressing of an arrow key (or any key for that matter). One is to use a KeyListener and the other is to use Key Bindings. Lets take a look at these two approaches.

Based on questions in the forums I hang out on, it would appear the using a KeyListener is the common approach (for beginners). I suspect this is so for a few reasons:

  • using a KeyListener (appears) to be a little easier
  • using a KeyListener was the only solution available when using AWT (so there is lots of old code lying around the web)

This is unfortunate because, although Key Bindings do take a little more thought to code, they are far more flexible than using a KeyListener. Anyway, I will attempt to provide two simple solutions that allow you to control the motion of a component within a panel, one using a KeyListener and the other using Key Bindings. Hopefully this will allow you to compare both approaches and decide for yourself which solution to use.

The examples will create a simple JFame with one JPanel and a JLabel (containing an Icon) added to this panel. The panel will use a null layout so the label can be moved randomly. Each class contains a move(int, int) method which is responsible for moving the label across the screen. The two int variables control the X/Y movement respectively every time the move(…) method is invoked. The code in this method is identical for both classes.

This discussion will highlight the differences of each approach with regards to ultimately invoking the move(…) method.

Motion With Key Listener

The MotionWithKeyListener class will use a KeyListener to control the motion of the label. It extends the KeyAdapter class and implements the keyPressed() method. The code in the keyPressed() method is straight forward:

@Override
public void keyPressed(KeyEvent e)
{
	if (e.getKeyCode() == KeyEvent.VK_LEFT)
		move(-deltaX, 0);
	else if (e.getKeyCode() == KeyEvent.VK_RIGHT)
		move(deltaX, 0);
	else if (e.getKeyCode() == KeyEvent.VK_UP)
		move(0, -deltaY);
	else if (e.getKeyCode() == KeyEvent.VK_DOWN)
		move(0, deltaY);
}

The code checks the KeyEvent to determine which key was pressed so it can invoke the move(…) method with the appropriate values. The deltaX/deltaY values will be passed into this class as parameters when the class is created. This will give you a little flexibility to control the distance the label is moved for each event generated.

Using the MotionWithKeyListener class is quite simple:

KeyListener keyListener = new MotionWithKeyListener(component, 3, 3);
component.addKeyListener(keyListener);
component.setFocusable(true); // very important

The class is created and is added as a KeyListener on the label. Don’t forget to make the label focusable. KeyEvents are dispatched to the component that has focus. If the label doesn’t have focus it will never receive events and the motion code will never be executed. Forgetting to make the label focusable is the biggest mistake people make when using a KeyListener. You may also need to invoke the requestFocusInWindow() method to make sure the label has focus.

Take a look at the complete MotionWithKeyListener code to make sure you understand it. Then compile and execute the code to see how it works. Just be warned that clicking on the “LEFT” button will stop the code from working. I’ll explain why in more detail later.

Motion With Key Bindings

Using Key Bindings is a little more involved. Key Bindings are used to bind an Action to a KeyStroke. When a certain KeyStroke occurs, the Action bound to the KeyStroke is executed. It is important to understand these concepts because all Swing components use Key Bindings. I have included links to the Swing tutorial on How to Use Actions and How to Use Key Bindings below for you to read at your leisure.

The MotionWithKeyBindings class shows one way you might use Key Bindings to implement motion on a component. First we need to create an Action:

private class MotionAction extends AbstractAction implements ActionListener
{
	private int deltaX;
	private int deltaY;

	public MotionAction(String name, int deltaX, int deltaY)
	{
		super(name);

		this.deltaX = deltaX;
		this.deltaY = deltaY;
	}

	public void actionPerformed(ActionEvent e)
	{
		move(deltaX, deltaY);
	}
}

The Action is created with a value to identify the Action as well values to control the X/Y movement. Using an Action is just like using an ActionListener. You create a class that implements the actionPerformed(…) method. So, when the Action is invoked the move(…) method is executed with the provided deltaX/deltaY values.

Next we will create a helper method, addAction(…), that will bind a KeyStroke to the above Action:

public MotionAction addAction(String name, int deltaX, int deltaY)
{
	MotionAction action = new MotionAction(name, deltaX, deltaY);

	KeyStroke pressedKeyStroke = KeyStroke.getKeyStroke(name);
	InputMap inputMap = component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
	inputMap.put(pressedKeyStroke, name);
	component.getActionMap().put(name, action);

	return action;
}

The basics of this method will be the same any time you want to use Key Bindings. That is, you will always need 4 Objects:

  • an Action
  • a KeyStroke
  • the components InputMap
  • the components ActionMap

It sounds like a lot, but once you’ve used Key Bindings a few times you get the hang of it.

Finally, we need to create an instance of the MotionWithKeyBindings class:

int delta = 3;
MotionWithKeyBindings motion = new MotionWithKeyBindings(component);
motion.addAction("LEFT", -delta,  0);
motion.addAction("RIGHT", delta,  0);
motion.addAction("UP",    0, -delta);
motion.addAction("DOWN",  0,  delta);

Once the MotionWithKeyBindings class is created, each Key Binding must be added individually to the label by using the addAction(…) method.

This code is longer and a little more complex than the first solution, so take a look at the MotionWithKeyBindings code to make sure you understand it. Then compile and execute the code to see how it works. Don’t forget to try clicking on the “LEFT” button. Unlike the first solution the button actually works and the Key Bindings will continue to work.

Analysis of the Two Approaches

Without a doubt the code for MotionWithKeyListener is simpler although is has the major drawback that the component must have focus. MotionWithKeyBindings is more involved but does not have the focus issue.

However to really evaluate the solutions I believe you need to plan for the future. Requirements constantly change. What works today may not work tomorrow. Which approach allows you to customize it to meet future requirements?

  1. Ease of Coding

    The KeyListener code is shorter and simpler. Although many people will suggest that nested if/else statements is an indication of poor design. The Key Bindings code is longer and more involved because of the need to create Actions and bind individual KeyStrokes to the Action.

  2. Code Flexibility

    The KeyListener approach is not flexible at all. All logic is hardcoded within the class. Any time you want to make changes you need to create an entirely new class with a different class name. On the other hand the Key Bindings approach is extremely flexible. This is because you customize the class by adding the bindings after the class is created.

    For example, how would you support functionality to double the speed of motion for each of the 4 keys when control is held down? That is you want to support Ctrl+Left, Ctrl+Right, Ctrl+Up, Ctrl+Down in addition to the default arrow keys.

    For the KeyListener approach your first thought would probably be to create code like:

    else if (e.getKeyCode() == KeyEvent.VK_RIGHT && e.isControlDown())
    	move(6, 0);
    

    The next question is where to add the code? Do you add the code before or after the existing code? Placement of the code does matter. Try adding the code in both places to see what happens.

    For the Key Bindings approach the code would be:

    motion.addAction("control RIGHT",  6,  0);
    

    The order is not important as Key Bindings can be assigned in any order.

  3. Reusing Actions

    In both examples I added a “LEFT” button at the bottom of the screen.

    When using Key Bindings you can actually click on the button and the component will move to the left. This is because the button uses the same Action that is invoked when you use the left arrow key. This type of reusability is one of the most powerful features of using Actions.

    When using a KeyListener, nothing happens when you click on the button. This is because listeners don’t support this concept. That is there is no way to make an Action out of a listener.

  4. Component Focus

    When you clicked on the LEFT button, the button got focus.

    This causes problems when using a KeyListener. Since the label no longer has focus it can no longer receive events. Even worse you can’t just click on the label to have it regain focus. If you want the label to regain focus then you would need to add a MouseListener to the label. Then in the mousePressed event you would need to invoke the requestFocusInWindow() method on the label.

    Key Bindings do not have this problem. This is because Key Bindings allow you to control which InputMap the bindings are added to.

  5. Moving Multiple Components

    Lets say you have one component you want to control with the arrow keys and another component by using W/A/S/D.

    This is no problem when using Key Bindings. You just create another MotionWithKeyBindings and assign the bindings for the 4 keys and you are good to go.

    Supporting this with a KeyListener is far more involved. First you need to copy and customize the MotionWithKeyListener class to support the 4 keys. Next you need to create a MouseListener that will request focus on a component when the component is clicked. Finally you need to explain to the user that first you must click on the component (to make sure it has focus) before you can use the keyboard to move the component. Not a very friendly user experience.

Using a KeyListener is a simpler approach. However, in the long run using Key Bindings provides you with far more flexibility.

Finally, both of the above solutions have a few limitations:

  • they only support the pressing of one key at a time. For example, you can’t press DOWN and RIGHT at the same time to have diagonal motion.
  • you can’t control the frequency of events. That is, when you hold the key down the motion doesn’t repeat for a period of time. This is because the OS controls the delay interval until another keyPressed event is generated.

In the next section I will present a potential solution for the above problems.

Motion With Multiple Keys Pressed

The KeyboardAnimation class is used to implement the solution. The solution uses Key Bindings, so I will discuss the difference between this solution and the MotionWithKeyBindings solution.

The implementation differences are:

  • it uses a Map to track the currently pressed keys. The Map contains an identifier of the KeyStroke along with a Point to represent the X/Y movement of this KeyStroke.
  • the move(…) method has been slightly modified. When this method is invoked it now iterates through all the entries in the Map and adds up values found in each Point object to determine the X/Y movement of the component.
  • A Timer is used to schedule the motion. When the Timer fires the move(…) method is invoked. This allows us to control the frequency of movement while not worrying about the OS delay rate and repeat rate.
  • the addAction(…) method has been modified to create Actions and add bindings to handle both keyPressed and keyReleased events.
  • the Action has been modified. It does not directly invoke the move(…) method. Instead it invoke a new method called handleKeyEvents(…). This method is responsible for updating the Map and controlling the Timer:
    • when pressed the KeyStroke is added to the Map. If this is the first KeyStroke in the Map, then the Timer is started
    • when released the KeyStroke is removed from the Map. If the Map is empty, then the Timer is stopped.

Take a look at the code and then try executing the program. I think you will find the label responds better to any key press. Hopefully these example will give you ideas about controlling a components motion when using the keyboard.

Get The Code

MotionWithKeyListener.java
MotionWithKeyBindings.java
KeyboardAnimation.java

Related Reading

Java Tutorials: How to Use Actions
Java Tutorials: How to Use Key Bindings
Java Tutorials: How to Use Swing Timers
Java Tutorials: How to Write a Mouse Listener
Java API: Action
Java API: KeyStroke

About these ads

3 Responses to “Motion Using the Keyboard”

  1. ptodd said

    I experienced some difficulties on OSX with keys held down for a long time not behaving as expected. See http://lifehacker.com/5826055/make-your-keyboard-keys-repeat-properly-when-held-down-in-mac-os-x-lion

  2. thank lot it is easy to understand and lot of impotent information.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 97 other followers

%d bloggers like this: