http://www.developer.com/

Back to article

Introducing a Lightweight UI Toolkit: Shake Your User Interface


September 25, 2008

Introduction

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.

The Problem with Yahoo Weather

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:

  • In the Resource Editor application explained in the first article, you can add data to your resources. You could add all city codes as a data file on the resources, but that will increase your application's size. It will be hard to collect all city codes (Yahoo shows weather information for many cities in the world).
  • You can implement those steps for getting city codes, allowing your users to browse through all countries and cities presented on Yahoo's site. The problem is that every redesign on Yahoo's pages or site may cause errors in your application. Also, parsing HTML is slower than getting code from a data file.

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!

A new parser, but for HTML

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:

  • To use that relaxed mode, you have to define a feature in this strange way:
    ...
    parser.setFeature("http://xmlpull.org/v1/doc/
                       features.html#relaxed", true);
    ...
    
    Annotate it. I didn't see many examples on Internet
  • You will search some special divs on Yahoo weather, and simply parse the anchor tags (<a>). If someday Yahoo decides to change its pages, your application will be in trouble, but I think that is better than nothing (or better than remembering city codes).
    ...
    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

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.

What is a List?

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:

  • As discussed earlier, you only can change all buttons' appearance with the Resource Editor. It forces you to uses Styles and apply them to every component you want to be different. With Lists, you define the exact appearance your components will have, so you can set a Label style on your resources file, and override its appearance for all List elements.
  • You can define different button states, but a button can contain only an Image and/or text. A List can be composed of any component—for example, Container elements—so you can have a completely different appearance when an element is selected.
  • With buttons, you have to define CommandListeners for every one. Okay, you can define only one, but you have to discriminate which button was selected; that sometimes generates ugly code. With Lists, you set only one List listener; it receives what component was selected.

Lists also allow you to organize your code better because it follows the Model-View-Controller pattern:

  • The List object itself is the controller: It accepts the user's inputs and modifies the model state.
  • The ListCellRenderer class defines how that List is going to be drawn onscreen.
  • The ListModel class is the model: It will be asked to return only visible elements, so it can be used to keep in memory only the indispensable information. It can act as a mirror of remote data, for example.

Your Location List example

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:

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);
}

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:

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;

      }
   }

ListCellRenderer defines only two methods:

  • getListCellRendererComponent: Receives all values passed to the List, and returns a LWUIT component that displays information as you want. And when you say "component," you are also referring to Containers, so you can see that a List can be composed of complex components.
  • getListFocusComponent: Returns a component that is going to be used to paint the pointer or the current focused component. You can think of it as it was a magnifying glass that you move up or down above the List: It must have some degree of transparency to see what is under it when the focus on the List moves. Once the focused item reaches the cell location, this Component is drawn under the selected item. It can be null, but defining it in your List in conjunction with setting smooth scrolling brings you a cool effect.

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:

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);
   }
}

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.

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.

...
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));
}
...

Figure 6: A menu with all stored cities is shown

Dialogs

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:

...
// 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;
   }
...

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:

...
case ABOUT_COMMAND:
   Dialog.show("About", getAboutText(), Dialog.TYPE_INFO, null,
               "Ok", null);
   break;
...

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

Progress Bar

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.

How animation is done in LWUIT

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:

  • void paint(Graphics g): Paints screen, and that is the common paint method of every component (remember lcdui Canvas?).
  • boolean animate(): Called once every frame, and tells EDT whether or not you are are going to repaint your element.

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.

An animated component

Observe your progress bar:

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();
   }
}

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.

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;
   }

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.

...
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

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.

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);
   }

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.

Conclusion

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.

Download the Code

You can download the code that accompanies this article here.

References

  1. First article of this series: http://www.developer.com/ws/article.php/3759471
  2. Yahoo weather API description: http://developer.yahoo.com/weather/
  3. Shai's Java / LWUIT Blog: http://lwuit.blogspot.com/

About the Author

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.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date