1

I am creating a compound custom control. The control consists of a single element across the bottom and an unknown number of elements across the top. The background for each of these elements is a StateListDrawable defining the NinePatchDrawables to use for each state of that element.

As the individual top elements change their width to suit their content, the region of the bottom element directly below needs to also change width to suit. The top elements may also have their positions shifted left or right.

control layout mock-up

The images above and below are just quick mock-ups to convey the layout and behavior I need to achieve. I am not responsible for designing the actual background images to be used, but they will not be a uniform flat grey.

desired stretch behavior

I managed to solve the first part of this re-sizing problem by:

  • Creating a custom class which extends Drawable.
  • Passing the class an array of NinePatchDrawables defining various potential regions of the Bottom View (eg left_end, right_end, middle_below_control, middle_below_gap).
  • Changing the bounds of each to match the relevant view above during the custom class' onBoundChange().
  • Drawing my array of re-sized NinePatches to the Bottom View canvas in onDraw().
  • Setting a combined padding for the Bottom View based on the padding of the NinePatches in the array.

This solution raises a number of problems though:

  • You need StateListDrawables for each of the potential regions of the Bottom View.
  • You need individual NinePatch images for each of the states in each of these StateListDrawables.
  • You have to design an image for the Bottom View which is restricted to being very uniform across it's width.
  • You need ear defenders and a thick skin to deal with all the shouting from the graphic designers.

What I really need to achieve is:

  • Have a single StateListDrawable for the Bottom View.
  • Extract the single image for each state and slice it up into the required sections.
  • Create a new NinePatchDrawable for each slice, applying the data Chunk from the view above for width only. Not height.
  • Then re-combine them as per my previous solution above.

Unfortunately as we know, you can't really play with the data Chunk. It's generated at compile time from the source image to create the NinePatch binary.

One of the better answers on this sort of topic can be found here. Based on this, a possible compromise solution might be:

  • For each of the top element types, have the graphic designers supply an additional blank dummy NinePatch source with the identical height and width dimensions, identical width padding and patch definitions, but with the height padding and patch definitions as they want for the bottom view.
  • Use getNinePatchChunk() on the relevant dummy NinePatch compiled resource.
  • Combine the resulting data Chunk with the relevant Bottom View bitmap slice to create a new NinePatch using NinePatch(Bitmap bitmap, byte[] chunk, String srcName).
  • Put up with dirty looks from the graphic designers, but at least they've stopped shouting.

This seems like it would be a bit of a clunky way of doing things. Is there a better, more efficient way of going about this to achieve all the required goals?

Note: There are likely to be many instances of this custom control in a scroll view, when used in an app, so nested layouts need to be kept to a minimum.

Community
  • 1
  • 1
Sound Conception
  • 5,263
  • 5
  • 30
  • 47
  • as i can see you need only two nine patches per state – pskink Sep 18 '13 at 08:45
  • @pskink. Thanks for your comment, but I'm not sure what you mean. Can you elaborate? – Sound Conception Sep 18 '13 at 10:51
  • maybe im wrong but it seems you need. one 9patch for one of top parts and one for bottom view – pskink Sep 18 '13 at 10:53
  • The image in my question is only intended to give an idea of the layout. If we were to stick with a simple grey background like that, then yes you are right, we'd only need one NinePatch for the bottom. However, for what the graphic designers have in mind, we need to be able to define multiple stretchable regions at run time. – Sound Conception Sep 19 '13 at 01:34
  • ok, i see it now, but even though why. do you think you would play with data chunks? are the 9patches taken from the network or something i mean they are not included in your app (not prebuilt by aapt) ? – pskink Sep 19 '13 at 03:46
  • I totally agree. As I said, playing with data chunks seems a clunky way of doing it. A NinePatch has one stretch region in a known location, fixed at compile time. I need multiple stretch regions at locations that aren't known until run time. My compromise solution above is to slice the image and build NinePatches as needed at run time, but I don't like it. Is there a simpler way. – Sound Conception Sep 19 '13 at 06:41
  • you can slice the image but why you need to build 9Patches? you dont need it – pskink Sep 19 '13 at 06:48
  • I've added another image to the question to make it clearer what I need to achieve with the stretching. The stretched portion of the Bottom view needs to match the stretched portion of the Top view. If it's possible to somehow query the Top NinePatch to find out the width of the unstretchable regions, I could slice the bottom image in the right places and avoid building NinePatches. Is it possible to get this info from a compliled NinePatch resource? – Sound Conception Sep 19 '13 at 10:25
  • hmm, what i see in the sources, you cant get this info, sorry :( – pskink Sep 19 '13 at 11:37
  • Yes. I couldn't see anything either. Looks like I might have to put up with my ugly solution. Thanks for your time. – Sound Conception Sep 19 '13 at 11:52
  • no problema amigo, good luck – pskink Sep 19 '13 at 18:03

1 Answers1

0

I have finally come up with a way to avoid using a blank dummy NinePatch source as necessary my compromise solution above.

Based on this excellent solution to a similar problem, I have written a utility method to extract the padding info from a NinePatch resource. Using this, I can extract the necessary padding from the adjacent NinePatch regions, and apply them to my new slice. Hope this is helpful to others.

public static Rect getNinePatchPadding(Context context, int drawableId) {
    Rect mPadding = new Rect();
    Bitmap bm = BitmapFactory.decodeResource(context.getResources(), drawableId);
    final byte[] chunk = bm.getNinePatchChunk();
    if (!NinePatch.isNinePatchChunk(chunk)) return null;
    ByteBuffer byteBuffer = ByteBuffer.wrap(chunk).order(ByteOrder.nativeOrder());

    // Skip to padding
    for (int i = 0; i < 12; i++) {
        byteBuffer.get();
    }

    mPadding.left = byteBuffer.getInt();
    mPadding.top = byteBuffer.getInt();
    mPadding.right = byteBuffer.getInt();
    mPadding.bottom = byteBuffer.getInt();

    return mPadding;
}
Community
  • 1
  • 1
Sound Conception
  • 5,263
  • 5
  • 30
  • 47