Saturday, 23 February 2013

Create a Bitmap from a Layout/View before/without draw

Android development is a super-cool stuff but it's full of situations that can get a tight grip on your nerves and piss you off, either to work around them or to fix some evil glitch. Hell load of help materials available though, but the fight still goes on. I'm just another warrior on your side. And I have decided to write down about whatever demon of Android world I happen to victor over, so that you could escape it right away.

Let's do the shit!

This is my first post which goes round about "Creating bitmap from a layout". Now this is a situation where you require to create a bitmap from a particular custom layout at runtime. Creating one once the layout is laid out into a View is not really a high fly. Following code will do it gracefully..

LinearLayout view = (LinearLayout)findViewById(R.id.linearlayout);
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bm = view.getDrawingCache();
The real pain in azz is when you require this Bitmap dude before layout is drawn. That means the corresponding view hasn't yet gone through the onMeasure() and onDraw() methods, so it doesn't yet have any dimensions defined. It might cry down into a NullPointerException to honor the void state of height and width measures for the view. After a few of hours of dig and bounce, I've written the following method and it works for me..

public Bitmap createBitmapFromLayoutWithText() {
    LayoutInflater  mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    //Inflate the layout into a view and configure it the way you like
    RelativeLayout view = new RelativeLayout(context);
    mInflater.inflate(R.layout.layout_with_text, view, true);
    TextView tv = (TextView) findViewById(R.id.my_text);
    tv.setText("Beat It!!");

    //Provide it with a layout params. It should necessarily be wrapping the
    //content as we not really going to have a parent for it.
    view.setLayoutParams(new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
                                          RelativeLayout.LayoutParams.WRAP_CONTENT));

    //Pre-measure the view so that height and width don't remain null.
    view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 
                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

    //Assign a size and position to the view and all of its descendants 
    view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());

    //Create the bitmap
    Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), 
                                        view.getMeasuredHeight(), 
                                        Bitmap.Config.ARGB_8888);
    //Create a canvas with the specified bitmap to draw into
    Canvas c = new Canvas(bitmap);

    //Render this view (and all of its children) to the given Canvas
    view.draw(c);
    return bitmap;
}
So what really does the work here? First of all, the measure method sets up the dimensions for the view and UNSPECIFIED mode for MeasureSpec dimension establishes that the parent view doesn't impose any constraint. The view can be whatever size it wants. So it locks up at wrapping the contents, i.e., exactly the size of the background drawable you might have provided, or upto the length of a text, or any other such blah blahs.... plus any margins specified. Then you create the bitmap with the measured dimensions and draw it on a canvas (doesn't really draws it on the screen. Think of it as a virtual canvas). Manually render this canvas on to our view. Annnnnd.......Here we go! That's it. Enjoy your bitmap... eat it up..

If you happened to bump into this post as a random reading, you might be trying to figure out what requirement could exhibit this situation. Well, it could be numerous. For instance, my particular requirement was to create Bitmaps from a custom layout dynamically to be drawn as Markers on Google Map v2. I was having to plot large number of markers, each with different text, and Google Map Api v2 requires a BitmapDescriptor as the icon for the marker, which in turn requires a Bitmap. So, i had to create these bitmaps at runtime.

I hope this works for your lot as well..
Cheers Droiders!!