http://www.developer.com/java/j2me/article.php/3773866/Introducing-a-Lightweight-UI-Toolkit-Shake-Your-User-Interface.htm
You saw in the first article of this series [1] the basic structure of a simple LWUIT application, but today it is not sufficient just to have a clear UI. From modern web applications (full of JavaScript tricks) to desktop applications, every 21st century application shows dynamic user interfaces that react with smooth transitions, animations, and other design tips. It's time to add cool stuff to your weather application. You will beautify the application you started in the first article: I have changed some of the images and default theme from the first article's resources file, and you will play with all the animating effects that LWUIT supports. Without any effort from programmers, this library improves your application's look and feel, adding movement to some of your components. If your target devices don't support those effects well, or they have very limited computing capabilities, you can disable all effects by using the Display.setLightMode(true) method, which shows fewer visual effects/lighter versions of these visual effects to work properly on low-end devices. But, you also have to resolve some problems with your first application. You will discover other strengths of LWUIT on your way. In the first article, your application showed weather forecasts only for one city (Bilbao was the default). I told you that you need to know the location ID of the target city to query Yahoo's service. Unfortunately, the only way to know the code of a world city is by browsing to http://weather.yahoo.com/; the ID is in the URL for the forecast page for the wanted city, which is the way they recommend [2]. As you can imagine, it's awful advice telling you to use a browser to introduce their city code. This is not what a user expects to encounter in a complete weather application. You have several ways of resolving that: You will take the second approach: If the weather city codes change, your application still will run without problems, and I don't think that Yahoo is going to change its page design in the near future. Another desired feature that you didn't add in first article is the capability of asking weather information for many cities. Of course, you simply can let users browse the countries list every time they want to change the city, but it will be more useful to store the last consulted city codes as bookmarks. Now, it's time to get to work! You introduced a new parser class, KXmlHtmlParser, to read Yahoo's HTML pages; KXML supports a "relaxed" mode to parse XML documents that aren't standard as HTML pages. Some points about it: I created a new class, PlacesDisplay, that's very similar to the existing WeatherDisplay. It will show you all countries and cities and allows browsing though those lists. Yahoo pages first show world zones; secondly, it shows countries; and finally, it shows city codes. Some of the countries have many cities that are divided on alphabetical order pages. Figure 1: First page on the Yahoo site Figure 2: Second page showing states Figure 3: Last page where links have city codes The PlacesDisplay class works in a loop, when some element is selected in one of the first lists (regions, countries) or in the alphabetical menu; it downloads and parses all information of that element's link page. You will know that you get a city code because all city codes end with a digit. And, you can implement all this behavior by using the features provided by one of the most interesting components of LWUIT: Lists. A List is simply a group of elements displayed in a single column. Explained in that way,- it seems like a very basic feature, but the key to that definition is the word "elements." A List is composed of anything you can imagine, from Containers to simple Labels, and moreover, LWUIT offers a way to define how that list is going to be rendered, a MVC architecture, and a very elegant way of working with selection events. You can create menus by using buttons, as you did in the first article of this series, but if you use Lists, you encounter some benefits: Lists also allow you to organize your code better because it follows the Model-View-Controller pattern: To create a List, you can pass to the constructor a Vector, an array of objects, or a ListModel object. In the following listing, you create and initialize your location list. Notice that you only create a horizontal list of letters when the parsed page has those links: It is very important that you set the containing form as non-scrollable: List implements its own scroll and your application will behave strangely if you don't do so. In your application, you pass a Vector of YWRecords to the List, but how is it going to be rendered a YWRecord? They aren't LWUIT components, so you have to tell to the List how to show them. You defined a LocationRenderer class that implements ListInterface: ListCellRenderer defines only two methods: In LwuitWeather, you have implemented a common paradigm on LWUIT: You create a component that also implements the ListCellRenderer interface, so it can be returned from those methods. This way, you avoid creating a new Component each time getListCellRendererComponent is called. Remember that we are developing to devices with big memory constraints. Notice that getListCellRendererComponent also receives all the List as a parameter: With a little coding, it will be easy to create a List that focuses elements and alters the appearance of near components, like in a Fish Eye Menu. I will let that be an exercise for readers to implement it. Finally, you should react to selections of items on that list. To achieve that, PlacesDisplay will be your actionListener, whose action performing method is shown next: Figure 4: Location List Figure 5: Main Menu also is a List Basically, the PlacesDisplay class gets a List of Locations and shows them on screen, until you reach a city (you read the location link and test whether the yahooCode is a digit). It allows you to navigate forward, but to maintain a example clean, it doesn't allow backward navigation. Finally, it simply stores that information on RMS and shows weather info for that city. to do that, I have created some methods on the WeatherMidlet class and I have improved WeatherDisplay to show all cities stored on RMS using Menus. I took advantage of another feature of LWUIT: If you add more than two commands to a form, a menu command is generated automatically; this opens a dialog with your options. LWUIT also animates that dialog window with a transition; I will explain that later in this article. In the following code, you will see that you only add commands; LWUIT does all the hard work. Figure 6: A menu with all stored cities is shown As you've seen, a menu is a little window that is painted in front of your screen. It is really implemented as some special Dialog window. As with desktop applications, you can show a dialog window to warn, inform, or obtain some input from users. By default, all dialogs are modal; they intercept all input and block the background form/window, which is the common scenario when showing a dialog. A mode-less dialog window is planned for future releases of LWUIT. In your application, if the current city is not selected (first time users have to select the city to show weather forecast) you should warn users about it. I added the following code to WeatherDisplay: And, the About dialog window on WeatherMidlet could be more pleasant than your first ugly Form. it is better; the code is smaller than on your first approach: The LWUIT API offers some static methods to create a Dialog, as you see in those examples. If you don't add a "Cancel" text, only a OK command will be added to the dialog. There are some methods that allow developers to add different Commands to dialog window; these can be useful if the Dialog is going to affect execution flow. Figure 7: About Dialog Window As every JavaME developer knows, most of the time it is not sufficient to test your application in emulators; you have to go to real devices, not only to test possible implementation bugs, but also to test on real execution conditions. In your case, network latencies may greatly affect your application's usability: At every moment, you have to inform your users about what your application is doing, so you are going to add some progress bars when your application is connecting to the Internet to download information. LWUIT doesn't offer a generic progress bar widget, but creating one is very easy. In your application, downloading is done at the beginning of the forms, when you need all information to be showed to your users, so while you are downloading all information, you can show a progress indicator to inform users that you are doing "something" and that they have to wait. Because you don't know how long your download is going to last, you need is an infinite progress indicator (as some Ajax applications use). So, what you could do is create a simple Label and animate it. LWUIT has a single main thread where all events and paints are dispatched. It is called EDT (inspired by the Event Dispatch Thread in Swing and AWT). LWUIT offers an interface, Animation interface, that all components (Forms, Containers, Buttons...) implement with two methods: To use animation, you have to register your component as animated in your Form by using the registerAnimated method. Animation frames are set on the Display class, the class that controls the EDT, in setFramerate(int fps). So, if you use the default 10 FPS, in one second EDT will call animate() of every registered component of the current form 10 times. Basically, this is the same common technique some people use when using a Timer class that is called every X milliseconds and repaints your screen, but in a more organized, easier, and standardized way. Observe your progress bar: It is a simple label in which you have overridden the animate component: Every 50 milliseconds, it changes the label's image by a 90 degree rotated image, and returns true to the repaint component on screen. You can change REPAINT_TIME to modify the animation speed without modifying the entire application's frame rate. You also overrode getHeight and getWidth. If you don't do it, the animation isn't painted, because, as you will see, you call setPreferredSize of your new component with those values. To show animation on screen, I have created a Form that will be called every time you are going to access the Internet, to provide a visual hint to the user. You create a method on WeatherMidlet because it can be accessed from everywhere. It is shown in the following code and returns an initialized Form. You have to register where the animated components are placed on the Form. To place a "Requesting..." label on your progress bar approximately in the middle of the screen, you have set their top margins using a relative positioning dependent from the device's display height. The following is the result on screen: Figure 8: Downloading the Form Every time you have to go to the Yahoo weather site to download information, you do the same thing: Get the downloading Form, and then create a new thread that downloads requested information, parses it, and creates a new Form that finally is shown. So, while EDT is painting your downloading form, showing it on screen, you are creating the new screen to be shown. In addition to the capability of animating every component, you also have the possibility of animating when you are leaving and entering a Form. With those transition effects, is very easy to create an overall 3D sensation, as you did in your Midlet. You are changing the direction of a 3D cube's rotation to simulate a three face prism: The Location Form is placed on the right face, and the Weather Form on the left. Take care that, if you want to use Transition3D class, your target devices require M3G (JSR 184) support, which is very common today. In this series of two articles, you have made a basic application that serves as introduction to the LWUIT library. You have seen how components are placed on containers, how you include resources inside an application, and some of the effects you can use in your own applications. I just want to recommend the excellent blog of one of the developers of LWUIT, Shai Almog [3], where you will find a lot of advanced tips and where new features developed on releases of LWUIT are explained in detail. You can download the code that accompanies this article here. Ibon Urrutia is a Spanish IT Engineer with wide experience in JavaME applications targeted to be used in many devices. Nowadays, he works for MDTEC http://www.mdtec.net, participating in a complete JavaME framework with advanced user interface functionalities, TagsMETM http://www.tagsme.com. He also is member of Netbeans Dream Team http://wiki.netbeans.org/NetBeansDreamTeam.
Introducing a Lightweight UI Toolkit: Shake Your User Interface
September 25, 2008
Introduction
The Problem with Yahoo Weather
A new parser, but for HTML
...
parser.setFeature("http://xmlpull.org/v1/doc/
features.html#relaxed", true);
...
Annotate it. I didn't see many examples on Internet
...
if ("yw-regionalnav".equals(parser.getAttributeValue(null,
"id"))) {
navLetters = parseLocations(); }
else if ("yw-regionalloc".equals
(parser.getAttributeValue(null, "id")) ||
"yw-browseloc".equals(parser.getAttributeValue(null,
"id"))) {
records = parseLocations();
...
Browsing through Countries and Cities: Lists



What is a List?
Your Location List example
private void initList(ParsedWeatherPage page, Form form) {
List locList = new List(page.locations);
locList.setListCellRenderer(new LocationRenderer());
locList.setSmoothScrolling(true);
locList.setFixedSelection(List.FIXED_CENTER);
locList.addActionListener(this);
Container locContainer = new Container(new BorderLayout());
locContainer.addComponent(BorderLayout.CENTER, locList);
if (page.navigationLetters != null) {
List navList = new List(page.navigationLetters);
navList.setOrientation(List.HORIZONTAL);
navList.setBorderGap(1);
navList.setListCellRenderer(new LocationRenderer());
navList.setSmoothScrolling(true);
navList.setFixedSelection(List.FIXED_CENTER);
navList.setSelectedIndex(selectedIndex);
navList.addActionListener(this);
locContainer.addComponent(BorderLayout.NORTH, navList);
}
form.setScrollable(false);
form.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
form.addComponent(locContainer);
}
class LocationRenderer extends Label implements ListCellRenderer {
public LocationRenderer() {
getStyle().setPadding(5,5,5,5);
}
public Component getListCellRendererComponent(List list,
Object value, int index, boolean isSelected)
final YWRecord record = (YWRecord) value;
setText(record.name);
if (isSelected) {
setFocus(true);
getStyle().setBgImage(resources.getImage("labelback.png"));
getStyle().setBgTransparency(100);
getStyle().setFont(resources.getFont("bold"));
} else {
setFocus(false);
getStyle().setBgImage(null);
getStyle().setBgTransparency(0);
getStyle().setFont(resources.getFont("small"));
}
return this;
public Component getListFocusComponent(List arg0) {
setText("");
setFocus(true);
getStyle().setBgTransparency(100);
getStyle().setFont(resources.getFont("big"));
return this;
}
}
public void actionPerformed(ActionEvent evt) {
List selectionList = (List) evt.getSource();
...
YWRecord newSelected = (YWRecord)
(selectionList).getSelectedItem();
// We identify when finishes our browsing when we have got
// a complete Yahoo code
if (Character.isDigit(newSelected.yahooCode.
charAt(newSelected.yahooCode.length() - 1))) {
...
midlet.addCityCode(newSelected);
midlet.setCurrentCity(newSelected);
midlet.showWeatherForecast();
} else {
...
createLocationForm(yahooHome + newSelected.url);
}
}


Menus
...
Vector citiesStored = actList.getCities();
for (int i = 0; i < citiesStored.size(); i++) {
YWRecord city = (YWRecord) citiesStored.elementAt(i);
weatherForm.addCommand(new Command(city.name, COMMAND_CITY));
}
...

Dialogs
...
// Current City has not been set
if (currentCity.yahooCode == null) {
Dialog.show("Set city to display info",
"Go to set place and select " +
"cities to show weather information for them", "Ok", null);
return;
}
...
...
case ABOUT_COMMAND:
Dialog.show("About", getAboutText(), Dialog.TYPE_INFO, null,
"Ok", null);
break;
...

Progress Bar
How animation is done in LWUIT
An animated component
public class ProgressBar extends Label {
private static final int REPAINT_TIME = 50;
private Image progress;
private Image[] animation = new Image[4];
public ProgressBar(Resources res) {
progress = res.getImage("progress.png");
animation[0] = progress;
animation[1] = progress.rotate(90);
animation[2] = progress.rotate(180);
animation[3] = progress.rotate(270);
}
private long lastInvoke;
private int currentImage = 0;
public boolean animate() {
long current = System.currentTimeMillis();
if (current - lastInvoke > REPAINT_TIME) {
lastInvoke = current;
currentImage++;
if (currentImage == animation.length) {
currentImage = 0;
}
setIcon(animation[currentImage]);
setAlignment(Component.CENTER);
return true;
}
return false;
}
public int getHeight() {
return progress.getHeight();
}
public int getWidth() {
return Display.getInstance().getDisplayWidth();
}
}
public Form getDownloadingPane() {
Form downForm = new Form();
Container cont = new Container();
cont.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
ProgressBar bar = new ProgressBar(res);
bar.setPreferredSize(new Dimension(bar.getWidth(),
bar.getHeight()));
bar.setBorderPainted(false);
bar.getStyle().setBgTransparency(0);
bar.getStyle().setMargin(Component.TOP,
Display.getInstance().getDisplayHeight()/10);
Label label = new Label("Requesting...");
label.getStyle().setFont(res.getFont("bold"));
label.getStyle().setMargin(Component.TOP,
Display.getInstance().getDisplayHeight()/3);
label.setAlignment(Component.CENTER);
cont.addComponent(label);
cont.addComponent(bar);
downForm.addComponent(cont);
downForm.registerAnimated(bar);
return downForm;
}

...
weatherMid.getDownloadingPane().show();
new Thread() {
public void run() {
YahooWeather weatherItem = getWeatherFromYahoo();
final Form weatherForm = new Form();
weatherForm.addCommand(backCommand);
weatherForm.setCommandListener(actList);
weatherForm.setBackCommand(backCommand);
weatherForm.setSmoothScrolling(true);
Vector citiesStored = actList.getCities();
YWRecord lastRecord = (YWRecord) citiesStored.elementAt(0);
for (int i = 0; i < citiesStored.size(); i++) {
YWRecord city = (YWRecord) citiesStored.elementAt(i);
weatherForm.addCommand(new Command(city.name,
COMMAND_CITY));
}
initializeData(weatherItem, weatherForm);
weatherForm.show();
}
}.start();
...
Transitions
private void setInRotation(boolean right) {
in = Transition3D.createCube(TRANSITION_RUN_SPEED, right);
mainMenu.setTransitionInAnimator(in);
}
private void setOutRotation(boolean right) {
out = Transition3D.createCube(TRANSITION_RUN_SPEED, right);
mainMenu.setTransitionOutAnimator(out);
}
Conclusion
Download the Code
References
About the Author