Listening to the Model

The Android UI framework uses the handler installation pattern pervasively. Although our earlier examples were all Buttons, many other Android widgets define listeners. The View class defines several events and listeners that are ubiquitous, and which we will explore in further detail later in this chapter. Other classes, however, define other specialized types of events and provide handlers for those events that are meaningful only for those classes. This is a standard idiom that allows clients to customize the behavior of a widget without having to subclass it.

This pattern (called the Callback Pattern) is also an excellent way for your program to handle its own external, asynchronous actions. Whether responding to a change in state on a remote server or an update from a location-based service, your application can define its own events and listeners to allow its clients to react.

The examples so far have been elementary and have cut several corners. Although they demonstrate connecting a View and a Controller, they have not had real Models. (Example 10-4 actually used a String owned by the implementation of EditText as a Model.) In order to proceed, we're going to have to take a brief detour to build a real, usable Model.

The two classes in Example 10-6 comprise a Model that will support extensions to the demo application for this chapter. They provide a facility for storing a list of objects, each of which has X and Y coordinates, a color, and a size. They also provide a way to register a listener, and an interface that the listener must implement.

Example 10-6. The Dots Model package com.oreilly.android.intro.model;

/** A dot: the coordinates, color and size. */ public final class Dot {

private final float x, y; private final int color;

private final int diameter; /**

* @param x horizontal coordinate.

* @param y vertical coordinate.

* @param color the color.

* @param diameter dot diameter.

public Dot(float x, float y, int color, int diameter) { this.x = x; this.y = y; this.color = color; this.diameter = diameter;

/** @return the horizontal coordinate. */ public float getX() { return x; }

/** @return the vertical coordinate. */ public float getY() { return y; }

public int getColor() { return color; }

public int getDiameter() { return diameter; }

package com.oreilly.android.intro.model;

import java.util.Collections; import java.util.LinkedList; import java.util.List;

public interface DotsChangeListener {

/** @param dots the dots that changed. */ void onDotsChange(Dots dots);

private final LinkedList<Dot> dots = new LinkedList<Dot>();

private final List<Dot> safeDots = Collections.unmodifiableList(dots);

private DotsChangeListener dotsChangeListener;

/** @param l the new change listener. */ public void setDotsChangeListener(DotsChangeListener l) { dotsChangeListener = l;

/** @return the most recently added dot, or null. */ public Dot getLastDot() {

return (dots.size() <= 0) ? null : dots.getLast();

public List<Dot> getDots() { return safeDots; } /**

* @param x dot horizontal coordinate.

* @param y dot vertical coordinate.

* @param color dot color.

public void addDot(float x, float y, int color, int diameter) { dots.add(new Dot(x, y, color, diameter)); notifyListener();

/** Delete all the dots. */ public void clearDots() { dots.clear(); notifyListener();

private void notifyListener() {

if (null != dotsChangeListener) {

dotsChangeListener.onDotsChange(this);

In addition to using this model, the next example also introduces a widget used to view it, the DotView: it will be discussed later, in Example 12-3. For now we introduce it as a library widget. Its job is to draw the dots represented in the Model, in the correct color and at the correct coordinates. The complete source for the application is on the website for this book.

Example 10-7 shows the new demo application, after adding the new Model and View.

Example 10-7. Dots demo package com.oreilly.android.intro;

import java.util.Random;

import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.widget.Button; 10 import android.widget.EditText; import android.widget.LinearLayout;

import com.oreilly.android.intro.model.Dot; import com.oreilly.android.intro.model.Dots; import com.oreilly.android.intro.view.DotView;

/** Android UI demo program */ public class TouchMe extends Activity {

public static final int DOT_DIAMETER = 6;

private final Random rand = new Random();

final Dots dotModel = new Dots();

DotView dotView;

/** Called when the activity is first created. */ @Override public void onCreate(Bundle state) { super.onCreate(state);

dotView = new DotView(this, dotModel);

// install the view setContentView(R.layout.main);

((LinearLayout) findViewById(R.id.root)).addView(dotView, 0); // wire up the controller

((Button) findViewById(R.id.button1)).setOnClickListener( new Button.OnClickListener() {

@Override public void onClick(View v) {

((Button) findViewById(R.id.button2)).setOnClickListener(

new Button.OnClickListener() {

@Override public void onClick(View v) { makeDot(dots, dotView, Color.GREEN); } });

final EditText tb1 = (EditText) findViewById(R.id.text1); final EditText tb2 = (EditText) findViewById(R.id.text2); dots.setDotsChangeListener(new Dots.DotsChangeListener() { @Override public void onDotsChange(Dots d) { Dot d = dots.getLastDot();

tb1.setText((null == d) ? "" : String.valueOf(d.getX())); tb2.setText((null == d) ? "" : String.valueOf(d.getY())); dotView.invalidate(); } });

* @param dots the dots we're drawing

* @param view the view in which we're drawing dots

* @param color the color of the dot

void makeDot(Dots dots, DotView view, int color) { int pad = (DOT_DIAMETER + 2) * 2; dots.addDot(

DOT_DIAMETER + (rand.nextFloat() * (view.getWidth() - pad)), DOT_DIAMETER + (rand.nextFloat() * (view.getHeight() - pad)), color,

DOT_DIAMETER);

Here are some of the highlights of the code:

O These two calls to setOnClickListener add new listeners to the layout obtained from the XML definition.

© Anonymous classes handle click event callbacks to the "Red" and "Green" buttons. These event handlers differ from those in the previous example only in that here their behavior has been factored out into the local method makeDot, described in item 5.

© Calls to makeDot within onClick (to take place when a button is clicked).

O The most substantial change to the example. This is where the Model is wired to the View, using the Callback pattern, by installing a dotsChangedListener. When the Model changes, this new listener is called. It installs the X and Y coordinates of the last dot into the left and right text boxes, respectively, and requests that the Dot View redraw itself (the invalidate call).

© Definition of makeDot. This new method creates a dot, checking to make sure it is within the DotView's borders, and adds it to the Model. It also allows the dot's color to be specified as a parameter.

Figure 10-5 shows what the application looks like when run.

Figure 10-5. Running the Dots demo

Pushing the button labeled "Red" adds a new red dot to the DotView. Pushing the "Green" button adds a green one. The text fields contain the coordinates of the last dot added.

The basic structure of Example 10-2 is still recognizable, with some extensions. For example, here is the chain of events that results from clicking the "Green" button:

1. When the button is clicked, its clickHandler is called.

2. This causes a call to the anonymous class installed as an OnClickHandler. It, in turn, calls makeDot with the color argument Color.GREEN. The makeDot method generates random coordinates and adds a new green Dot to the Model at those coordinates.

3. When the Model is updated, it calls its DotsChangedListener.

4. The listener updates the values in the text views and requests that the DotView be redrawn.

0 0

Post a comment