Monday, December 30, 2013

Android NinePatch Image Pain

Since 2010, Android has provided 9-patch image support. It enables app developers to specify bitmap areas that can be stretched in order to allow text to fit inside graphics. This format allows custom graphics to be used as buttons with fairly quick ease.  Even iOS appears to have borrowed this concept and third-party libraries are available to leverage this format.

There are a multitude of sites explaining how to use the tool to create your own 9 patch images.  The documentation on Android's site provides a walkthrough about how to use the draw9patch tool to create new ones.  The idea of 9-patch is to define the areas that can be stretched both horizontally and vertically by drawing a line along the outside border.  If you also want to specify where the text should fix, you also need to define the region by creating lines along the right and bottom lines.

The draw9patch tool provides the ability to show how these areas are defined as you change the borders, but it often doesn't show you what the gotchas are.  I encountered some of these hiccups when trying to create custom speech bubbles with the Android Map Utils library.  Here was my first attempt to define the stretchable region:

Here's the result:

The arrow gets stretched too, so we actually want to define two stretchable regions:

The actual result?

Android's documentation says that if the bottom and right lines are not included, it uses the left and top lines.   It doesn't say what happens what happens if two stretchable regions are defined.   To avoid this confusion, we add a black line to the border below:

Now we get:

Also, if you're trying to understand how NinePatch internals works, you actually have to dig into NinePatchImpl.cpp.  The NinePatch implementation is actually a JNI interface to C++ code. The header file ResourceTypes.h is actually the best documented source of how the stretchable regions are defined internally.    The implementation code handles the magic of stretching the regions and proportionately scaling them.  In the example above,  only 1 pixels are defined on both left and right so they will be stretched uniformly.

Underlying all these internals is that Android relies on the Skia 2D library, so many of the esoteric units that start with Sk has to deal with this fact that much of the library deals in SkScalar metrics.   The magic happens with the calculateStretch() function, which figures out how many pixels to scale based on the total boundary size and the remaining number of stretchable regions.  It just turns out that much of Android's graphics library is built with Skia, so trying to understand how any of it works leads you down the path of reviewing the C++ code

Finally, keep in mind that the only way the draw9patch/Android knows that your images are 9-patch drawable is by the file extension.  If you save your custom button with rounded corners with the 9.png file extension, it's likely you'll find your corners cut off since it assumes that the outer 1px borders have been created to define the stretchable and content regions.   There's no magic number type, much less any type of header prefix, so be careful about naming of files.

Furthermore, there's also a source and compiled version of 9.png files.   If you try to retrieve the 9.png file from the .apk file, chances are it will not look like what's in your source code.

No comments:

Post a Comment