The ImageLoader
class handles the loading of
images. To save memory, loaded images are cached.
The loadImage
methods load an image and store it to the cache,
so that subsequent calls can retrieve the image from the cache and
no duplicate images need to be created.
/** * Loads an image from resources and returns it. * Caches all loaded images in hopes of saving some memory. * @param imagePath * @return loaded image * @throws IOException */ public final Image loadImage(final String imagePath, final Hashtable cache) throws IOException { Image image = getImageFromCache(imagePath, cache); if (image == null) { InputStream in = this.getClass().getResourceAsStream(imagePath); if (in == null) { throw new IOException("Image not found."); } image = Image.createImage(in); cacheImage(imagePath, image, cache, false); } return image; } /** * Loads an image from resources or network. * Caches all loaded images. * @param url * @param defaultImage a default image which is returned while the image is * loaded from network * @param listener listener which is notified when the image is loaded from * network * @return the image or while the image is loaded from the network the * default image */ public final Image loadImage(final String url, final Image defaultImage, final Listener listener, final Hashtable cache) { try { return loadImage(url, cache); } catch (IOException e) { cacheImage(url, defaultImage, cache, false); new ImageOperation(new ImageOperation.Listener() { public void imageReceived(String url, byte[] data) { Image image = defaultImage; try { image = Image.createImage(data, 0, data.length); } catch (IllegalArgumentException e) { } catch (NullPointerException e) { } cacheImage(url, image, cache, true); listener.imageLoaded(image); } }, url).start(); } return defaultImage; }
The cacheImage
method caches
an image to the specified cache and to a static imageCache
. The imageCache
uses WeakReferences
, so all available free memory is used for caching images even when
the specified cache is cleaned. The getImageFromCache
method retrieves an image from the specified cache.
private void cacheImage(String key, Image image, Hashtable cache, boolean toWeakCache) { if (cache != null) { cache.put(key, image); } if (toWeakCache) { imageCache.put(key, new WeakReference(image)); } } private Image getImageFromCache(String key, Hashtable cache) { Image image = null; if (cache != null) { image = (Image) cache.get(key); } if (image == null) { WeakReference imageRef = (WeakReference) imageCache.get(key); image = imageRef == null ? null : (Image) imageRef.get(); if (image != null && cache != null) { cache.put(key, image); } } if (image == null) { imageCache.remove(key); } return image; }
The loadMapMarker
method creates
a map marker image. This method also uses the cache to avoid duplicate
images. The marker is created by drawing a label over a background
image. By default, images are immutable in Java ME. The DirectUtils
class provides a method for creating mutable transparent images,
but the method does not work correctly on all devices. The multiplyImages
method takes two images and combines them
by multiplying each pixel in the first image with a corresponding
pixel in the second image. The transparency of the first image is
preserved.
public final Image loadMapMarker(final String id, final String imagePath, final Hashtable cache) throws IOException { Image background = loadImage(imagePath, cache); String url = "" + id + imagePath; Image image = null; if (cache != null) { image = (Image) cache.get(url); } if (image == null) { int w = background.getWidth(); int h = background.getHeight(); image = Image.createImage(w, h); Graphics g = image.getGraphics(); g.setColor(0xffffff); g.fillRect(0, 0, w, h); g.setColor(Visual.MAP_MARKER_COLOR); g.setFont(Visual.MAP_MARKER_FONT); g.drawString(id, w / 2, h / 2 + g.getFont().getHeight() / 2 - (Main. isS60Phone() ? 3 : 5), Graphics.BOTTOM | Graphics.HCENTER); image = multiplyImages(background, image); if (cache != null) { cache.put(url, image); } } return image; } private static Image multiplyImages(Image img1, Image img2) { if (img1.getWidth() != img2.getWidth() || img1.getHeight() != img2.getHeight()) { throw new IllegalArgumentException( "Sizes of the images must be same"); } int[] rawImg1 = new int[img1.getHeight() * img1.getWidth()]; img1.getRGB(rawImg1, 0, img1.getWidth(), 0, 0, img1.getWidth(), img1.getHeight()); int[] rawImg2 = new int[img2.getHeight() * img2.getWidth()]; img2.getRGB(rawImg2, 0, img2.getWidth(), 0, 0, img2.getWidth(), img2. getHeight()); int mrgb, mr, mg, mb, a, r, g, b; for (int i = 0, l = rawImg1.length; i < l; i++) { mrgb = rawImg2[i] & 0xffffff; if (mrgb < 0xffffff) { mr = mrgb >>> 16; mg = (mrgb & 0xff00) >>> 8; mb = mrgb & 0xff; a = rawImg1[i] & 0xff000000; r = (((rawImg1[i] & 0xff0000) * mr) / 0xff) & 0xff0000; g = (((rawImg1[i] & 0xff00) * mg) / 0xff) & 0xff00; b = (((rawImg1[i] & 0xff) * mb) / 0xff) & 0xff; rawImg1[i] = a | r | g | b; } } return Image.createRGBImage(rawImg1, img1.getWidth(), img1.getHeight(), true); }
The TextWrapper
class provides the wrapTextToWidth
method, which splits a string to multiple lines.
public static Vector wrapTextToWidth(String text, int wrapWidth, Font font) { if (wrapWidth < 20) { wrapWidth = 240; } Vector lines = new Vector(); int start = 0; int position = 0; int length = text.length(); while (position < length - 1) { start = position; int lastBreak = -1; int i = position; for (; i < length && font.stringWidth(text.substring(position, i)) <= wrapWidth; i++) { if (text.charAt(i) == ' ') { lastBreak = i; } else if (text.charAt(i) == '\n') { lastBreak = i; break; } } if (i == length) { position = i; } else if (lastBreak <= position) { position = i; } else { position = lastBreak; } lines.addElement(text.substring(start, position)); if (position == lastBreak) { position++; } } return lines; }
The UIUtils
class provides
methods for customizing those parts of the UI that function differently
on Series 40 full touch devices. The FtUIUtils
class
extends the UIUtils
and provides the functionality
specific to Series 40 full touch devices.
The UIUtils.getInstance
method returns an FtUIUtils
object if the MIDlet
is run on a Series 40 full touch device.
private static UIUtils getInstance() { if (instance == null) { try { Class.forName("com.nokia.mid.ui.IconCommand"); Class.forName("com.nokia.mid.ui.CategoryBar"); Class.forName("com.nokia.mid.ui.VirtualKeyboard"); Class clazz = Class.forName("com.nokia.example.attractions." + "utils.FtUIUtils"); instance = (UIUtils) clazz.newInstance(); } catch (Exception e) { instance = new UIUtils(); } } return instance; }
The creation of new Command
objects
is implemented in the UIUtils
class, so that IconCommand
objects can be used only when the device supports
them.
The UIUtils
class contains a static method
for creating Commands
. The UIUtils.createCommand
method creates a new Command
using the newCommand
method, which the FtUIUtils
class
overrides to return an IconCommand
object instead.
public static Command createCommand(int command) { return getInstance().newCommand(command); }
The UIUtils.newCommand
method
returns basic Command
objects.
protected Command newCommand(int command) { Command result; switch (command) { case EXIT: result = new Command("Exit", Command.EXIT, 1); break; case BACK: result = new Command("Back", Command.BACK, 1); break; case MAP: result = new Command("Map", Command.SCREEN, 1); break; case GUIDES: result = new Command("Guides", Command.SCREEN, 2); break; case BUY_GUIDES: result = new Command("Buy more", Command.SCREEN, 2); break; case ABOUT: result = new Command("About", Command.HELP, 3); break; case HELP: result = new Command("Help", Command.HELP, 3); break; case POLICY: result = new Command("Policy", Command.HELP, 3); break; case SETTINGS: result = new Command("Settings", Command.HELP, 3); break; case OPEN: result = new Command("Open", Command.OK, 1); break; case BUY: result = new Command("Buy", Command.OK, 1); break; case ACCEPT: result = new Command("Accept", Command.OK, 1); break; case CANCEL: result = new Command("Cancel", Command.CANCEL, 1); break; case CHANGE: result = new Command("Change", Command.OK, 1); break; case SAVE: result = new Command("Save", Command.OK, 2); break; default: result = null; break; } return result; }
By comparison, the FtUIUtils.newCommand
method, which overrides the UIUtils.newCommand
method,
returns IconCommand
objects when needed.
protected Command newCommand(int command) { Command result; switch (command) { case EXIT: result = new IconCommand("Exit", Command.EXIT, 1, IconCommand.ICON_BACK); break; case BACK: result = new IconCommand("Back", Command.BACK, 1, IconCommand.ICON_BACK); break; case MAP: result = newCommand("Map", "/icons/map.png", Command.SCREEN, 1); break; case GUIDES: result = new Command("Guides", Command.SCREEN, 2); break; case BUY_GUIDES: result = newCommand("Buy more", "/icons/ovi_store.png", Command.SCREEN, 2); break; case ABOUT: result = new Command("About", Command.HELP, 3); break; case HELP: result = new Command("Help", Command.HELP, 3); break; case POLICY: result = new Command("Policy", Command.HELP, 3); break; case SETTINGS: result = new IconCommand("Settings", Command.HELP, 3, IconCommand.ICON_OPTIONS); break; case OPEN: result = new Command("Open", Command.OK, 1); break; case BUY: result = new Command("Buy", Command.OK, 1); break; case ACCEPT: result = newCommand("Accept", "/icons/ok.png", Command.OK, 1); break; case CANCEL: result = newCommand("Cancel", "/icons/cancel.png", Command.BACK, 1); break; case CHANGE: result = new Command("Change", Command.OK, 1); break; case SAVE: result = newCommand("Save", "/icons/save.png", Command.OK, 2); break; default: result = null; break; } return result; }
The UIUtils.init
method is called
to initialize the UI when the MIDlet is started.
public static void init() { getInstance().initialize(); }
The UIUtils.initialize
method
does nothing. The FtUIUtils.initialize
method, which
overrides the UIUtils.initialize
method, hides the
default open keypad command from the options menu on a Series 40 full
touch device.
protected void initialize() { super.initialize(); VirtualKeyboard.hideOpenKeypadCommand(true); }
On a Series 40 full touch device, the back button
is an overlay. This means that some padding needs to be added to scrollable
views so that the back button does not hide any content underneath
it. The UIUtils.bottomPadding
method calls the getBottomPadding
method of the UIUtils
or FtUIUtils
instance to return how much padding is needed.
public static int bottomPadding() { return getInstance().getBottomPadding(); }
The UIUtils.getBottomPadding
method
returns 0
as padding is only needed on Series 40
full touch devices. The FtUIUtils.getBottomPadding
method, which overrides the UIUtils.getBottomPadding
method, returns the height of the back button on a Series 40 full
touch device.
protected int getBottomPadding() { return CategoryBar.getBestImageHeight(CategoryBar.IMAGE_TYPE_BACKGROUND) - 4; }