Posts Tagged ‘Drawable’

TextDrawable: Draw Some Text!

You can find the full source code for TextDrawable and the sample project on GitHub.

The Drawable framework in Android is a neat and really flexible way to create portions of your UI.  Many times have I been able to simplify the view hierarchy or required resources just by getting creating with what a Drawable can do.  Recently, I had a need to place text into a Drawable so it could be inserted in places where the framework only allows Drawables to go.  So I created TextDrawable and though it share it.

If you would like to take a look at the TextDrawable source directly, head over to the GitHub link above.  This post deals mostly with the example application that wraps the implementation.

The Basics

Basically, TextDrawable is exactly that, a Drawable that displays a CharSequence.  It supports most all of the functionality you would find on Textview for setting and formatting text display.  It supports multi-line strings (line breaks, etc.) and is configured to have intrinsic bounds equal to the size required to draw the text contents as formatted.  This means that in most cases, you don’t have to explicitly call setBounds() on the object, it knows how big it wants to be…similar to working with Bitmaps.

With this class, text can now be part of the Drawable world, meaning it can not only be set alone in places where you would normally put an image, it can also be placed together with other Drawables in containers like StateListDrawable or animated with the likes of TransitionDrawable and ClipDrawable.  In many cases, we can use this to do a job that would otherwise require multiple views or compound controls just to achieve a given visual effect; thus it can reduce overhead in your view hierarchy.

Java Only

One of the major drawbacks of creating your own Drawable implementations is they cannot be inflated using XML (the Android team claims this is a security concern since this code is used by the core platform, so I wouldn’t expect it to change anytime soon).  This can make them more difficult to work with if you want to include a custom implementation inside a larger composite container (such as a state list or layer list).  Note that it is still possible, it just means you must do all your Drawable construction in Java.

Usage Examples

A simple example of using TextDrawable looks something like this:

ImageView mImageOne;
TextDrawable d = new TextDrawable(this);
d.setText("SAMPLE TEXT\nLINE TWO");
d.setTextAlign(Layout.Alignment.ALIGN_CENTER);

mImageOne.setImageDrawable(d);

This simply display the two-line string supplied, center-aligned, using the default text appearance settings from the application theme.

Path Drawing

TextDrawable does have one additional feature, in that you can also pass a Path object to it if you want the text to be drawn in a particular custom way (e.g. in a circle or along a curve).  In this case, the text measurement code cannot properly determine the size required, so TextDrawable will report no intrinsic size and you will need to call setBounds() with a size that appropriately matches the Path you have applied.

ImageView mImageThree;
TextDrawable d = new TextDrawable(this);
d.setText("TEXT DRAWN IN A CIRCLE");
d.setTextColor(Color.BLUE);
d.setTextSize(12);

Path p = new Path();
int origin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP40, getResources().getDisplayMetrics());
int radius = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP30, getResources().getDisplayMetrics());
int bound = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP80, getResources().getDisplayMetrics());
p.addCircle(origin, origin, radius, Path.Direction.CW);

d.setTextPath(p);
//Must call setBounds() since we are using a Path
d.setBounds(0, 0, bound, bound);

mImageThree.setImageDrawable(d);

In this example we apply a circular path to the TextDrawable on which to draw the text content.  Because we are using a custom path, and TextDrawable cannot measure its proper size, our application must also call setBounds() to the Drawable before handing it over to the ImageView.

Compound Drawable

Another very useful case for this is to place a small piece of static text in the top, bottom, left, right drawable locations on a TextView widget.  The following example inserts a short, non-editable, text prefix before whatever the user types into an EditText:

EditText mEditText;
d = new TextDrawable(this);
d.setText("SKU#");

mEditText.setCompoundDrawablesWithIntrinsicBounds(d, null, null, null);

If you want to use the same text in multiple locations, you can even set the same instance in more than one place.  Notice how we used the version of this method that expects intrinsic bounds.  If we had used a Path, we would need to explicitly call setBounds() and use setCompoundDrawables() instead.

Note: Since Button is also a Textview, you can use this technique there as well.

Dynamic Drawable

The following example wraps TextDrawable in a ClipDrawable for the purposes of animating the revealing of the supplied text in a more granular fashion rather  than just going letter-by-letter (i.e. typewriter style):

ImageView mImageFive;
d = new TextDrawable(this);
d.setText("SHOW ME TEXT");
ClipDrawable clip = new ClipDrawable(d, Gravity.LEFT, ClipDrawable.HORIZONTAL);

mImageFive.setImageDrawable(clip);

You can then animate this by calling setImageLevel() repeatedly on the enclosing container.

These are only some of the examples of what you might be able to simplify in your application UI by using text as a Drawable.

Framework Bug

In developing the examples for this, I came across a problem on ICS and Jelly Bean devices (and probably Honeycomb) having to do with hardware acceleration.  We’re all familiar by now with the fact that hardware acceleration is a great addition to the platform starting in Android 3.0 but that not all drawing primitives and features are quite yet properly supported there.  In many cases View, Activities, or entire applications need to disable hardware acceleration to keep compatibility with their graphics code…this is one of those such cases.  It seems that drawText() or one of its variants is not yet fully supported in hardware.

You may notice if you look at the example code posted with the TextDrawable implementation that some of the ImageView instances have a separate scaling mode applied to illustrate how TextDrawable responds to being matrix scaled.  The framework matrix scales images by transforming the Canvas on which they are drawn, and on older devices or with hardware acceleration disabled, this works great.  However, enabling hardware acceleration will cause the text in the scaled cases to get terribly pixelated.  I imagine in the future this will be corrected, but it’s behavior at least on these versions of the platform will forever be this way.

As is common in these cases, the solution is to add hardwareAcceleration="false" to your manifest or use setLayerType(View.LAYER_TYPE_SOFTWARE, null) on the View where you may be doing these transformations.

Tip: Resizing Issues

One thing to be aware of when using Drawables in a dynamic manner (i.e. changing their contents after they are set on a View) is that most widgets in the framework do not allow for a drawable to notify the host that it has been resized.  Most widgets like TextView and ImageView act as a callback for the Drawable so it can request, through invalidation, that it be redrawn.  however, in most cases this does not trigger the host View to re-check the Drawable bounds.  The initial bounds it calculated are those you are stuck with.  In many cases where we display Bitmaps, this is not a concern, however if you are expecting to dynamically change the text of a TextDrawable after it is attached, you will notice that text will simply be redrawn inside of the initial bounding box.

There are a few hacks to get this to work if that is your plan.  For example, ImageView does remeasure its Drawable content whenever setSelected() is called, so it is possible to force ImageView to resize the drawable with each change by calling setSelected(false) each time as well (assuming this doesn’t affect other code you may have in place).  The best solution, however, is to use the container Drawables in the framework whenever possible to create the dynamic effects you need.

So there you have it!  Now the next time you think to yourself “if I could just put this text into a Drawable…”; now you can!

9-Patches Explained

One of Android’s most well-known points to consider in application development is the requirement to support a variety of different screen sizes and orientations in an application. The SDK is full of documentation and tools on how to help the developer best accomplish this goal; one of these tools is the 9-Patch creator.

The 9-Patch is a PNG image with some coding added that allows the Android system to determine how the image can be stretched and contorted to meet the specific layout constraints during use (like fill_parent and wrap_content).  It does this by taking a predefined PNG image, and allowing the user to define a 1-pixel border around the image in locations where stretching can occur.

Creating a 9-Patch

  1. Navigate to the tools/ directory of your SDK from a command-line.
  2. Run draw9patch and a window will appear.
  3. Drag and drop and PNG image into the application window
    • The image will appear with a blank 1-px border around it
  4. Add stretchable regions by clicking on pixels in the newly created blank area (you will see black mark appear for each selection)
    • Remove any mistakes by holding the Shift key and clicking on a marked pixel
    • See below for more on this

9-Patch Theory

The 9-Patch gets its name from the fact that the overlay created breaks the image up into nine defined regions, organized similar to tic-tac-toe.  Each region has specific stretch properties:

  • Corner Regions (A,C,G,I)
    • These regions are fixed and nothing inside them will stretch
  • Horizontal Sides (D,F)
    • The pixels in these regions will stretch vertically when necessary
  • Vertical Sides (B,H)
    • The pixels in these regions will stretch horizontally when necessary
  • Center (E)
    • The pixels in this regions will stretch in both horizontal and vertical directions equally

Draw9Patch

The draw9patch tool allows the user to define two pieces of data by applying marks on the image border:

  • Stretchable zones
    • The portions of the image that are to be fixed or stretched when the image must fill or wrap in layout
    • Created by marking border pieces on the TOP and LEFT of the image
  • Content area
    • The area portion of the image where content is to be inserted (generally text or something applied via the android:src xml tag)
    • Created by marking border pieces on the BOTTOM and RIGHT of the image

As an example, below is what a basic patched graphic will look like inside the draw9patch tool.  The green regions define the patches that will stretch either horizontal or vertical.  The pink patch is a fully stretchable patch.

Original

Patched

You can see how the colored regions map to the example grid shown above.  This is a simple example, but the user can create multiple regions in the same image like so:

Now we have an image with multiple fixed and stretchable regions.  Again, pink will stretch in both directions, green in one direction, and the rest of the image will be fixed.

A Word About Content

Defining the content area gives the user the ability to assist Android in providing the proper location where source content (text, other images, etc.) are placed when this image is a background or other content wrapper image.  Think of the standard Button as an example.  When a user defines android:text (of android:src on an ImageButton), this is the button content.  That content is laid out inside of the content area that was defined in the button background image (a 9-patch, by the way).

With our example, if I define a solid mark across the bottom and right of the image, stopping just short of the rounded corners, a content area will be defined as shown in this preview pane:

Tricks of the Tool

  • Show Patches
    • Turn on the visual assistance of the green/pink patch locations
  • Show Content
    • In the preview pane, highlight the content section that exists in each orientation
  • Show Bad Patches
    • Allow draw9patch to evaluate whether you might be causing trouble by defining a stretchable area that includes components (like a piece of text or a gradient) that won’t always stretch consistently.  The “bad” patches are enclosed in bright red borders.