Would you like to tell us how we are doing?

You bet No thanks

Sorry about the red box, but we really need you to update your browser. Read this excellent article if you're wondering why we are no longer supporting this browser version. Go to Browse Happy for browser suggestions and how to update.

Tutorial: Create a custom button

This tutorial introduces you to UI development in the BlackBerry Java SDK.

In this tutorial, we'll create a custom button by extending the Field class. Our button has a custom border that is created from PNG images. It responds to events (such as selecting and clicking) by changing its visual appearance.

Here's an example of what we'll be creating:

Final result of this tutorial.

Before you start

Before you begin this tutorial, you should have the following:

  • Basic knowledge of Java and an understanding of developing applications using the BlackBerry Java SDK
  • A tool, such as Eclipse and the BlackBerry Java Plug-in for Eclipse, to create and edit .java files.

For more information about how to install and use the BlackBerry Java Plug-in, visit www.blackberry.com/go/devdocs to read the BlackBerry Java Plug-in for Eclipse Installation and Configuration Guide.

Create the class files and image files

In Eclipse or in a text editor, create the following three .java files to represent the different parts of our application:

  • CustomButton.java: This file will contain the main application class, CustomButton. This class will contain the main() method, which is the entry point to our application.
  • CustomButtonScreen.java: This file will contain the class that represents the screen in our application, CustomButtonScreen. This screen is displayed when the application starts, and will contain the fields, managers, and other components that will make up the UI of our application.
  • CustomButtonField.java: This file will contain the class that represents our custom button. This class will contain all of the methods that are required to draw the graphical elements of the button (such as border, background, and text) as well as the methods that determine how these graphical elements are arranged.

You should also create the following three PNG image files:

  • custom_normal.png: This image will represent the border and background of the button when it does not have the focus on the screen.
  • custom_focus.png: This image will represent the border and background of the button when it has the focus, but is not pressed.
  • custom_active.png: This image will represent the border and background of the button when the BlackBerry device user presses the button.

In this tutorial, we'll use the following three images:

Images we'll use to create our buttons.

Set up the custom button class

We'll start by creating the class that represents our custom button, CustomButtonField. Our button consists of text that is surrounded by a border. The border is represented by the three PNGs that we created earlier, each of which represents a different visual state of the button. Using PNGs to create the border allows for a lot of flexibility when designing the button, as you can simply use a different set of PNGs to change the appearance of the button.

Import the required classes

Open the CustomButtonField.java file that we created earlier. First, we need to import the classes that our button uses:

Class

Description

Field

Provides the basic functionality for fields. Nearly all UI components in the BlackBerry Java SDK, such as those that are included in the net.rim.device.api.ui.component and net.rim.device.api.ui.container packages, are derived from the Field class, so our button will also derive from this class.

Graphics

Allows an application to draw directly on the screen of the device. We'll use this class to draw the visual elements of the button.

DrawStyle

Provides drawing styles that are used by Graphics objects and Field objects. We'll use several of the constants in this class to specify how to draw the visual elements of the button.

XYRect

Represents a 2-D rectangle. We'll use XYRect objects to represent the boundaries of the content area, border area, and background area of the button.

Border and Background

Represents the border and background of the button.

Keypad, TouchEvent, and Characters

Provides support for input handling, such as receiving and processing keyboard input and touch input.

import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.DrawStyle;
import net.rim.device.api.ui.XYRect;
import net.rim.device.api.ui.decor.Border;
import net.rim.device.api.ui.decor.Background;
import net.rim.device.api.ui.Keypad;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.system.Characters;

Create the button class and instance variables

Next, we create the CustomButtonField class. This class extends Field to provide support for the fundamental properties of fields, such as scrolling and focus handling.

public class CustomButtonField extends Field
{

The CustomButtonField class needs instance variables to store the properties of the button. In particular, we'll need variables to represent the text color, border, and background for each visual state of the button, as well as variables to represent the text of the button and the current visual state. We'll also need XYRect objects to represent the dimensions of the button's components (content area, border area, and background area).

    // text color
    private int _normalColor;
    private int _focusColor;
    private int _activeColor;

    // border
    private Border _normalBorder;
    private Border _focusBorder;
    private Border _activeBorder;

    // background
    private Background _normalBackground;
    private Background _focusBackground;
    private Background _activeBackground;

    // text of the button
    private String _text;

    // flags to indicate the current visual state
    private boolean _active;
    private boolean _focus;

    // dimensions of the button's components
    private XYRect _contentRect;
    private XYRect _borderRect;
    private XYRect _backgroundRect;

Create the constructor

To complete the initial portion of our custom button, we provide a single constructor that accepts the following parameters:

  • The text to display on the button

  • The borders to use for each visual state of the button

  • The text color for each visual state

  • The style of the button

The style parameter that we provide to the constructor consists of style bits that are defined in the Field class and determine the properties of a field. Some of these properties are used by the field itself, and some are used by the field's manager. For example, you can use Field.USE_ALL_WIDTH to create a field that uses the entire available width that is provided by its manager, and you can use Field.FIELD_RIGHT to align the field to the right in its manager.

    public CustomButtonField( String text, Border normalBorder,
        Border focusBorder, Border activeBorder,
        int normalColor, int focusColor, int activeColor,
        long style )
    {

The first thing that we should do in our constructor is invoke super() to create a basic field using the Field constructor. This basic field should be able to accept the focus, and should also reflect any style bits that are specified. Then, we populate the instance variables with the appropriate parameter values that the constructor received.

        super( Field.FOCUSABLE | style );

        _text = text;

        _normalColor = normalColor;
        _focusColor = focusColor;
        _activeColor = activeColor;

        _normalBorder = normalBorder;
        _focusBorder = focusBorder;
        _activeBorder = activeBorder;

        _normalBackground = _normalBorder.getBackground();
        _focusBackground = _focusBorder.getBackground();
        _activeBackground = _activeBorder.getBackground();

        _borderRect = new XYRect();
        _contentRect = new XYRect();
        _backgroundRect = new XYRect();
    }

We've used getBackground() on the Border objects to retrieve backgrounds for each visual state of the button. These backgrounds are based on the images that you use to create the associated borders, and we'll use these backgrounds when it's time for us to paint the background of our button.

Now that we've set up our button, we'll implement the methods that are required to properly lay out the contents of the button.

Lay out the button

Our custom button is made up of three areas:

  • Content area: The area that contains the text of the button
  • Background area: The area that represents the background of the content area, including the content area
  • Border area: The area that represents the entire button, including the content area and background area

The three areas of the button.

The purpose of the layout methods in the CustomButtonField class is to arrange these areas properly in relation to each other, as well as in relation to the amount of space that is provided by our button's manager. For example, we need to make sure that the text of the button is positioned inside the boundaries of the button's border.

Set the size of the button

To set the size of the button, we implement the getPreferredWidth() and getPreferredHeight() methods. You don't need to implement these methods, but managers might use them to determine the best width and height for a custom field. In the case of our custom button, these methods provide appropriate width and height values based on the text of the button. The layout() method of our button uses getPreferredWidth() and getPreferredHeight() to specify the size of the button according to its text.

In general, it's a good idea to implement getPreferredWidth() and getPreferredHeight() if your custom field includes content that might affect the size of the field. For example, if your field contains images or other graphical elements, you'll probably want to implement getPreferredWidth() and getPreferredHeight() to return values that are based on the size of these elements. You can then invoke these methods in your layout() method to size your field appropriately.

In getPreferredWidth(), we test to see if this button was created using the Field.USE_ALL_WIDTH style bit. If it was, we want our button to use the entire width that's provided by the manager, so we return a large integer value to indicate this; you'll see why when we implement the layout() method in the next section. Otherwise, we specify the width to be the width of the button's text, in addition to the width of the left border and right border. We invoke getBounds() to determine the width of a String.

    public int getPreferredWidth()
    {
        if( isStyle( USE_ALL_WIDTH ) )
        {
            return Integer.MAX_VALUE;
        }
        else
        {
            return _normalBorder.getLeft() + getFont().getBounds( _text )
                + _normalBorder.getRight();
        }
    }

In getPreferredHeight(), we specify the height of our button to be the height of the button's text, in addition to the height of the top border and bottom border.

    public int getPreferredHeight()
    {
        return _normalBorder.getTop() + getFont().getHeight()
            + _normalBorder.getBottom();
    }

Lay out the contents

Let's implement the layout() method now. This method is invoked on a Field object whenever it needs to be arranged in a manager, such as when you add the Field to the manager, or when the BlackBerry device is rotated and the orientation changes.

    protected void layout( int width, int height )
    {

The first thing we need to do in layout() is invoke setExtent() to specify the width and height of the entire button. When a manager invokes layout() on a Field, the manager passes in the maximum width and height that the Field is allowed to use. We use the lesser of these provided values (specified by the width and height parameters) and the preferred values (specified by getPreferredWidth() and getPreferredHeight()) for the width and height of the button, because we don't want our button to use more space than what's been provided by our manager. This is why we returned a large integer value from getPreferredWidth() if the Field.USE_ALL_WIDTH style bit was used to create the button, and the result is that our button automatically uses the entire width that's provided by the manager.

        setExtent( Math.min( width, getPreferredWidth() ),
            Math.min( height, getPreferredHeight() ) );

Next, we determine the size of the border, and use these values to set the sizes of the content area, border area, and background area. We invoke getContentRect() to retrieve the size of the content area, which is the area that is occupied by the button's content and padding (the space between the content and the border). The border area is the entire area of the button, and the background area is the border area minus the size of the border itself.

        int borderLeft    = _normalBorder.getLeft();
        int borderRight   = _normalBorder.getRight();
        int borderTop     = _normalBorder.getTop();
        int borderBottom  = _normalBorder.getBottom();

        int borderWidth   = borderLeft + borderRight;
        int borderHeight  = borderTop  + borderBottom;

        getContentRect( _contentRect );
        _borderRect.set( 0, 0, getWidth(), getHeight() );
        _backgroundRect.set( borderLeft, borderTop,
            _borderRect.width - borderWidth, 
            _borderRect.height - borderHeight );
    }

We've now implemented the methods that are required to lay out the contents of our button. Next, we'll implement the methods that handle the painting of the content elements of our button.

Paint the button

The painting methods for our button describe how to draw the button's content (the text of the button), border (the border images for each visual state), and background (derived from the border images). We do most of the painting work for our button in the paint() and paintBackground() methods of the CustomButtonField class.

Create helper methods

Before we start implementing those methods, we need a create a few helper methods. In particular, we create methods that return the color of the text to draw, the current border, and the current background. Because we've designed our button to change its appearance depending on whether it's unfocused, focused, or pressed, each of these methods should check what state the button is in and return the appropriate values. We use the _active and _focus flags to indicate the visual state. If _focus is true, our button is focused. If _active is true, our button is pressed. These flags are set in the input handling methods of our button, which we'll discuss in the next section.

    private int getTextColor()
    {
        if( _active )
        {
            return _activeColor;
        }
        else if( _focus )
        {
            return _focusColor;
        }
        else
        {
            return _normalColor;
        }
    }

    private Border getCurrentBorder()
    {
        if( _active )
        {
            return _activeBorder;
        }
        else if( _focus )
        {
            return _focusBorder;
        }
        else
        {
            return _normalBorder;
        }
    }

    private Background getCurrentBackground()
    {
        if( _active )
        {
            return _activeBackground;
        }
        else if( _focus )
        {
            return _focusBackground;
        }
        else
        {
            return _normalBackground;
        }
    }

Paint the contents

After creating our helper methods, we're ready to implement paint(). The paint() method receives a Graphics object as a parameter. The Graphics object is a representation of a particular region on the screen of the BlackBerry device, and we can use the Graphics object to draw directly in this region.

    protected void paint( Graphics g )
    {

In the case of our button, the Graphics object represents the content area and surrounding padding. Our paint() method sets the color of the text to draw using setColor(), and draws the text of the button using drawText(). In addition to the text and the drawing position, the drawText() method also accepts a set of position flags that indicate how to align the text. We specify the DrawStyle.HCENTER constant to indicate that the text should be centered in the drawing area. This constant is necessary to make sure that the text is centered on our button if the Field.USE_ALL_WIDTH style bit is used to construct the button.

        int oldColour = g.getColor();
        try
        {
            g.setColor( getTextColor() );
            g.drawText( _text, 0, _backgroundRect.y,
                DrawStyle.HCENTER, _contentRect.width ); 
        }
        finally
        {
            g.setColor( oldColour );
        }
    }

We've used a try/finally block to contain our drawing code. It's considered a best practice to leave the Graphics object in the same state at the end of paint() as it was at the beginning, in case other Field objects need this object to draw their contents. If your drawing code changes any properties of the Graphics object (such as the color, as is the case for our button), you should use a finally block to reset these properties to their original values when you're done drawing.

Paint the border and background

Now let's implement paintBackground(). For our custom button, we use this method to paint both the border and the background of the button.

    protected void paintBackground( Graphics g )
    {

First, we retrieve the current border and background, which both depend on the current visual state of the button. Next, we invoke paint() of the Border class to draw the border of the button, and we invoke draw() of the Background class to draw the background of the button. Both of these methods accept as parameters the current Graphics object to use for drawing, as well as XYRect objects that specify the dimensions of each area.

        Border currentBorder = getCurrentBorder();
        Background currentBackground = getCurrentBackground();

        currentBorder.paint( g, _borderRect );
        currentBackground.draw( g, _backgroundRect );
    }

Handle the focus

There's one more method that we need to implement for our button to appear correctly. The drawFocus() method is provided in the Field class, and a field's manager invokes this method after painting the field. This method attempts to draw a standard focus indicator when the field receives the focus, but because our paintBackground() method takes care of drawing the appropriate border and background when our button is focused, we don't need drawFocus() to do anything. So, we override it and provide an empty implementation.

    protected void drawFocus( Graphics g, boolean on )
    {
    }

At this point, we've finished setting up how our button should look. Next, we'll learn how to change the button's visual state in response to user input.

Handle user input

The layout and painting methods that we've implemented so far describe how our custom button looks, but we still need to set the visual state appropriately based on whether the button is unfocused, focused, or pressed. We'll use the _focus and _active flags to indicate these states.

Respond to focus events

We'll talk about focus events first. The Field class includes two methods that handle focus events for a field. The onFocus() method is invoked when a field receives the focus, and the onUnfocus() method is invoked when a field loses the focus. We override both of these methods and set the _focus and _active flags appropriately in each one. After we change the value of the flags, we invoke invalidate(), which indicates that this field's contents must be repainted and results in calls to paint() and paintBackground(). Finally, we invoke super.onFocus() and super.onUnfocus() to complete the change of focus.

    protected void onFocus( int direction )
    {
        _focus = true;
        invalidate();
        super.onFocus( direction );
    }
    
    protected void onUnfocus()
    {
        if( _active || _focus ) {
            _focus = false;
            _active = false;
            invalidate();
        }
        super.onUnfocus();
    }

Respond to user input events

The next step is to implement the methods that respond to specific events, such as when the BlackBerry device user clicks the button or presses the Enter key when the button has the focus. For our custom button, we'll respond to events that are associated with the following user actions:

Action

Result

The user clicks the button using the trackball or trackpad on the BlackBerry device.

The button transitions into the pressed state.

The user releases the button using the trackwheel, trackball, or trackpad.

The button transitions out of the pressed state.

The user presses the Enter key when the button has the focus.

The button transitions into the pressed state.

The user releases the Enter key when the button has the focus.

The button transitions out of the pressed state.

In the methods for each of these events, we set the _active flag to true or false, as appropriate, and then invoke invalidate() to repaint the field. When the state of a custom field changes, the field should invoke fieldChangeNotify() to update the state of the field. We use the clickButton() helper method to make this call for us.

The methods for these events also need to return boolean values. A return value of true means that the method consumed the event; that is, the event was handled completely by the method, and no other input handling methods need to be invoked for that event. A return value of false means that the method didn't consume the event, and additional input handling methods might need to be invoked to continue processing and responding to the event.

For example, consider keyDown() for our button. This method is invoked when the button has the focus and the user presses a key on the device. If the user pressed the Enter key, we respond to this event by changing the state of the button to the pressed state and repainting the field. We don't want keyDown() to consume the key press event, because we still need to change back to the focused state when the user releases the key. Instead of returning true, keyDown() invokes super.keyDown() and returns that value. The Field.keyDown() method always returns false, to allow classes that extend Field to override this method and provide specific handling of the key down event, and so our implementation of keyDown() returns false. When the user releases the key, keyUp() is invoked. If the user released the Enter key, we return change the state of the button to the focused state and return true, indicating that no further processing of this key press event is needed.

For our button, the methods that consume input events are keyUp(), navigationUnclick(), and invokeAction(). The methods that don't consume input events are keyDown(), keyChar(), and navigationClick().

    protected boolean keyUp(int keycode, int time)
    {
        if( Keypad.map( Keypad.key( keycode ),
            Keypad.status( keycode ) ) == Characters.ENTER )
        {
            _active = false;
            invalidate();
            return true;
        }

        return false;
    }

    protected boolean keyDown(int keycode, int time)
    {
        if( Keypad.map( Keypad.key( keycode ),
            Keypad.status( keycode ) ) == Characters.ENTER )
        {
            _active = true;
            invalidate();
        }

        return super.keyDown( keycode, time );
    }

    protected boolean keyChar( char character, int status, int time ) 
    {
        if( character == Characters.ENTER )
        {
            clickButton();
            return true;
        }

        return super.keyChar( character, status, time );
    }

    protected boolean navigationClick(int status, int time)
    {
        _active = true;
        invalidate();

        return super.navigationClick( status, time );
    }

    protected boolean navigationUnclick(int status, int time)
    {
        _active = false;
        invalidate();
        clickButton(); 

        return true;
    }

    protected boolean invokeAction( int action ) 
    {
        switch( action )
        {
            case ACTION_INVOKE:
            {
                clickButton(); 
                return true;
            }
        }

        return super.invokeAction( action );
    } 

    public void clickButton()
    {
        fieldChangeNotify( 0 );
    }

We'd also like our button to be able to change state in response to touch events. To enable this functionality, we override the touchEvent() method. We want to make sure that the touch event occurred inside the boundaries of our button, so we use the touchEventOutOfBounds() helper method. If the touch event occurred inside our button, we determine which specific event occurred and modify the _active flag.

    protected boolean touchEvent(TouchEvent message)
    {
        boolean isOutOfBounds = touchEventOutOfBounds( message );
        switch(message.getEvent())
        {
            case TouchEvent.DOWN:
                 if( !isOutOfBounds )
                 {
                     if( !_active )
                     {
                        _active = true;
                        invalidate();
                     }
                     return true;
                }
                return false;

            case TouchEvent.UNCLICK:
                if( _active )
                {
                    _active = false;
                    invalidate();
                }

                if( !isOutOfBounds )
                {
                    clickButton();
                    return true;
                }

            case TouchEvent.UP:
                if( _active )
                {
                    _active = false;
                    invalidate();
                }

            default : 
                return false;
        }
    }

    private boolean touchEventOutOfBounds( TouchEvent message )
    {
        int x = message.getX( 1 );
        int y = message.getY( 1 );
        return ( x < 0 || y < 0 || x > getWidth() ||
            y > getHeight() );
    }

Handle dirty and muddy states

There are two more methods that we should override for our custom button. Each field has two internal states called dirty and muddy. A field is dirty if its contents have changed and these changes haven't been saved. A field is muddy if its contents have changed and the field still has the focus. These states are important if you have data on your screen that should be saved before the screen is closed. Because our button doesn't have any data that needs to be saved, we don't need to worry about these states and their associated methods, so we override setDirty() and setMuddy() and provide empty implementations.

    public void setDirty( boolean dirty )
    {
    }

    public void setMuddy( boolean muddy )
    {
    }

We've now finished creating the custom button. The next step is to create an application that uses our button.

Create the application class

Start by opening the CustomButton.java file that we created earlier. We only need to import one class, UiApplication.

import net.rim.device.api.ui.UiApplication;

We create the CustomButton class that represents our application, and extend the UiApplication class to create an application that has a UI.

public class CustomButton extends UiApplication
{

The main() method of CustomButton is the entry point to our application. We create an instance of our application class, and invoke enterEventDispatcher() to start the event dispatching thread.

    public static void main(String[] args)
    {
        CustomButton theApp = new CustomButton();
        theApp.enterEventDispatcher();
    }

Finally, we implement the constructor of our application class, and push the only screen for our application onto the display stack to display it to the BlackBerry device user.

    public CustomButton()
    {
        pushScreen(new CustomButtonScreen());
    }
}

Finally, we need to create our screen class.

Create the screen class

The screen class for our application is called CustomButtonScreen, and it's here that we create new CustomButtonField objects and display them on the screen of the BlackBerry device.

Import the required classes

Open the CustomButtonScreen.java file that we created earlier. We'll need to import several classes to use in our screen. In addition to some of the classes that we used in CustomButtonField, we'll need to import the following classes:

Class

Description

Bitmap

Represents a bitmap image. We'll use Bitmap objects to store the PNG images that represent the border of our custom button.

BorderFactory

Allows you to create different types of borders. We'll use this class to create Border objects using our Bitmap objects.

XYEdges

Represents the width of a rectangular border area, in pixels. We'll use this class to specify how thick the border of our button should be.

MainScreen

Represents a screen for a BlackBerry device application. Our screen class will extend MainScreen to provide the fundamental elements of a screen, such as a title section and main scrollable section.

import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.decor.Border;
import net.rim.device.api.ui.decor.BorderFactory;
import net.rim.device.api.ui.XYEdges;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.container.MainScreen;

Create the screen class and constructor

Next, let's create our screen class. Make sure to extend the MainScreen class.

public class CustomButtonScreen extends MainScreen
{

All of the code for our screen class is placed in the constructor, so let's create that now.

    public CustomButtonScreen()
    {

Create the border

To create the border that we want our buttons to use, we need to load the PNG images into our application. We invoke Bitmap.getBitmapResource() to retrieve Bitmap objects that represent the borders for the different visual states. Then, we invoke BorderFactory.createBitmapBorder() to create Border objects based on each of these Bitmap objects. In our call to BorderFactory.createBitmapBorder(), we pass in an XYEdges object that specifies the thickness (in pixels) of our border.

        Bitmap normalBorderPic = Bitmap.getBitmapResource("custom_normal.png");
        Bitmap focusBorderPic = Bitmap.getBitmapResource("custom_focus.png");
        Bitmap activeBorderPic = Bitmap.getBitmapResource("custom_active.png");

        Border normalBorder = BorderFactory.createBitmapBorder(new
          XYEdges(10, 10, 10, 10), normalBorderPic);
        Border focusBorder = BorderFactory.createBitmapBorder(new
          XYEdges(10, 10, 10, 10), focusBorderPic);
        Border activeBorder = BorderFactory.createBitmapBorder(new
          XYEdges(10, 10, 10, 10), activeBorderPic);

Create the custom buttons

We'll create three of our custom buttons to add to the screen. The text of the buttons changes color depending on the visual state of the button. When the button is unfocused, the text is black (0x000000). When the button is focused, the text is light gray (0xDDDDDD). When the button is pressed, the text is red (0xFF0000).

The first button has no style bits associated with it. The second button uses the entire available width of the screen, indicated by the Field.USE_ALL_WIDTH style bit. The third button is aligned to the right of the screen, indicated by the Field.FIELD_RIGHT style bit.

        CustomButtonField cbf1 = new CustomButtonField("My First Button",
          normalBorder, focusBorder, activeBorder, 0x000000, 0xDDDDDD,
          0xFF0000, 0);
        cbf1.setMargin(10, 10, 10, 10);

        CustomButtonField cbf2 = new CustomButtonField("My Second Button",
          normalBorder, focusBorder, activeBorder, 0x000000, 0xDDDDDD,
          0xFF0000, Field.USE_ALL_WIDTH);
        cbf2.setMargin(10, 10, 10, 10);

        CustomButtonField cbf3 = new CustomButtonField("My Third Button",
          normalBorder, focusBorder, activeBorder, 0x000000, 0xDDDDDD,
          0xFF0000, Field.FIELD_RIGHT);
        cbf3.setMargin(10, 10, 10, 10);

Next, we add the custom buttons to the screen.

        add(cbf1);
        add(cbf2);
        add(cbf3);

Finally, nearly every screen in a BlackBerry device application should have a title.

        setTitle("Custom Button Demo");
    }
}

We've now finished the entire application. When you compile and run this application, you should see our three custom buttons on the screen.

Completed sample with three buttons on the screen.