Creating and combining NinePatchDrawables at runtime - android

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.
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.
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.

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;
}

Related

Place an imageview on customdialog screen depending on variable

I'm trying to place a small image on the top of a big one in a custom dialog.
I want the small one to be placed in a different place depending on a variable, which I called "pos". It contains a int value from 0 to 100, meaning percentage of the dialog width.
If pos = 0, I want to place the image in the left margin of the big one, if pos = 30 it should be placed in the 30% of the screen, starting from the left.
If pos = 50 I want it to able placed right in the middle, being 100 the maximum value for it and placing it in the right margin.
I include a draft to explain myself a bit better.
I tried with RelativeLayout.LayoutParams but I never get expected output.
Perhaps a simpler strategy is to extend ImageView, then create a custom image using Canvas commands in the onDraw method that draw the bitmap appropriately on the x axis with a background set to match your layout.
You can determine the width of the custom dialog in the the onMeasure method of your custom ImageView, so the returned bitmap (including empty background) can be the correct width, and the position of the smaller bitmap can be correctly apportioned.
Once you've created the class, which will take just a few minutes, you can add it to your XML layout with something like:
<com.domain.yourapp.YourCustomerImageView
android:id="#+id/bitmap_graph"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"/>

Using setLayerInset on LayerDrawable with Bitmap gives unexpected results

I'm dynamically placing small dot bitmaps ('hotspots') over a large bitmap image using LayerDrawable. The layer at index 0 contains the large image. The dots' positions on the image are specified to me in terms of percentage of large image. For example, I might have a hotspot that is supposed to be 50% from the left and 75% from the top of the large base image. In order to position the dots over the correct part of the image, I'm using
setLayerInset(layer, leftOffset, topOffset, rightOffset, bottomOffset)
and calculating the offsets based on the width and height of the ImageView which contains the LayerDrawable. I'm happy that the calculations are correct and that the right height and width are being retrieved, and expect the dots to be correctly displayed, but they are not. The dots are being skewed along the Y axis and their positions are incorrect (too high and too far to the left). Has anyone encountered a similar problem and found a solution?
I spend a day and a half working on this, trying to make sense of the numbers, and here's what I found:
In the situation I am dealing with, where the first layer contains a BitmapDrawable and I'm setting insets for the subseqent layers, the offset in the call:
setLayerInset(layer, leftOffset, topOffset, rightOffset, bottomOffset)
are relative to the dimensions of the bitmap in the first layer, even if those dimensions are greater than the view in which the DrawableLayer is being placed (e.g. when the Bitmap is being redimensioned automatically by the view).
Bizarre and unexpected, but true. Hope this saves somebody some time.

Android ListView fast scroll with sections: section text too long

I am using ListView to implement a timeline. I enabled FastScroll and used SectionIndexer so that user could drag the scrollbar and see the section text displayed.
The problem is all these are built-in UI. The textview that displays the section text is too small for me, I am trying to display 05pm and it's too long for the textview(or other UI?).
Any easier way to resolve this? For instance, a method I can set the font size of the section text or the textview layout?
Thanks!
Looking through the source code for AbsListView, you can find the class that handles the fast scrolling mechanism which ends up being FastScroller. FastScroller actually draws the image drawable and text on the canvas that was provided to the AbsListView like so...
canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2 - hOff, (int) (rectF.bottom + rectF.top) / 2 + mOverlaySize / 4 - descent - vOff, paint);
The code above actually draws the text on top of the bottom image drawable so it does not respect the bounds of the bottom drawable, which is why the text is actually overflowing and not being cut off. As to why this is designed this way? My guess is that the intention of the index feature was to be mainly used for single characters such as A, B, C, etc... and therefore the fast scroll index feature was designed to fit that.
So, to give a definite answer to your question, there's really no way to change the text size or change how the text is being drawn unless you modify AbsListView and FastScroller to suit your need.

Android Custom View sizing

I am attempting to experiment in creating my own Views by subclassing "View." I want to simulate a new Button type class that will be drawn with the onDraw where I simply draw a rectangle from the canvas being passed in. I then add that custom View into the layout XML file, but I cannot control its size. For example, I set the width to "fill_parent" and the height to "wrap_content" but I don't know what the content of my View is. (BTW, the LinearLayout that my view is in is oriented to "vertical")
Can anyone give me some pointers in how to control the size of my View? For example, how does my custom view know what parameters were set in the XML file?
Android cannot detect the contents of your Canvas, so it assumes the contents are 0x0.
To do what you're looking for, you have to override onLayout or onMeasure (onLayout is specified by Android developers as a way to lay out children instead). You will also need a way to calculate the size of your contents.
First, start with a member variable called Point mSize = new Point(0, 0);. In a method other than onDraw, but called just as often, you will want to replace this with the size of your contents. If you draw a circle that has a radius of 25, do: mSize.x = 50; mSize.y = 50;.
Next, I'll introduce you to this answer (for onMeasure). It shows how to set the size of an item based on its parent's dimensions (divided by 2, in this case). Then, you can pull the size from mSize.x and mSize.y and apply those dimensions.
Finally, calling invalidate() after you measure your new mSize should force the View to lay itself out again, and adjust to your new parameters. This is the one part I'm unsure of in this regard.
NB: Also, you could call setLayoutParams using a fixed width and height once you calculate the size of your Canvas contents. Not sure of the performance implications for this, though.

How to have a circular TextView

I have been trying to make a circular TextView. Its a circle in which I want to accomodate whole space above a circular bubble as shown in image below.
Kindly see attached image.
In this image, we have a circular bubble with circular text in it.
I have already tried setting oval shape .xml as background of TextView but still no luck.
Edit:
As text length increase. It must reduces in size to fit inside the circle. This is the hardest part to think about.
You need to create a custom view, extending from TextView probably, setting the circle as background image, and calculate the text width / break the lines manually according to the width of the text.
To calculate the width of a string, see How to calculate string font width in pixels?
Some math and calculations is required of course to measure the available space per line; but I think that's the only way, as there's no standard component out there to do it.
To place the text onto the view, use drawText of the Canvas class.

Categories

Resources