2.1. Drawing text
The most important Canvas
methods are those used to draw text. Although some Canvas
functionality is duplicated in other places, text-rendering
capabilities are not. In order to put text in your widget, you will
have to use the Canvas (or, of
course, subclass some other widget that uses it).
Canvas methods for rendering
text come in pairs: three sets of two signatures. Example 4 shows one of the
pairs.
Example 4. A pair of text drawing methods
public void drawText(String text, float x, float y, Paint paint) public void drawText(char[] text, int index, int count, float x, float y, Paint paint)
|
There are several pairs of methods. In each pair, the first of
the two methods in the pair uses String, and the
second uses three parameters to describe the text: an array of
char, the index indicating the
first character in that array to be drawn, and the number of total
characters in the text to be rendered. In some cases, there are
additional convenience methods.
Example 5 contains an
onDraw method that demonstrates the
use of the first style of each of the three pairs of text rendering
methods. The output is shown in Figure 2.
Example 5. Three ways of drawing text
@Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
paint.setColor(Color.RED); canvas.drawText("Android", 25, 30, paint);
Path path = new Path(); path.addArc(new RectF(10, 50, 90, 200), 240, 90); paint.setColor(Color.CYAN); canvas.drawTextOnPath("Android", path, 0, 0, paint);
float[] pos = new float[] { 20, 80, 29, 83, 36, 80, 46, 83, 52, 80, 62, 83, 68, 80 }; paint.setColor(Color.GREEN); canvas.drawPosText("Android", pos, paint); }
|
As you can see, the most elementary of the pairs, drawText, simply draws text at the passed
coordinates. With DrawTextOnPath,
on the other hand, you can draw text along any Path. The example path is just an arc. It
could just as easily have been a line drawing or a Bezier
curve.
For those occasions on which even DrawTextOnPath is insufficient, Canvas offers DrawPosText, which lets you specify
the exact position of each character in the text. Note that the
character positions are specified by alternating array elements:
x1,y1,x2,y2,
and so on.
2.2. Matrix transformations
The second interesting group of Canvas methods are the Matrix transformations and their related
convenience methods, rotate,
scale, and skew. These methods transform what you draw
in ways that will immediately be recognizable to those familiar
with 3D graphics. They allow a single drawing to be rendered
in ways that can make it appear as if the viewer were moving with
respect to the objects in the drawing.
The small application in Example 6 demonstrates the Canvas’s coordinate transformation
capabilities.
Example 6. Using a Canvas
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
public class TranformationalActivity extends Activity {
private interface Transformation { void transform(Canvas canvas); String describe(); }
private static class TransfomedViewWidget extends View { private final Transformation transformation;
public TransfomedViewWidget(Context context, Transformation xform) { super(context);
transformation = xform;
setMinimumWidth(160); setMinimumHeight(105); }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension( getSuggestedMinimumWidth(), getSuggestedMinimumHeight()); }
@Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
canvas.save(); transformation.transform(canvas);
paint.setTextSize(12); paint.setColor(Color.GREEN); canvas.drawText("Hello", 40, 55, paint);
paint.setTextSize(16); paint.setColor(Color.RED); canvas.drawText("Android", 35, 65, paint);
canvas.restore();
paint.setColor(Color.BLACK); paint.setStyle(Paint.Style.STROKE); Rect r = canvas.getClipBounds(); canvas.drawRect(r, paint);
paint.setTextSize(10); paint.setColor(Color.BLUE); canvas.drawText(transformation.describe(), 5, 100, paint); }
}
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.transformed);
LinearLayout v1 = (LinearLayout) findViewById(R.id.v_left); v1.addView(new TransfomedViewWidget( this, new Transformation() { @Override public String describe() { return "identity"; } @Override public void transform(Canvas canvas) { } } )); v1.addView(new TransfomedViewWidget(<-- (9) this, new Transformation() {<-- (10) @Override public String describe() { return "rotate(-30)"; } @Override public void transform(Canvas canvas) { canvas.rotate(-30.0F); } })); v1.addView(new TransfomedViewWidget(<-- (9) this, new Transformation() {<-- (10) @Override public String describe() { return "scale(.5,.8)"; } @Override public void transform(Canvas canvas) { canvas.scale(0.5F, .8F); } })); v1.addView(new TransfomedViewWidget(<-- (9) this, new Transformation() {<-- (10) @Override public String describe() { return "skew(.1,.3)"; } @Override public void transform(Canvas canvas) { canvas.skew(0.1F, 0.3F); } }));
LinearLayout v2 = (LinearLayout) findViewById(R.id.v_right); v2.addView(new TransfomedViewWidget( this, new Transformation() {<-- (10) @Override public String describe() { return "translate(30,10)"; } @Override public void transform(Canvas canvas) { canvas.translate(30.0F, 10.0F); } })); v2.addView(new TransfomedViewWidget(<-- (12) this, new Transformation() {<-- (10) @Override public String describe() { return "translate(110,-20),rotate(85)"; } @Override public void transform(Canvas canvas) { canvas.translate(110.0F, -20.0F); canvas.rotate(85.0F); } })); v2.addView(new TransfomedViewWidget(<-- (12) this, new Transformation() {<-- (10) @Override public String describe() { return "translate(-50,-20),scale(2,1.2)"; } @Override public void transform(Canvas canvas) { canvas.translate(-50.0F, -20.0F); canvas.scale(2F, 1.2F); } })); v2.addView(new TransfomedViewWidget(<-- (12) this, new Transformation() {<-- (10) @Override public String describe() { return "complex"; } @Override public void transform(Canvas canvas) { canvas.translate(-100.0F, -100.0F); canvas.scale(2.5F, 2F); canvas.skew(0.1F, 0.3F); } })); } }
|
The results of this protracted exercise are shown in Figure 3.
Here are some of the highlights of the code:
This small application introduces several new ideas and
demonstrates the power of Android graphics for maintaining state and
nesting changes.
The application defines a single widget, TransformedViewWidget, of which it creates
eight instances. For layout, the application creates two views named
v1 and v2, retrieving their parameters from
resources. It then adds four instances of TransformedViewWidget to each LinearLayout view. This is an example of how
applications combine resource-based and dynamic views. Note that the
creation both of the layout views and the new widgets take place
within the Activity’s onCreate
method.
This application also makes the new widget flexible through a
sophisticated division of labor between the widget and its
Transformation. Several simple objects are drawn
directly within the definition of TransformedViewWidget, in its onDraw method:
In the middle of this, the onDraw method performs a transformation
specified at its creation. The application defines its own interface,
called Transformation, and the
constructor for TransformedViewWidget accepts a Transformation as a parameter. We’ll see in
a moment how the caller actually codes a transformation.
It’s important to see first how the widget onDraw preserves its own text from being affected by the
Transformation. In this example, we want to make
sure that the frame and label are drawn last, so that they are drawn
over anything else drawn by the widget, even if they might overlap. On
the other hand, we do not want the transformation applied earlier to
affect them.
Fortunately, the Canvas
maintains an internal stack onto which we can record and recover the
translation matrix, clip rectangle, and many other elements of mutable
state in the Canvas. Taking
advantage of this stack, onDraw
calls save to preserve its state
before the transformation, and restore afterward to recover the saved
state.
The rest of the application controls the transformation used in
each of the eight instances of TransformedViewWidget. Each new instance of
the widget is created with its own anonymous instance of Tranformation. The image in the area labeled
“identity” has no translation applied. The other seven areas are
labeled with the transformations they demonstrate.
The base methods for Canvas
translation are setMatrix and
concatMatrix. These two methods allow you to build any possible
transformation. The getMatrix
method allows you to recover a dynamically constructed matrix for
later use. The methods introduced in the example—translate, rotate, scale, and skew—are convenience methods that compose
specific, constrained matrixes into the current Canvas state.
Although it may not be obvious at first, these transformation
functions can be tremendously useful. They allow your application to
appear to change its point of view with respect to a 3D object. It
doesn’t take too much imagination, for instance, to see the scene in
the square labeled “scale(.5,.8)” as the same as that seen in the
square labeled “identity”, but viewed from farther away. With a bit
more imagination, the image in the box labeled “skew(.1,.3)” again
could be the untransformed image, but this time viewed from above and
slightly to the side. Scaling or translating an object can make it
appear to a user as if the object has moved. Skewing and rotating can
make it appear that the object has turned. We will make good use of
this technique in animation.
When you consider that these transformation functions apply to
everything drawn on a canvas—lines, text, and even images—their
importance in applications becomes even more apparent. A view that
displays thumbnails of photos could be implemented trivially, though
perhaps not optimally, as a view that scales everything it displays to
10% of its actual size. An application that simulates what you see as
you look to your left while driving down the street might be
implemented in part by scaling and skewing a small number of
images.