Showing posts with label field. Show all posts
Showing posts with label field. Show all posts

User Defined Buttons - Create a Custom Button From an Image

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.

Custom Searchbox



Blackberry application development can be challenging due to the tersely documented and sometimes difficult to understand APIs, especially when compared to iPhone application development. Apple tends to provide thorough and complete resources including working sample applications for reference while much of Blackberry's developer resources are dated or simply riddled with errors. Of course the Blackberry Developer Zone contains many resources particularly the developer message boards that you will definitely be referencing regularly as you scratch and claw your way towards mastery of the API.

Background


Recently, a project required developing a custom Search box for an application that scrolled as the user typed input in since the search term might be longer than the extent of the on-screen search box. I discovered that the default Blackberry components don't have this functionality without customization. The first step towards developing the custom searchbox was creating the vector components since our custom searchbox will have a button for dropdown functionality to modfy the type of search and an input area for user search terms. A half-hour's investment in Inkscape yielded a polished searchbox perfect for our purposes. I can't attach the picture but imagine the searchbox in Google's iPhone application and you'll have a good idea of what I came up with.

Creating custom components will involve some experience with a vector drawing tool such as Inkscape or Adobe's Illustrator to create custom designs. The focus of this blog is on the programming side of things however there are numerous vector drawing tutorials residing on the web for reference.


Let's Get to the Code


In order to provide scrolling searchbox functionality, we have to extend the existing HorizontalFieldManager, create an internal HorizontalFieldManager and include a BasicEditField to provide an area for user input, all while matching the dimension of our searchbox. Below is the code for the constructor of the scrolling "text" box demonstrating the creation of these HFM's and editfield:


public class ScrollingSearchBox extends HorizontalFieldManager{

private int managerWidth;
private int managerHeight;
private BasicEditField editField;
private Font font;


public ScrollingTextBox(int width, int height, int fontSize, String fontName){
super(Manager.NO_HORIZONTAL_SCROLL);
managerWidth = width;
managerHeight = height;

HorizontalFieldManager hfm = new HorizontalFieldManager(Manager.HORIZONTAL_SCROLL);

editField = new BasicEditField(){
public void paint(Graphics g) {

super.paint(g);
}

};

try {
FontFamily fFam = FontFamily.forName(fontName);
font = fFam.getFont(Font.ITALIC, fontSize);
} catch (ClassNotFoundException ex) {

}

setFont(font);
setText("Enter search term");
hfm.add(editField);
add(hfm);
}



Note the partial class definition above creates three private integer variables that will hold the parent's width, height, and a BasicEditField for user input. The constructor is calling the super class constructor, in this case HorixontalFieldManager, and passes it the flag value "Manager.NO_HORIZONTAL_SCROLL" which keeps the private variables editField(where the user inputs text) and hfm(container for editField).

You must create a manager for the BasicEditField, in this case the HorizontalFieldManager, and pass it the flag value "Manager.HORIZONTAL_SCROLL" thus enabling horizontal scrolling ability within the BasicEditField. Finally, we set the default text of the field, add the BasicEditField to the HorizontalFieldManger, and then add the HorizontalFieldManager to the ScrollingTextBox. Using the FontFamily getFont() functionality we'll set the size and the font type via the con

Override Sublayout Method


In order to ensure that you can position the searchbox where you'd like you must override the sublayout function. Sublayout defines how the fields controlled by are positioned and is called by the delegate manager. Refer to the code below:


public void sublayout(int width, int height) {
if (managerWidth == 0) {
managerWidth = width;
}
if (managerHeight == 0) {
managerHeight = height;
}
super.sublayout(managerWidth, managerHeight);
setExtent(managerWidth,managerHeight);

}


The local private variables are checked to ensure they are not set to 0 and if so they are set to the values passed in by the managing class. The call to super.sublayout tells the parent to configure itself to the size of the child. SetExtent is a key function that tells the searchbox where it should end based on the values passed into the constructor when it is created. The values that are passed will be dependent on the width and height of the graphic image you've created.

Override the keyDown Method


The final step in creating the scrolling searchbox is to override the keyDown method such that when the user starts typing in the search terms the default text indicating the function of the box, "Enter search term" will be erased. Our version of keyDown() will intercept the user keystroke, compare if the text still equals "Enter search term" and if so it will clear the input box before the user input is captured. Refer to the following code:

protected boolean keyDown(int keycode, int time) {
if(editField.getText().equals("Enter search term")){
editField.setText("");
}

return super.keyDown(keycode, time);
}

Overall, the functionality is not too difficult - by simply extending the built-in API we are able to get some powerful functionality that permits us to build a custom scrolling searchbox.
top