MOBILE

Android Application Development : Rolling Your Own Widgets (part 4) - Drawables, Bitmaps

11/19/2013 8:11:29 PM

3. 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 Example 7 and Example 8 show the changes necessary to implement the previous example, Figure 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 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 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(160);
setMinimumHeight(135);
}

/** @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(10);
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 4 demonstrates. It could even be used to transform a Drawable animation.

Figure 4. Transformed views with photos


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 the code in Example 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 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.

4. Bitmaps

The Bitmap is the last member of the four essentials for drawing: something to draw (a String, Rect, etc.), Paint with which to draw, a Canvas on which to draw, and the Bitmap to hold the bits. Most of the time, you don’t have to deal directly with a Bitmap, because the Canvas provided as an argument to the onDraw method already has one behind it. However, there are circumstances under which you may want to use a Bitmap directly.

A common use for a Bitmap is as a way to cache a drawing that is time-consuming to draw but unlikely to change frequently. Consider, for example, a drawing program that allows the user to draw in multiple layers. The layers act as transparent overlays on a base image, and the user turns them off and on at will. It might be very expensive to actually draw each individual layer every time onDraw gets called. Instead, it might be faster to render the entire drawing with all visible layers once, and only update it when the user changes which are visible.

The implementation of such an application might look something like Example 9.

Example 9. Bitmap caching
private class CachingWidget extends View {
private Bitmap cache;

public CachingWidget(Context context) {
super(context);
setMinimumWidth(200);
setMinimumHeight(200);
}

public void invalidateCache() {
cache = null;
invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
if (null == cache) {
cache = Bitmap.createBitmap(
getMeasuredWidth(),
getMeasuredHeight(),
Bitmap.Config.ARGB_8888);

drawCachedBitmap(new Canvas(cache));
}

canvas.drawBitmap(cache, 0, 0, new Paint());
}


// ... definition of drawCachedBitmap
}

This widget normally just copies the cached Bitmap, cache, to the Canvas passed to onDraw. If the cache is marked stale (by calling invalidateCache), only then will drawCachedBitmap be called to actually render the widget.

The most common way to encounter a Bitmap is as the programmatic representation of a graphics resource. Resources.getDrawable returns a BitmapDrawable when the resource is an image.

Combining these two ideas—caching an image and wrapping it in a Drawable—opens yet another interesting window. It means that anything that can be drawn can also be postprocessed.

Other  
  •  iPhone SDK 3 Programming : XML Processing - An RSS Reader Application
  •  iPhone SDK 3 Programming : XML Processing - Simple API for XML (SAX)
  •  iPhone SDK 3 Programming : XML Processing - Document Object Model (DOM)
  •  iPhone SDK 3 Programming : XML and RSS
  •  Windows Phone 8 : Making Money - Modifying Your Application, Dealing with Failed Submissions, Using Ads in Your Apps
  •  Windows Phone 8 : Making Money - Submitting Your App (part 3) - After the Submission
  •  Windows Phone 8 : Making Money - Submitting Your App (part 2) - The Submission Process
  •  Windows Phone 8 : Making Money - Submitting Your App (part 1) - Preparing Your Application
  •  Windows Phone 8 : Making Money - What Is the Store?
  •  BlackBerry Push APIs (part 3) - Building an Application that Uses the BlackBerry Push APIs - Checking the Status of a Push Request and Cancelling a Push Request
  •  
    Top 10
    Review : Sigma 24mm f/1.4 DG HSM Art
    Review : Canon EF11-24mm f/4L USM
    Review : Creative Sound Blaster Roar 2
    Review : Philips Fidelio M2L
    Review : Alienware 17 - Dell's Alienware laptops
    Review Smartwatch : Wellograph
    Review : Xiaomi Redmi 2
    Extending LINQ to Objects : Writing a Single Element Operator (part 2) - Building the RandomElement Operator
    Extending LINQ to Objects : Writing a Single Element Operator (part 1) - Building Our Own Last Operator
    3 Tips for Maintaining Your Cell Phone Battery (part 2) - Discharge Smart, Use Smart
    REVIEW
    - First look: Apple Watch

    - 3 Tips for Maintaining Your Cell Phone Battery (part 1)

    - 3 Tips for Maintaining Your Cell Phone Battery (part 2)
    VIDEO TUTORIAL
    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 1)

    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 2)

    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 3)
    Popular Tags
    Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8