While it's tempting to use the standard built-in ButtonField class to create user-selectable buttons for your application so you can rapidly release it to the public, I'd recommend delving into creating your own. Going through the process will give you a good understanding of how listeners work and how to detect and react to user input within the Blackberry environment. I'll provide a simple example of just how to do that with a comparison between using our custom button versus the ButtonField class. The end result of our efforts will be a screen with a custom search button and a standard ButtonField button, as shown below:
Note that all the code in each sections below is intended to be combined into one file within the project space.
Create the Custom Button png Using Inkscape
As I've stated in other posts, I recommend using Inkscape for creating the custom vector graphics you'll use on your applications as it's free and is really a great substitute for Illustrator. For this example I crafted two custom search buttons in just a few minutes using the standard "magnifying glass" as inspiration to distinguish it's use for search. One of the images has grey highlights and the other has a glow effect which our app will switch to when the user rolls over it.
Make sure that you keep the end user in mind when you're designing the buttons since you want to make sure they are immediately recognizable for the respective action they are supposed to perform - you don't want to confuse the user because they'll turn off your app before spending 10 minutes trying to figure out how it's supposed to work. I'm not going to post the images I created as separate files to download - instead take some time to learn Inkscape and come up with a few on your own. The time you invest will pay off later (if you insist on being lazy you could cut it out of the image above and convert to .png).
Create the Main Class and First Part of Constructor
Again, this is a simplified example and as a result we are doing most of the work within the constructor of the custom Button class. We are creating MyCustomButton as a private class of the overall MyButton class and if you want to jump ahead to see that code it's at the bottom of the post.
In this first part the constructor creates a HorizontalFieldManager that will have a gradient background painted on and will act as the container to layout the buttons. Note that I've tried to document the code with lots of descriptive comments to help understand what's going on.
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
public class MyButton extends UiApplication {
public static void main(String[] args){
//main entry point
MyButton theApp = new MyButton();
theApp.enterEventDispatcher();
}
public MyButton(){
//Declare the Custom button and send it the path to the icon images
MyCustomButton mcb = new MyCustomButton("search_button_on.png", "search_button_off.png");
//Declare the normal ButtonField with name
ButtonField bf = new ButtonField("Search");
//Get the device width and height
final int width = Display.getWidth();
final int height = Display.getHeight();
//Create the mainScreen - this holds the _hfm manager
MainScreen mainScreen;
mainScreen = new MainScreen();
HorizontalFieldManager _hfm;
//Draw background gradient on this manager
_hfm = new HorizontalFieldManager() {
public void paint(Graphics g)
{
//Variables for drawing the gradient
int[] X_PTS_MAIN = { 0, width, width, 0};
int[] Y_PTS_MAIN = { 0, 0, height, height };
int[] drawColors_MAIN = { Color.BLACK, Color.BLACK, Color.DARKBLUE, Color.DARKBLUE};
try {
//Draw the gradients
g.drawShadedFilledPath(X_PTS_MAIN, Y_PTS_MAIN, null, drawColors_MAIN, null);
} catch (IllegalArgumentException iae) {
System.out.println("Bad arguments.");
}
//Call super to paint the graphics on the inherited window
super.paint(g);
}
//Sublayout is passed the width and height of the parent window and will tell the window manager
//how to layout the buttons, images, etc.
protected void sublayout(int w, int h) {
//GetFieldCount returns the number of fields attached to the instance of this manager.
//and lays out the position
if (getFieldCount() >0) {
//Get the custom button we've created and add it at coordinate (100,160)
Field customButton = getField(0);
layoutChild(customButton, w, h);
setPositionChild(customButton,100,160);
//Get the standard Blackberry Button we've created and add to coordinate(220,160)
Field standardButton = getField(1);
layoutChild(standardButton, w, h);
setPositionChild(standardButton,220,160);
}
setExtent(width,height);
}
};
...
Declare the Buttons and FieldChangeListeners
Finish off the constructor portion by declaring two custom FieldChangeListeners, setting the Listeners to the Buttons, adding the buttons to the HorizontalFieldManager, and adding the HFM to the main Screen. The FieldChangeListener will perform the action defined in the fieldChanged() method. when the user clicks the button.
Here we are triggering a Dialog alert so when the user clicks either of the buttons they'll get this:
In real code you'll do something more realistic such as push a new screen to the stack that performs whatever functionality you intend for the buttons.
FieldChangeListener customListener = new FieldChangeListener() {
public void fieldChanged(Field field, int context) {
Dialog.alert("Success!!! You clicked the Custom Button!!!");
}
};
FieldChangeListener standardListener = new FieldChangeListener() {
public void fieldChanged(Field field, int context) {
Dialog.alert("Success!!! You clicked the Standard Button!!!");
}
};
mcb.setChangeListener(customListener);
bf.setChangeListener(standardListener);
//Add our custom button to the HorizontalFieldManager
_hfm.add(mcb);
_hfm.add(bf);
//Push the HorizontalFieldManager to the stack
mainScreen.add(_hfm);
pushScreen(mainScreen);
}//End Ctor
Create the Custom Button Class
The meat of the code lies within the MyCustomButton class that extends the abstract Field class and implements the DrawStyle interface. We're taking two strings in the constructor that are the paths to the image files and using the Bitmap class method "getBitmapResource()" to import the images and set them to our private onPicture and offPicture variables.
The getPreferredHeight() and getPreferredWidth() methods return 80 since the images I've created for the custom button are 80x80px but you'll want to set these to the size of your button image. The most critical methods of the code and what they do are as follows:
-
onFocus() and onUnfocus(): these functions set the _currentPicture variable to the correct image when the user rolls over the button using the paint() method. The image is redrawn after the change by calling the invalidate() method
-
paint(): draws whatever image the _currentPicture variable is set to onthe screen.
-
fieldChangeNotify() and navigationCLick(): alerts this registered button's listener we've attached that the user has clicked the button. The listener code is subsequently triggered - the Dialog alert is shown to the user.
Refer to the attached custom Button code:
//Custom private class that creates the button and switches the image depending
//on the return value of onFocus()
private class MyCustomButton extends Field implements DrawStyle{
private Bitmap _currentPicture;
private Bitmap _onPicture; //image for "in focus"
private Bitmap _offPicture; //image for "not in focus"
private int width;
private int height;
//Ctor: pass path to on/off images you're using.
MyCustomButton (String onImage, String offImage){
super();
_offPicture = Bitmap.getBitmapResource(offImage);
_onPicture = Bitmap.getBitmapResource(onImage);
_currentPicture = _offPicture;
}
public int getPreferredHeight(){
return 80;
}
public int getPreferredWidth(){
return 80;
}
public boolean isFocusable(){
return true;
}
//Override function to switch picture
protected void onFocus(int direction){
_currentPicture = _onPicture;
invalidate();
}
//Override function to switch picture
protected void onUnfocus(){
_currentPicture = _offPicture;
invalidate();
}
protected void layout(int width, int height) {
setExtent(Math.min( width, getPreferredWidth()), Math.min( height, getPreferredHeight()));
}
//update the fieldchange
protected void fieldChangeNotify(int context) {
try {
this.getChangeListener().fieldChanged(this, context);
} catch (Exception exception) {
}
}
//Since button is rounded we need to fill corners with dark color to match
protected void paint(Graphics graphics) {
graphics.setColor(Color.BLACK);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.drawBitmap(0, 0, getWidth(), getHeight(), _currentPicture, 0, 0);
}
//Listen for navigation Click
protected boolean navigationClick(int status, int time){
fieldChangeNotify(1);
return true;
}
}
}
I think you'll agree that the custom button looks much slicker than using the stock Blackberry buttons - if done right it will give your app a great degree of elegance.