
Programmatically Creating NinePatchDrawable
The new Android Lollipop has such an interesting component as VectorDrawable . If you use it wisely, you can significantly reduce the volume of the application, saving on graphic resources, plus the use of vector graphics frees us from the dreary process of creating images for different screen densities. The first thought that came to me when I saw VectorDrawable was: “Wow! Can it be pulled like a NinePatch ? ” It turned out impossible. Here one could be a little upset and content with the fact that at least the icons can be kept in the vector. However, I decided not to stop there. The result is a universal utility that can make NinePatchDrawable from any Drawable.

The image you see uses a vector, but it is centered. And this is amazing! The ability to stretch a vector only in certain areas provides, in truth, tremendous opportunities. And when you consider that there are projects that allow you to use the vector on earlier versions of android, vector images begin to show themselves in all their splendor.
Before starting the wonderful transformation, let's go a little deeper into the details and find out how we can get the desired result. First, let's try to understand what NinePatchDrawable is. And it consists of two main parts, the first of which is Bitmap, and the second is an array of bytes called “chunk”, which contains information on how to stretch this Bitmap. It follows that having any Bitmap and the corresponding “chunk” on hand, it is possible to create a NinePatchDrawable bypassing the standard model: create 9.png, put it into the project, compile it.
However, in practice, not everything is so simple. The fact is that, in fact, it is not realistic to find documentation about how “chunk” is formed. The aapt utility is engaged in its generation, even at the compilation stage, and in the android API there are no classes that help in generating “chunk”. I managed to figure out what a large part of the array is. Frankly, I did not fully understand which byte was responsible for what, but this turned out to be enough.
Let's parse this array by bytes. Bytes, the purpose of which I do not know, I decided to call "magic", because of their mystery and mystery. So:
The zero byte stores the boolean value “wasDeserialized”. All the examples I found say that it should be 0x01, but if you specify any other value instead, nothing bad happens, it will automatically set to true when converting the “chunk” array to a native object.
Padding, XDivs, YDivs, and Colors are stored in int (4 bytes). By the way, that is why Padding does not occupy 4 bytes, according to the number of sides, but 16 bytes.
XDivs and YDivs contain areas for stretching along the X and Y axes. Counting is done from scratch. The first number indicates the beginning of the first region, the next to its end, and so on. Then, the following array is described in a similar way.
As an example, let's take a look at the NinePatch image 6 by 6.

Here, the stretchable region along the X axis runs from 2 to 3 pixels, and along the Y axis from 2 to 4. So XDivs will consist of [0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00], and YDivs from [ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00]. As the first (second in order) and second (third in order) bytes of the “chunk” array, which determine the sizes of XDivs and YDivs, you should specify 0x02, which means 2 points of 4 bytes each.
For a better understanding of what should be stored in the Colors array, I made small changes to the previous image, let's look at it again.

As can be seen from the figure, the image can be divided into 9 regions. So, Colors determines how to draw these regions. There are 2 options:
As the name might seem, Colors is responsible for a certain color, however, in this case, its purpose is much more trivial - to indicate the visibility of the region.
For the above image, if we want to leave all regions visible, we need to specify [0x01, 0x00, 0x00, 0x00] 9 times, and set the value equal to 0x09 for 3 bytes (4 in order) of the “chunk” array.
A curious remark: apparently, the native code involved in drawing is still the size of the Colors array, he himself knows how many regions the image has and will receive them no matter what size we set for Colors. As a result, an appeal will be made to the memory area outside of our array, as a result, we will get sectors that disappear or appear, from one drawing to another.
At first, I doubted if this was enough for me to create a full-fledged NinePatch. Enough turned out. The fact is that NinePatch, before using “chunk”, converts it using the validateNinePatchChunk method into a native Res_png_9patch object. If you look at the source, you can see by the code, bytes that are not clear to us are not used, which means that you can fill them with any values, for example, zeros.
Now, knowing how to generate “chunk”, it will not be difficult to create NinePatch from any image, including Drawable, if, first, draw it on Bitmap. To simplify these steps, I decided to create the NinePatchBuilder class .
The following code shows how to use it in the case of regular Bitmap.
The addStretchSegment methods specify areas for stretching. Since, when creating NinePatch, images whose size is not known in advance can be used, it was decided to use relative sizes in the range [0, 1]. When you call build, depending on the settings and the size of the Bitmap, an array of “chunk” will be formed and NinePatchDrawable will be created.
Here's what happens inside the NinePatchBuilder:
I will not give the code of the getChunkByteArray method, since most of its implementation follows from the “chunk” generation algorithm described earlier.
Similarly for Drawable. Anything can act as Drawable here, including VectorDrawable. As a result, having only one vector image, we get a full set of NinePatchDrawable, for all screen densities!
Let's say we have a vector image.
Converting it to NinePatch is not a big deal.
Additionally, you need to specify the sizes in pixels, since not all Drawable have fixed sizes. At the time of the call to build, Drawable is drawn on the Bitmap and this Bitmap is already used to create the NinePacthDrawable.
Naturally, blindly using drawn Drawable is not always a good solution, since there are, for example, DrawableContainer and its descendants. To support such complex objects had to go to a certain trick.
This is part of the code from NinePatchBuilder, it creates a new Drawable of the same class as the original one using Class.newInstance (), and then fills it with the inflate method. All of this is similar to what happens inside the LayoutInflater, with the exception of ResourceWrapper. The whole essence lurks in it. If we look at the work of the inflate method, we will see that the Drawable children are obtained by the getDrawable method from the resources transferred as a parameter. To get the desired result, just override this method.
Thanks to this “feint with ears”, we implemented full support for all DrawableContainer descendants with any level of nesting, and if you convert StateListDrawable, then the output will be a StateListDrawable consisting of NinePatchDrawable.
One builder was not enough for me, I decided to go ahead and make a NinePatchInflater class that collects NinePatch from an XML file. As a result, our Drawable can be described as follows:
The file should be in the “xml” folder. Now, the code involved in creating such a Drawable can be reduced to one line.
In addition to putting most of the code into a separate file, inflater-a has another big plus - caching by resource id. The fact is that creating a Drawable can be a very expensive operation, especially in our case when to get one Drawable, you have to create a bunch of objects that are not needed in the future. Fortunately, most of the necessary work has already been done in the ConstantState class, we just need to save the ConstantState of the Drawable created in the cache and, if necessary, create new Drawable using the ConstantState.newDrawable () method. I will not go into details, the article turned out to be extensive, and besides, I didn’t come up with anything new, this is the way caching occurs in the Resources class.
It turned out not bad, however, to create a full wrapper over resources so that you could insert links to these files directly in the XML markup, without resorting to writing program code, it didn’t work out. As it turned out, when creating a View, in places, methods with the default access modifier are used, and sometimes, static methods of the Drawable class are called directly. Despite this, I believe that the desired result was achieved, although not to the full extent.
GitHub Project: NinePatchBuildUtils
Links for especiallycurious people like me curious:
NinePatch sources: NinePatch.java and NinePatch.cpp A
place to read about Res_png_9patch: ResourceTypes.h and ResourceTypes.cpp
Thank you for your attention!

The image you see uses a vector, but it is centered. And this is amazing! The ability to stretch a vector only in certain areas provides, in truth, tremendous opportunities. And when you consider that there are projects that allow you to use the vector on earlier versions of android, vector images begin to show themselves in all their splendor.
Before starting the wonderful transformation, let's go a little deeper into the details and find out how we can get the desired result. First, let's try to understand what NinePatchDrawable is. And it consists of two main parts, the first of which is Bitmap, and the second is an array of bytes called “chunk”, which contains information on how to stretch this Bitmap. It follows that having any Bitmap and the corresponding “chunk” on hand, it is possible to create a NinePatchDrawable bypassing the standard model: create 9.png, put it into the project, compile it.
However, in practice, not everything is so simple. The fact is that, in fact, it is not realistic to find documentation about how “chunk” is formed. The aapt utility is engaged in its generation, even at the compilation stage, and in the android API there are no classes that help in generating “chunk”. I managed to figure out what a large part of the array is. Frankly, I did not fully understand which byte was responsible for what, but this turned out to be enough.
What is a chunk?
Let's parse this array by bytes. Bytes, the purpose of which I do not know, I decided to call "magic", because of their mystery and mystery. So:
- [0] - flag wasDeserialized;
- [1] - indicates the number of points in the XDivs array;
- [2] - indicates the number of points in the YDivs array;
- [3] - indicates the number of points in the Colors array;
- [4-11] - 8 "magic" bytes;
- [12-27] - 16 bytes for Padding;
- [28-31] - 4 "magic" bytes;
- [32- ~] - Next is an listing of the XDivs, YDivs and Colors arrays.
The zero byte stores the boolean value “wasDeserialized”. All the examples I found say that it should be 0x01, but if you specify any other value instead, nothing bad happens, it will automatically set to true when converting the “chunk” array to a native object.
Padding, XDivs, YDivs, and Colors are stored in int (4 bytes). By the way, that is why Padding does not occupy 4 bytes, according to the number of sides, but 16 bytes.
XDivs and YDivs contain areas for stretching along the X and Y axes. Counting is done from scratch. The first number indicates the beginning of the first region, the next to its end, and so on. Then, the following array is described in a similar way.
As an example, let's take a look at the NinePatch image 6 by 6.

Here, the stretchable region along the X axis runs from 2 to 3 pixels, and along the Y axis from 2 to 4. So XDivs will consist of [0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00], and YDivs from [ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00]. As the first (second in order) and second (third in order) bytes of the “chunk” array, which determine the sizes of XDivs and YDivs, you should specify 0x02, which means 2 points of 4 bytes each.
For a better understanding of what should be stored in the Colors array, I made small changes to the previous image, let's look at it again.

As can be seen from the figure, the image can be divided into 9 regions. So, Colors determines how to draw these regions. There are 2 options:
- 0x00000000 (TRASPARENT) region will be transparent.
- 0x00000001 (NO_COLOR) region will be visible.
As the name might seem, Colors is responsible for a certain color, however, in this case, its purpose is much more trivial - to indicate the visibility of the region.
For the above image, if we want to leave all regions visible, we need to specify [0x01, 0x00, 0x00, 0x00] 9 times, and set the value equal to 0x09 for 3 bytes (4 in order) of the “chunk” array.
A curious remark: apparently, the native code involved in drawing is still the size of the Colors array, he himself knows how many regions the image has and will receive them no matter what size we set for Colors. As a result, an appeal will be made to the memory area outside of our array, as a result, we will get sectors that disappear or appear, from one drawing to another.
At first, I doubted if this was enough for me to create a full-fledged NinePatch. Enough turned out. The fact is that NinePatch, before using “chunk”, converts it using the validateNinePatchChunk method into a native Res_png_9patch object. If you look at the source, you can see by the code, bytes that are not clear to us are not used, which means that you can fill them with any values, for example, zeros.
NinePatchBuilder
Now, knowing how to generate “chunk”, it will not be difficult to create NinePatch from any image, including Drawable, if, first, draw it on Bitmap. To simplify these steps, I decided to create the NinePatchBuilder class .
The following code shows how to use it in the case of regular Bitmap.
NinePatchBuilder ninePatchBuilder = new NinePatchBuilder(getResources())
.addStretchSegmentX(0.49f, 0.51f)
.addStretchSegmentY(0.49f, 0.51f)
.setBitmap(bitmap);
Drawable drawable = ninePatchBuilder.build();
The addStretchSegment methods specify areas for stretching. Since, when creating NinePatch, images whose size is not known in advance can be used, it was decided to use relative sizes in the range [0, 1]. When you call build, depending on the settings and the size of the Bitmap, an array of “chunk” will be formed and NinePatchDrawable will be created.
Here's what happens inside the NinePatchBuilder:
// Код из NinePatchBuilder.
private Drawable buildFromBitmap(Bitmap bitmap) {
return new NinePatchDrawable(mResources,
bitmap,
getChunkByteArray(bitmap),
getPaddingRect(bitmap.getWidth(), bitmap.getHeight()),
mSrcName);
}
I will not give the code of the getChunkByteArray method, since most of its implementation follows from the “chunk” generation algorithm described earlier.
Similarly for Drawable. Anything can act as Drawable here, including VectorDrawable. As a result, having only one vector image, we get a full set of NinePatchDrawable, for all screen densities!
Let's say we have a vector image.
android.xml
Converting it to NinePatch is not a big deal.
NinePatchBuilder ninePatchBuilder = new NinePatchBuilder(resources)
.addStretchSegmentX(0.49f, 0.51f)
.addStretchSegmentY(0.49f, 0.51f)
.setDrawable(R.drawable.android,
(int) resources.getDimension(R.dimen.android_width),
(int) resources.getDimension(R.dimen.android_height));
Additionally, you need to specify the sizes in pixels, since not all Drawable have fixed sizes. At the time of the call to build, Drawable is drawn on the Bitmap and this Bitmap is already used to create the NinePacthDrawable.
Special cases
Naturally, blindly using drawn Drawable is not always a good solution, since there are, for example, DrawableContainer and its descendants. To support such complex objects had to go to a certain trick.
// Код из NinePatchBuilder.
if (drawable instanceof DrawableContainer) {
final XmlPullParser parser = mResources.getXml(drawableId);
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type = XmlPullParser.START_DOCUMENT;
try {
while ((type=parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty loop
}
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
}
if (type == XmlPullParser.START_TAG) {
Drawable result = null;
try {
result = drawable.getClass().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
if (result != null) {
try {
result.inflate(new ResourceWrapper(mResources), parser, attrs);
return result;
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
}
}
}
}
This is part of the code from NinePatchBuilder, it creates a new Drawable of the same class as the original one using Class.newInstance (), and then fills it with the inflate method. All of this is similar to what happens inside the LayoutInflater, with the exception of ResourceWrapper. The whole essence lurks in it. If we look at the work of the inflate method, we will see that the Drawable children are obtained by the getDrawable method from the resources transferred as a parameter. To get the desired result, just override this method.
// Код из NinePatchBuilder.
private class ResourceWrapper extends Resources {
public ResourceWrapper(Resources resources) {
super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
}
@Override
public Drawable getDrawable(int id) throws NotFoundException {
return buildFromDrawable(id, mDrawableWidth, mDrawableHeight);
}
@Override
public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
return buildFromDrawable(id, mDrawableWidth, mDrawableHeight);
}
}
Thanks to this “feint with ears”, we implemented full support for all DrawableContainer descendants with any level of nesting, and if you convert StateListDrawable, then the output will be a StateListDrawable consisting of NinePatchDrawable.
XML and caching
One builder was not enough for me, I decided to go ahead and make a NinePatchInflater class that collects NinePatch from an XML file. As a result, our Drawable can be described as follows:
The file should be in the “xml” folder. Now, the code involved in creating such a Drawable can be reduced to one line.
Drawable drawable = NinePatchInflater.inflate(resources, R.xml.vector_drawable_nine_patch);
In addition to putting most of the code into a separate file, inflater-a has another big plus - caching by resource id. The fact is that creating a Drawable can be a very expensive operation, especially in our case when to get one Drawable, you have to create a bunch of objects that are not needed in the future. Fortunately, most of the necessary work has already been done in the ConstantState class, we just need to save the ConstantState of the Drawable created in the cache and, if necessary, create new Drawable using the ConstantState.newDrawable () method. I will not go into details, the article turned out to be extensive, and besides, I didn’t come up with anything new, this is the way caching occurs in the Resources class.
Conclusion
It turned out not bad, however, to create a full wrapper over resources so that you could insert links to these files directly in the XML markup, without resorting to writing program code, it didn’t work out. As it turned out, when creating a View, in places, methods with the default access modifier are used, and sometimes, static methods of the Drawable class are called directly. Despite this, I believe that the desired result was achieved, although not to the full extent.
GitHub Project: NinePatchBuildUtils
How to connect to your project
There are 2 options.
1 option (forehead):
2 option (elegant):
1 option (forehead):
- Copy the folder with the ninepatchbuildutils module to your project
- In the settings.gradle file add:
include ':ninepatchbuildutils'
- Add the dependency to the build.gradle file of the application module:
compile project(':ninepatchbuildutils')
- Rebuild the project
2 option (elegant):
- Download the project from gita to a separate folder, for example, "NinePatchBuildUtils"
- In the settings.gradle file add:
You can also use the relative path:include ':ninepatchbuildutils' project(':ninepatchbuildutils').projectDir = new File('<Путь к папке с проектами>/NinePatchBuildUtils/ninepatchbuildutils/')
project(':ninepatchbuildutils').projectDir = new File(settingsDir, '../NinePatchBuildUtils/ninepatchbuildutils/')
- Add the dependency to the build.gradle file of the application module:
compile project(':ninepatchbuildutils')
- Rebuild the project
Links for especially
NinePatch sources: NinePatch.java and NinePatch.cpp A
place to read about Res_png_9patch: ResourceTypes.h and ResourceTypes.cpp
Thank you for your attention!