Drawables

A Drawable is an object that knows how to render itself on a Canvas. Because a Drawable has complete control during rendering, even a very complex rendering process can be encapsulated in a way that makes it fairly easy to use.

Examples 12-7 and 12-8 show the changes necessary to implement the previous example, Figure 12-3, using a Drawable. The code that draws the red and green text has been refactored into a HelloAndroidTextDrawable class, used in rendering by the widget's onDraw method.

Example 12-7. Using a TextDrawable private static class HelloAndroidTextDrawable extends Drawable { private ColorFilter filter; private int opacity;

public HelloAndroidTextDrawable() {}

@Override public void draw(Canvas canvas) { Paint paint = new Paint();

paint.setColorFilter(filter); paint.setAlpha(opacity);

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

@Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public void setAlpha(int alpha) { } ^Override public void setColorFilter(ColorFilter cf) { }

Using the new Drawable implementation requires only a few small changes to the onDraw method.

Example 12-8. Using a Drawable widget package com.oreilly.android.intro.widget;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.Rect;

import android.graphics.drawable.Drawable;

import android.view.View;

/**A widget that renders a drawable with a transformation */ public class TransformedViewWidget extends View {

/** A transformation */ public interface Transformation { /** @param canvas */ void transform(Canvas canvas); /** @return text descriptiont of the transform. */ String describe();

private final Transformation transformation;

private final Drawable drawable;

* Render the passed drawable, transformed.

* @param context app context

* @param draw the object to be drawn, in transform

* @param xform the transformation */

public TransformedViewWidget( Context context, Drawable draw, Transformation xform)

super(context);

drawable = draw; transformation = xform;

setMinimumWidth(l60); setMinimumHeight(l35);

/** @see android.view.View#onMeasure(int, int) */ ^Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(

getSuggestedMinimumWidth(), getSuggestedMinimumHeight());

/** @see android.view.View#onDraw(android.graphics.Canvas) */ ^Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE);

canvas.save();

transformation.transform(canvas);

drawable.draw(canvas);

canvas.restore();

Paint paint = new Paint(); paint.setColor(Color.BLACK); paint.setStyle(paint.Style.STROKE); Rect r = canvas.getClipBounds(); canvas.drawRect(r, paint);

paint.setTextSize(lo);

paint.setColor(Color.BLUE);

canvas.drawText(

transformation.describe(), 5, getMeasuredHeight() - 5, paint);

This code begins to demonstrate the power of using a Drawable. This implementation of TransformedViewWidget will transform any Drawable, no matter what it happens to draw. It is no longer tied to rotating and scaling our original, hardcoded text. It can be reused to transform both the text from the previous example and a photo captured from the camera, as Figure 12-4 demonstrates. It could even be used to transform a Drawable animation.

The ability to encapsulate complex drawing tasks in a single object with a straightforward API is a valuable—and even necessary—tool in the Android toolkit. Drawables make complex graphical techniques such as nine-patches and animation tractable. In addition, since they wrap the rendering process completely, Drawables can be nested to decompose complex rendering into small reusable pieces.

Consider for a moment how we might extend the previous example to make each of the six images fade to white over a period of a minute. Certainly, we could just change

Ancfroid identity

Android sialyl .2,1 2)

O Cil translate^40,-20), rotatefSOj

Figure 12-4. Transformed views with photos the code in Example 12-8 to do the fade. A different—and very appealing—implementation involves writing one new Drawable.

This new Drawable, FaderDrawable, will take, in its constructor, a reference to its target, the Drawable that it will fade to white. In addition, it must have some notion of time, probably an integer—let's call it t—that is incremented by a timer. Whenever the draw method of FaderDrawable is called, it first calls the draw method of its target. Next, it paints over exactly the same area with the color white, using the value of t to determine the transparency (alpha value) of the paint (as demonstrated in Example 12-2). As time passes, t gets larger, the white gets more and more opaque, and the target Drawable fades to white.

This hypothetical FaderDrawable demonstrates some of the important features of Drawables. First, note that FaderDrawable is nicely reusable: it will fade just about any Drawable. Also note that, since FaderDrawable extends Drawable, we can use it anywhere that we would have used its target, the Drawable that it fades to white. Any code that uses a Drawable in its rendering process can use a FaderDrawable without change.

Of course, a FaderDrawable could itself be wrapped. In fact, it seems possible to achieve very complex effects, simply by building a chain of Drawable wrappers. The Android toolkit provides Drawable wrappers that support this strategy, including ClipDrawable, RotateDrawable, and ScaleDrawable.

At this point you may be mentally redesigning your entire UI in terms of Drawables. Although a powerful tool, they are not a panacea. There are several issues to keep in mind when considering the use of Drawables.

You may well have noticed that they share a lot of the functionality of the View class: location, dimensions, visibility, etc. It's not always easy to decide when a View should draw directly on the Canvas, when it should delegate to a subview, and when it should delegate to one or more Drawable objects. There is even a DrawableContainer class that allows grouping several child Drawables within a parent. It is possible to build trees of Drawables that parallel the trees of Views we've been using so far. In dealing with the Android framework, you just have to accept that sometimes there is more than one way to scale a cat.

One difference between the two choices is that Drawables do not implement the View measure/layout protocol, which allows a container view to negotiate the layout of its components in response to changing view size. When a renderable object needs to add, remove, or lay out internal components, it's a pretty good indication that it should be a full-fledged View instead of a Drawable.

A second issue to consider is that Drawables completely wrap the drawing process because they are not drawn like String or Rect objects. There are, for instance, no Canvas methods that will render a Drawable at specific coordinates. You may find yourself deliberating over whether, in order to render a certain image twice, a View onDraw method should use two different, immutable Drawables or a single Drawable twice, resetting its coordinates.

Perhaps most important, though, is a more generic problem. The idea of a chain of Drawables works because the Drawable interface contains no information about the internal implementation of the Drawable. When your code is passed a Drawable, there is no way for it to know whether it is something that will render a simple image or a complex chain of effects that rotates, flashes, and bounces. Clearly this can be a big advantage. But it can also be a problem.

Quite a bit of the drawing process is stateful. You set up Paint and then draw with it. You set up Canvas clip regions and transformations and then draw through them. When cooperating in a chain, if Drawables change state, they must be very careful that those changes never collide. The problem is that, when constructing a Drawable chain, the possibility of collision cannot be explicit in the object's type by definition (they are all just Drawables). A seemingly small change might have an effect that is not desirable and is difficult to debug.

To illustrate the problem, consider two Drawable wrapper classes, one that is meant to shrink its contents and another that is meant to rotate them by 90 degrees. If either is implemented by setting the transformation matrix to a specific value (instead of composing its transformation with any that already exist), composing the two Drawables may not have the desired effect. Worse, it might work perfectly if A wraps B, but not if B wraps A! Careful documentation of how a Drawable is implemented is essential.

0 0

Post a comment