9 patch in Flutter
30 Jun 2020
History repeating
While not as applicable in these days of Material design and vector image file formats, the "9 patch" PNG file format in Android has been around since the first days of Android. I've used it on and off for years but never really thought much about it's details or history until recently when it came up in the context of a Flutter Q&A panel discussion where one of the other panelists suggested it's use, pointing out that Flutter had built in support for 9-patch files also. This piqued my curiosity as I had been completely unaware that Flutter had this functionality, but as often is the case, life is not quite that simple...
After doing a bit of research, what I first found is that 9-patch is actually a superset of a technique that predates Android called: "9 slice" which apparently first shipped in Flash 8 back in the mid 2000s.
Embrace and extend
So what is Android's 9-patch format?
Well luckily the format is very well documented in the Android docs 2d graphics topic. However the reason I used superset to describe it above is that while it's essentially a way to annotate images for applying the 9-slice technique to indicate what part of the image can be "stretched" when scaling, it has a couple of extra features on top of what 9-slice provides:
- allows specifying multiple horizontal and vertical regions for stretching
- allows specifying multiple optional "drawable sections"
The format itself though consists of just normal PNG files annotated with a 1-pixel wide border, with pixels being set to black on the top and left sides to represent areas that can be duplicated for to all the image to stretch and otherwise the border pixels need to be transparent or white. And the file are expected to have a suffix of 9.png
.
Make it so
If you have decided that you do want to use 9-patch, how can you make a 9-patch format file? Luckily these days Android Studio has a very nice built-in editor for the format. If you prefer not to use Android Studio, there is also a nice online tool available.
Slice and dice
So how does Flutter support 9-slice? Well its all down to a single property on the Image widget: centerSlice
. However the docs for this property are somewhat brief, but to the point:
The center slice for a nine-patch image.
The region of the image inside the center slice will be stretched both horizontally and vertically to fit the image into its destination. The region of the image above and below the center slice will be stretched only horizontally and the region of the image to the left and right of the center slice will be stretched only vertically.
Now given the spec for the 9-patch image format we looked at previously, this means that unlike the built-in support in Android, we can only implement the basic "9 slice", so the name of property is quite appropriate.
So given a 9-patch file, can we use it in Flutter? Well yes as long as it only uses a single stretchable region. To do so, what is left for us to do in Flutter: is to read the co-ordindates of the black pixels in the outer single-pixel border of the image and then use those to set the centerSlice
property Rect
along with discarding that outer 1-pixel border of the image before displaying it.
Getting the border pixel colours can be done using getPixel()
14 while trimming the border pixels off coudl probably be done using a package such as this one.
But wait there's more
For those who maybe interested in quickly trying the functionality provided by the Flutter Image widgets centerSlice
property, I have created a small demo on Dartpad.
One last thing
Finally a note that while Flutter's support does not match what the Android framework provides, it is equivalent to that available for other platforms such as for native iOS.
Acknowledgements
My thanks to Simon Lightfoot who sent me down this particular fascinating rabbit hole.