The ViewMaster
class handles the MIDlet UI views.
It extends the GameCanvas
class and performs all
drawing operations in a separate thread to keep the animations smooth.
The class also handles the logic for switching between views.
The class implements the singleton pattern, and the getInstance
method provides access to the singleton object.
public static ViewMaster getInstance() { if (instance == null) { instance = new ViewMaster(); instance.initialize(); } return instance; }
After the singleton instance is created, the MIDlet
initialises all the views using the initialize
method.
private void initialize() { this.setCommandListener(this); try { defaultThumbnailIcon = ImageLoader.getInstance().loadImage( "/thumbnail.png", null); } catch (IOException e) { } try { Image i = ImageLoader.getInstance().loadImage("/loader_content.png", null); defaultLoaderSprite = new Sprite(i, i.getWidth() / 24, i.getHeight()); } catch (IOException e) { } attractionsView = new AttractionsView(); detailsView = new DetailsView(); mapView = new MapView(); guidesView = new GuidesView(); aboutView = new AboutView(); helpView = new HelpView(); try { Class.forName("com.nokia.mid.payment.IAPClientPaymentListener"); Class c = Class.forName("com.nokia.example.attractions.views." + "BuyGuidesView"); buyGuidesView = (BaseView) (c.newInstance()); } catch (Exception e) { buyGuidesView = null; } }
The thread for drawing is started in the showNotify
method.
protected void showNotify() { ... draw(); final Graphics g = getGraphics(); new Thread() { public void run() { while(!hidden) { if (refreshScreen && canPaint) { refreshScreen = false; if(activeView != null) { activeView.draw(g); flushGraphics(); } } drawLock.sleep(50); } } }.start(); }
The platform calls the paint
method
when the GameCanvas
is shown and ready to be painted
on. When the paint
method is called, the MIDlet sets
the canPaint
flag in the ViewMaster
.
public final void paint(Graphics g) { canPaint = true; forceDraw(); }
The draw
method sets a flag indicating
that the screen must be refreshed.
public final void draw() { refreshScreen = true; }
The forceDraw
method sets a flag
indicating that the screen must be refreshed and wakes up the drawing
thread.
public final void forceDraw() { draw(); drawLock.wakeup(); }
The setView
method adds a view
to the top of the view stack, activates the view, and configures the
softkey buttons for the view.
private void setView(final BaseView view) { Main.getInstance().callSerially(new Runnable() { public void run() { if (activeView != null) { if (activeView == view) { // Trying to activate a view that's already active. return; } else { activeView.deactivate(); } } activeView = view; synchronized (viewStack) { // Remove the view from viewstack. viewStack.removeElement(activeView); // Push the view into viewstack. viewStack.addElement(activeView); } activeView.activate(); forceDraw(); } }); }
The setPreviousView
method activates
the previous view from the view stack.
public final void setPreviousView() { BaseView view = null; synchronized (viewStack) { if (viewStack.size() >= 2) { // First remove and forget the current view. viewStack.removeElement(viewStack.lastElement()); // Get the 2nd to last, which we want to activate. view = (BaseView) viewStack.lastElement(); } } // Activate the view. if (view != null) { setView(view); } }
The List class is a custom UI component
for the views. It provides a cursor for focus and a scrollbar for
visualising the number of items in the list. Only those items which
intersect the displayable area are drawn. Drawing the list items is
done by an implementation of the List.Drawer
interface.
The selection event is returned using the Listener interface.
The static getList
method creates a new list
component. If the device supports FrameAnimator
and
Gesture APIs, a GestureList
object is returned; if
not, a List object is returned.
public static List getList(ViewMaster view, Drawer drawer, Listener listener) { Listener listener) { List list = null; try { Class.forName("com.nokia.mid.ui.frameanimator.FrameAnimator"); Class.forName("com.nokia.mid.ui.gestures." + "GestureRegistrationManager"); Class c = Class.forName("com.nokia.example.attractions.views.list." + "GestureList"); list = (List) c.newInstance(); } catch (Exception e) { list = Main.isS60Phone() ? new S60List() : new List(); } list.drawer = drawer; list.listener = listener; return list; }
The GestureList
class extends the custom List
class with support for touch events and kinetic scrolling.
When the list is enabled, the class registers the frame animator and the zone for receiving gestures.
public final void enable() { if (enabled) { return; } enabled = true; super.enable(); // Use default values for maxFps an maxPps // (zero param means that default is used). final short maxFps = 0; final short maxPps = 0; if (animator.register((short) 0, (short) 0, maxFps, maxPps, this) != true) { throw new RuntimeException("FrameAnimator.register() failed!"); } if (GestureRegistrationManager.register(view, zone) != true) { throw new RuntimeException( "GestureRegistrationManager.register() failed!"); } GestureRegistrationManager.setListener(view, this); }
When the list is disabled, the class unregisters the gesture zone and the frame animator.
public final void disable() { if (!enabled) { return; } enabled = false; GestureRegistrationManager.unregister(view, zone); super.disable(); animator.unregister(); }
All UI views extend the BaseView
class.
When a view is activated, the ViewMaster
calls the activate
method. In this method, views refresh the data
that is drawn, activate any touch event listeners, and so on.
public void activate() { active = true; logEvent("activated"); }
When a view is hidden, the ViewMaster
calls the deactivate
method. In this method, views
stop their animations, deactivate touch event listeners, and so on.
public void deactivate() { viewMaster.removeCommand(exitCmd); viewMaster.removeCommand(backCmd); viewMaster.removeCommand(mapCmd); viewMaster.removeCommand(guidesCmd); viewMaster.removeCommand(buyGuidesCmd); viewMaster.removeCommand(aboutCmd); viewMaster.removeCommand(helpCmd); active = false; }
The draw method draws the view to the screen. Every subclass overrides this method with their own dedicated implementation.
public void draw(final Graphics g) { }
The DetailsView
shows the details of an attraction.
The view can contain a lot of text, and drawing text can be a slow
operation. To achieve smooth scrolling animations, the MIDlet draws
the text to a buffer. The MIDlet updates the buffer only if the text
changes or the screen dimensions change.
The drawBuffer
method draws the details of an attraction to the buffer.
protected final void drawBuffer(Graphics g) { int w = getContentWidth(); int x0 = 0; int y0 = 0; g.setFont(Visual.SMALL_BOLD_FONT); g.setColor(Visual.LIST_SECONDARY_COLOR); g.drawString(attraction.getStreet(), x0, y0, Graphics.TOP | Graphics.LEFT); if (attraction.getDistance() != null) { g.drawString(attraction.getDistance(), w, y0, Graphics.TOP | Graphics.RIGHT); } y0 += Visual.SMALL_BOLD_FONT.getHeight() + 4; g.setFont(Visual.SMALL_FONT); g.setColor(Visual.LIST_PRIMARY_COLOR); for (int i = 0; i < lines.size(); i++) { g.drawString((String) lines.elementAt(i), x0, y0, Graphics.TOP | Graphics.LEFT); y0 += Visual.SMALL_FONT.getHeight(); } }