Building a 9 patch drawable at runtime
Asked Answered
A

4

14

I am successfully building a 9patch drawable at runtime on Android 3.0+ using the excellent gist provided by Brian Griffey (found here).

Essentially I load the raw (no patches) graphic file from the network and the filename contains the cap insets that I need to use in order to scale the image accordingly. I then use these values with the class found above and apply the image as a background to a variety of elements (such as TextView, ImageButton, Button, ViewGroup, etc).

This works perfectly as you can see here:

Android 3.0+ result

However, running the same code on Android 2.3.x yields the result:

Android 2.x result

I've looked through the source code used in Android to parse a 9patch image (here and here) but have found no method of getting this to work correctly. I've tried just about everything I could throw at it to no avail.

For the record, the 9patch consists of three columns on each axis, one fixed, one stretchable and one fixed.

Here's hoping someone else has solved this problem before.

Thanks in advance.

EDIT I am only interested in duplicating this behavior on Android 2.3 and above (I originally had 2.x).

EDIT #2 This gist describes exactly what I'm trying to do + Source image: source image

EDIT #3 The size of the image is 22px/58px (width/height) and the insets are 14/6/14/6 (top/left/bottom/right).

Amundsen answered 12/5, 2013 at 16:15 Comment(10)
How do you apply the generated 9patch?Symmetry
@mr_archano I use the class in the gist above and pass it cap insets that match the scalable portions of the image I want to display. This generates a NinePatchDrawable that I then set as a background to the TextView.Amundsen
Ok I see. I was just wondering if Drawable.setBounds() is someway related to this issue.Symmetry
Sadly that does not seem to be the case. Thanks for your input though.Amundsen
Maybe this great guy knows the problem: @Brian Griffey. I don't know how to make a question directly to a person, but you can find out.Sicilia
I don't believe you can target a person directly with a question on SO.Amundsen
Have you checked that if you just use the original, pre-nine patch, background you get the expected result on 2.x?Aldric
Have you tested your problem on Android 2.3 against 2.2? This may be JDK version problem.Cobalt
@NeilTownsend I've clarified my version requirements for this feature and they only apply to Android 2.3 and above. Additionally, applying the exact same image with the 9-patch border matching the insets will produce the correct result on Android 2.3. Thanks.Amundsen
The above is also relevant to @alexeiburmistrovAmundsen
A
5

Putting together all I've spotted so far, and honing it a bit:

  • There doesn't seem to be a difference in the Res_png_9patch structure (which the byte chunk goes into) between android versions (see http://code.metager.de/source/xref/android), so that doesn't look like the cause.

  • Other answers about nine patch drawables suggest that each region (especially stretchable ones) should be at least 2x2 pixels (if not 3x3), but outside of that smaller is better.

  • However, the way some of the byte chunk is allocated looks like it could be updated. Try setting the 4th byte to 9 (the number of patches, I think), adding 7 more NO_COLORs to the the end and moving its size up to 56 + (7 x 4) = 84 bytes

Aldric answered 21/5, 2013 at 13:8 Comment(9)
I've tried just about every combination I've thought of. The problem with that explanation is that it doesn't define how to flag stretchable and fixed sections (even though I know they will always be SFSxSFS). I've also compared the byte data of the same image loaded via the normal .9.png file and one that I am generating, copying one to the other (.9.png chunk to generated version chunk) gives the same effect.Amundsen
If I load the image through the appt/.9png normal method it works fine. I added a gist showing my source code and image.Amundsen
I've added the size and insets of the image. The one displayed is the 'doubled up' version so insets should be doubled.Amundsen
Thanks (the link does explain how to define the stretchable sections, but it's not easy to read). However, if I've understood your numbers, the stretchable section in the middle is of zero size?Aldric
Even if I feed it the size manually to be 5, 4 or 3 (2, 1, 0) pixels left/right, I still get the same behavior.Amundsen
Maybe I've misunderstood you, but both left and right are measured from the left, so I would expect you to be saying 14/6/44/16 (top/left/bottom/right) to give a middle section of 10x30 pixels (w x h). Or have I just mis read what you meantAldric
If you notice in the source, I first extract the images' dimension and then proceed to subtracting the right / bottom coordinates from the width / height.Amundsen
Hi Neil, I wish I could split the rep but you're also absolutely correct in your assumption, the change with the 4th byte and the additional NO_COLOR made the difference worthwhile. Thank you very much for your help.Amundsen
Why the downvote out of interest - if I can improve the answer please do let me know how.Aldric
P
20

It's working for me after I updated the code. I think the color size made it unhappy for some reason(Based on the comments in the android source code each patch has a color hint, setting fewer than the number of sections in this case 9 appears to cause problems). I haven't tested with your image yet.

public static NinePatch createFixedNinePatch(Resources res, Bitmap bitmap, int top, int left, int bottom, int right, String srcName){
    ByteBuffer buffer = getByteBufferFixed(top, left, bottom, right);
    NinePatch patch = new NinePatch(bitmap, buffer.array(), srcName);
    return patch;
}

public static ByteBuffer getByteBufferFixed(int top, int left, int bottom, int right) {
    //Docs check the NinePatchChunkFile
    ByteBuffer buffer = ByteBuffer.allocate(84).order(ByteOrder.nativeOrder());
    //was translated
    buffer.put((byte)0x01);
    //divx size
    buffer.put((byte)0x02);
    //divy size
    buffer.put((byte)0x02);
    //color size
    buffer.put(( byte)0x09);

    //skip
    buffer.putInt(0);
    buffer.putInt(0);

    //padding
    buffer.putInt(0);
    buffer.putInt(0);
    buffer.putInt(0);
    buffer.putInt(0);

    //skip 4 bytes
    buffer.putInt(0);

    buffer.putInt(left);
    buffer.putInt(right);
    buffer.putInt(top);
    buffer.putInt(bottom);
    buffer.putInt(NO_COLOR);
    buffer.putInt(NO_COLOR);
    buffer.putInt(NO_COLOR);
    buffer.putInt(NO_COLOR);
    buffer.putInt(NO_COLOR);
    buffer.putInt(NO_COLOR);
    buffer.putInt(NO_COLOR);
    buffer.putInt(NO_COLOR);
    buffer.putInt(NO_COLOR);
    return buffer;
}
Paternity answered 21/5, 2013 at 17:53 Comment(1)
Thank you, but what is the correct value for top, left, bottom and right?. Currently, I use top= bitmapWidth/4, left= bitmapHeight/2, bottom = bitmapWidth - left, right = bitmapHeight- left. The background is fine but the content (text in button) is still get outside images. How can I make the content in button get in middle of the bitmap?.Arbitrament
A
5

Putting together all I've spotted so far, and honing it a bit:

  • There doesn't seem to be a difference in the Res_png_9patch structure (which the byte chunk goes into) between android versions (see http://code.metager.de/source/xref/android), so that doesn't look like the cause.

  • Other answers about nine patch drawables suggest that each region (especially stretchable ones) should be at least 2x2 pixels (if not 3x3), but outside of that smaller is better.

  • However, the way some of the byte chunk is allocated looks like it could be updated. Try setting the 4th byte to 9 (the number of patches, I think), adding 7 more NO_COLORs to the the end and moving its size up to 56 + (7 x 4) = 84 bytes

Aldric answered 21/5, 2013 at 13:8 Comment(9)
I've tried just about every combination I've thought of. The problem with that explanation is that it doesn't define how to flag stretchable and fixed sections (even though I know they will always be SFSxSFS). I've also compared the byte data of the same image loaded via the normal .9.png file and one that I am generating, copying one to the other (.9.png chunk to generated version chunk) gives the same effect.Amundsen
If I load the image through the appt/.9png normal method it works fine. I added a gist showing my source code and image.Amundsen
I've added the size and insets of the image. The one displayed is the 'doubled up' version so insets should be doubled.Amundsen
Thanks (the link does explain how to define the stretchable sections, but it's not easy to read). However, if I've understood your numbers, the stretchable section in the middle is of zero size?Aldric
Even if I feed it the size manually to be 5, 4 or 3 (2, 1, 0) pixels left/right, I still get the same behavior.Amundsen
Maybe I've misunderstood you, but both left and right are measured from the left, so I would expect you to be saying 14/6/44/16 (top/left/bottom/right) to give a middle section of 10x30 pixels (w x h). Or have I just mis read what you meantAldric
If you notice in the source, I first extract the images' dimension and then proceed to subtracting the right / bottom coordinates from the width / height.Amundsen
Hi Neil, I wish I could split the rep but you're also absolutely correct in your assumption, the change with the 4th byte and the additional NO_COLOR made the difference worthwhile. Thank you very much for your help.Amundsen
Why the downvote out of interest - if I can improve the answer please do let me know how.Aldric
L
1

Looks like working with 9patches is described in depth on SO already - Create a NinePatch/NinePatchDrawable in runtime

Layman answered 21/5, 2013 at 8:40 Comment(1)
That doesn't answer my question, which is related to Android version 2.3 specifically.Amundsen
K
0

Not really an answer to the question, but a useful workaround...

I've always found working with 9-patch files at runtime to be hairy. I have been successfully able to use FrameLayouts and automatic resizing of text to accomplish my desired effects. If your app will just be using this 9-patch, and not specifically allowing the user to download it as an image (an android 9-patch image generator, so to speak), I would advise you to look into that.

Kernan answered 21/5, 2013 at 13:3 Comment(1)
Thanks but the user does download the image and it needs to be applied to an element of arbitrary size so unfortunately that won't work in this case.Amundsen

© 2022 - 2024 — McMap. All rights reserved.