Posts Tagged ‘layout’

TextDrawable: Draw Some Text!

You can find the full source code for TextDrawable and the sample project on GitHub.

The Drawable framework in Android is a neat and really flexible way to create portions of your UI.  Many times have I been able to simplify the view hierarchy or required resources just by getting creating with what a Drawable can do.  Recently, I had a need to place text into a Drawable so it could be inserted in places where the framework only allows Drawables to go.  So I created TextDrawable and though it share it.

If you would like to take a look at the TextDrawable source directly, head over to the GitHub link above.  This post deals mostly with the example application that wraps the implementation.

The Basics

Basically, TextDrawable is exactly that, a Drawable that displays a CharSequence.  It supports most all of the functionality you would find on Textview for setting and formatting text display.  It supports multi-line strings (line breaks, etc.) and is configured to have intrinsic bounds equal to the size required to draw the text contents as formatted.  This means that in most cases, you don’t have to explicitly call setBounds() on the object, it knows how big it wants to be…similar to working with Bitmaps.

With this class, text can now be part of the Drawable world, meaning it can not only be set alone in places where you would normally put an image, it can also be placed together with other Drawables in containers like StateListDrawable or animated with the likes of TransitionDrawable and ClipDrawable.  In many cases, we can use this to do a job that would otherwise require multiple views or compound controls just to achieve a given visual effect; thus it can reduce overhead in your view hierarchy.

Java Only

One of the major drawbacks of creating your own Drawable implementations is they cannot be inflated using XML (the Android team claims this is a security concern since this code is used by the core platform, so I wouldn’t expect it to change anytime soon).  This can make them more difficult to work with if you want to include a custom implementation inside a larger composite container (such as a state list or layer list).  Note that it is still possible, it just means you must do all your Drawable construction in Java.

Usage Examples

A simple example of using TextDrawable looks something like this:

ImageView mImageOne;
TextDrawable d = new TextDrawable(this);
d.setText("SAMPLE TEXT\nLINE TWO");
d.setTextAlign(Layout.Alignment.ALIGN_CENTER);

mImageOne.setImageDrawable(d);

This simply display the two-line string supplied, center-aligned, using the default text appearance settings from the application theme.

Path Drawing

TextDrawable does have one additional feature, in that you can also pass a Path object to it if you want the text to be drawn in a particular custom way (e.g. in a circle or along a curve).  In this case, the text measurement code cannot properly determine the size required, so TextDrawable will report no intrinsic size and you will need to call setBounds() with a size that appropriately matches the Path you have applied.

ImageView mImageThree;
TextDrawable d = new TextDrawable(this);
d.setText("TEXT DRAWN IN A CIRCLE");
d.setTextColor(Color.BLUE);
d.setTextSize(12);

Path p = new Path();
int origin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP40, getResources().getDisplayMetrics());
int radius = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP30, getResources().getDisplayMetrics());
int bound = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP80, getResources().getDisplayMetrics());
p.addCircle(origin, origin, radius, Path.Direction.CW);

d.setTextPath(p);
//Must call setBounds() since we are using a Path
d.setBounds(0, 0, bound, bound);

mImageThree.setImageDrawable(d);

In this example we apply a circular path to the TextDrawable on which to draw the text content.  Because we are using a custom path, and TextDrawable cannot measure its proper size, our application must also call setBounds() to the Drawable before handing it over to the ImageView.

Compound Drawable

Another very useful case for this is to place a small piece of static text in the top, bottom, left, right drawable locations on a TextView widget.  The following example inserts a short, non-editable, text prefix before whatever the user types into an EditText:

EditText mEditText;
d = new TextDrawable(this);
d.setText("SKU#");

mEditText.setCompoundDrawablesWithIntrinsicBounds(d, null, null, null);

If you want to use the same text in multiple locations, you can even set the same instance in more than one place.  Notice how we used the version of this method that expects intrinsic bounds.  If we had used a Path, we would need to explicitly call setBounds() and use setCompoundDrawables() instead.

Note: Since Button is also a Textview, you can use this technique there as well.

Dynamic Drawable

The following example wraps TextDrawable in a ClipDrawable for the purposes of animating the revealing of the supplied text in a more granular fashion rather  than just going letter-by-letter (i.e. typewriter style):

ImageView mImageFive;
d = new TextDrawable(this);
d.setText("SHOW ME TEXT");
ClipDrawable clip = new ClipDrawable(d, Gravity.LEFT, ClipDrawable.HORIZONTAL);

mImageFive.setImageDrawable(clip);

You can then animate this by calling setImageLevel() repeatedly on the enclosing container.

These are only some of the examples of what you might be able to simplify in your application UI by using text as a Drawable.

Framework Bug

In developing the examples for this, I came across a problem on ICS and Jelly Bean devices (and probably Honeycomb) having to do with hardware acceleration.  We’re all familiar by now with the fact that hardware acceleration is a great addition to the platform starting in Android 3.0 but that not all drawing primitives and features are quite yet properly supported there.  In many cases View, Activities, or entire applications need to disable hardware acceleration to keep compatibility with their graphics code…this is one of those such cases.  It seems that drawText() or one of its variants is not yet fully supported in hardware.

You may notice if you look at the example code posted with the TextDrawable implementation that some of the ImageView instances have a separate scaling mode applied to illustrate how TextDrawable responds to being matrix scaled.  The framework matrix scales images by transforming the Canvas on which they are drawn, and on older devices or with hardware acceleration disabled, this works great.  However, enabling hardware acceleration will cause the text in the scaled cases to get terribly pixelated.  I imagine in the future this will be corrected, but it’s behavior at least on these versions of the platform will forever be this way.

As is common in these cases, the solution is to add hardwareAcceleration="false" to your manifest or use setLayerType(View.LAYER_TYPE_SOFTWARE, null) on the View where you may be doing these transformations.

Tip: Resizing Issues

One thing to be aware of when using Drawables in a dynamic manner (i.e. changing their contents after they are set on a View) is that most widgets in the framework do not allow for a drawable to notify the host that it has been resized.  Most widgets like TextView and ImageView act as a callback for the Drawable so it can request, through invalidation, that it be redrawn.  however, in most cases this does not trigger the host View to re-check the Drawable bounds.  The initial bounds it calculated are those you are stuck with.  In many cases where we display Bitmaps, this is not a concern, however if you are expecting to dynamically change the text of a TextDrawable after it is attached, you will notice that text will simply be redrawn inside of the initial bounding box.

There are a few hacks to get this to work if that is your plan.  For example, ImageView does remeasure its Drawable content whenever setSelected() is called, so it is possible to force ImageView to resize the drawable with each change by calling setSelected(false) each time as well (assuming this doesn’t affect other code you may have in place).  The best solution, however, is to use the container Drawables in the framework whenever possible to create the dynamic effects you need.

So there you have it!  Now the next time you think to yourself “if I could just put this text into a Drawable…”; now you can!

Efficient Multi-Screen Resource Selection

Since its inception, one of the key elements of the Android platform has been the tools provided in its resource framework to select appropriate assets tailored to the user’s device type. Over time, this system has been added to here and there to include differentiators for different screen sizes, resolution densities, presence of different hardware items like keyboards, and so on. Undoubtedly, the most common use of this system is to create differentiation for different screen configurations (both size and resolution density). As the Android device landscape has grown, developers like myself have found themselves struggling to keep up with the additions Google is making to the SDK to allow applications to properly adapt.

Today, basically three main device classes exist that Android applications need to support: handsets (phones), small tablets (7″ category), and large tablets (10″ and above). Within these categories you will find minor differences in resolution, aspect, ratio, and physical size; but for the design of an application’s user interface, these three should remain the focus. In most cases, if your application is going to support both tablets and handsets, those user experiences should be quite different, and this often means that the layouts used for each Activity of Fragment will also be very different. On top of this, the difference in screen real estate between a small and large tablet may also require a slightly different layout between them.

Universal Applications and Size Qualifiers

Google still recommends that you should present your application as a single entity to the user that runs optimized for each device type when the user runs it, rather than creating separate applications for the handset and tablet. So to accomplish this, and to keep our code from being full of branches for different devices, we want to make use of resource qualifiers to create separate layout assets that the device chooses at runtime to best suit its configuration. Google’s first attempt at creating qualifiers to separate layouts by device was to create a handful of size “buckets”: small, normal, large, xlarge. Initially, this worked out well as handset devices were in the small/normal category, the first 7″ tablets on the market were large, and the first 10″ tablets were xlarge…everything had a place.

However, more and more devices began appearing in the market with new screen sizes and the lines began to blur about where a device might present itself. A new solution to the problem was presented in Android 3.2, in the form of new qualifiers to pick resources based on a devices current width/height or “smallest” width; the latter being a new designation for the devices size by representing it in terms of the width (in density-independent pixels, or dp) of the shortest side of the screen. Doing this qualification in terms of the “dp” of the screen is more conducive to layout design, as we have an inherent minimum constraint that the layout can expect from the device. Using the new paradigm, it has become common to define small tablets as having a smallest width of 600dp, and large tablets at 720dp, with handsets being everything that falls below that line.  For applications targeting Android 3.2 or higher as a minimum this new system also works well because everything still has a place.

There is one additional difficulty created by today’s marketplace.  Developers wishing to create an application that runs universal on handsets and tablets are compelled to support a minimum Android version no higher than 2.2, or at best 2.3.  In addition, tablet devices in the 7″ and 10″ form factor really started to come about with the release of Android 3.0 and many remain either at that version or 3.1…still below the version limit for the new qualifiers.

So what do we do?  If we want to support handsets at all, and preferably older tablets, we are forced to use the old size qualifiers.  But as new handset devices begin to commonly reach 5″ screens, we start to see handsets encroaching on the “large” qualification we are trying to reserve for tablets, so we need to leverage the new qualifiers on devices that can understand them.  We could just create duplicates of each resource in a directory with the old size qualifier and place it an another directory with a corresponding smallest width qualifier.  It is common also that dimensions or other resource items will need to be specified differently for tablet and handset.  This may lead us to a directory structure like the following:

We place any layouts that are common to all devices and layouts that should be displayed only on handset devices in the default directory.

res/layout/
  main.xml
res/values/
  dimens.xml
  strings.xml

If our application needs some additional layouts and resources to create tablet UI separate from handset, we add the *-large and *-sw600dp directories and place those files there (the same layout files in each place).  The reason we need to have them defined in both is so the devices running pre-3.2 and 3.2+ can both find the correct resource.

res/layout-large/
  main.xml
res/values-large/
  dimens.xml
res/layout-sw600dp/
  main.xml
res/values-sw600dp/
  dimens.xml

If there are any further layouts or resource that need to be specified differently for large tablets versus small tablets, we can add the *-xlarge and *-sw720dp directories as well.

res/layout-sw720dp/
  main.xml
res/values-sw720dp/
  dimens.xml
res/layout-xlarge/
  main.xml
res/values-xlarge/
  dimens.xml

However, you can probably already see that this creates a maintenance issue for the application as almost every resource asset has to be maintained twice.  There is also a secondary problem in that all the files have the same name, even though they represent different layout content, and in some IDE tools this can become confusing as to which file you are working with.

Reducing Duplication

In order to reduce the number of resource copies, we can employ a technique called aliasing.  This involves placing the resource items themselves into a common directory (meaning they have different names, usually describing their function), and then placing links that represent each resource with its common name into qualified directories.  This reduces the number of directories we need since the layout aliases can fit into the values-* directories with other qualified resources.  So, for example, we now have the following

res/layout
  main.xml
  main_smalltablet.xml
  main_largetablet.xml
res/values/
  dimens.xml
  strings.xml
res/values-large/
  layout.xml
  dimens.xml
res/values-sw600dp/
  layout.xml
  dimens.xml
res/values-sw720dp/
  layout.xml
  dimens.xml
res/values-xlarge/
  layout.xml
  dimens.xml

The layout resources for handset (and any future small-screen device we didn’t anticipate) is named properly directly in the default directory.  It is always good practice to keep as much in your default locations as possible to avoid a ResourceNotFoundException on a device some day in the future.  The layout.xml files in the qualified directories contain the aliases, and look like the following:

res/layout-large/layout.xml
res/layout-sw600dp/layout.xml
<resources>
    <item name="main" type="layout">@layout/main_smalltablet</item>
</resources>
res/layout-xlarge/layout.xml
res/layout-sw720dp/layout.xml
<resources>
    <item name="main" type="layout">@layout/main_largetablet</item>
</resources>

These files normalize the other two layouts to have the same R.layout.main resource identifier on larger screen devices.  You can see that we still need to copy the alias file between the size qualified and smallest-width qualified directories, but this duplication is much easier to manage; we still only have one layout file to maintain.

This approach, and the one previous, both still suffer from one increasing problem: the *-large directories are considered a valid resource qualifier on 5″+ handset devices.  The smallest width qualifier may have higher precedence than the size qualifier, but any matching qualifier will win over a default selection…which is what we want all handsets to use.

A Proposed Solution

Taking all of this into account, the following is the approach I have chosen to take moving forward…

One Final Assumption

I made note of the fact that Android tablet devices smaller than 10″ were not prevalent in the market in until after Android 3.2; the original Galaxy Tab 7 is the only major device in this category running Android 2.2 and its sales were less than spectacular.  So to combat the issue of devices encroaching on “large”, we can remove this qualified directory with a minimal impact.  Any 7″ device running the application on pre-3.2 software will simply load the handset UI (less than ideal, but also the lesser evil).  For larger tablets, however, the device population running Android 3.0 and 3.1 is significant enough that using the older qualifier makes good sense.

The Compromise

Therefore, on most applications, where layout resources for all tablets are the same and no special distinctions need to be made between large and small, I use the following:

res/layout/
  **ALL layout resources, named appropriately**
res/values/
  **Default resource values**
res/values-sw600dp/
  **Tablet specific aliases and resource values**

In this paradigm we need not duplicate any resources or even aliases to resources, and the casualties in terms of devices not optimized is minimal.  In cases where special adaptions need to be made for larger tablets only, I add back in the xlarge and sw720dp buckets:

res/layout/
  **ALL layout resources, named appropriately**
res/values/
  **Default resource values**
res/values-sw600dp/
  **Tablet specific aliases and resource values**
res/values-sw720dp/
  **Large tablet specific alises and resource values**
res/values-xlarge/
  **Copy of large tablet aliases and resource values**

This reduces the copied resources down to a single set, and the layout copies are only aliases.  Until such time when we can remove support for device versions that do no recognize the new qualifier system, this approach is a good compromise that covers most devices in use and is still easy to maintain.

Adapting to Empty Views

Android has a pretty flexible framework for displaying data from a database, array, or other list structure using classes called Adapters.  There are plenty of great tutorials in the Android docs and throughout the web on how to use adapters to create custom behavior for display this data, so I’m going to focus on it’s less-discussed couterpart; the AdapterView.

Adapter Basics

Android uses an Adapter class to bind data from a data structure (like a database Cursor or an array) to a view that is specially designed to dynamically display the data as it moves and changes…this is the AdapterView.  There are many common subclasses of AdapterView (ListView, Spinner, Gallery, etc.), but they share the common function without any special overriding that I will discuss in detail today.

The Empty View

All AdapterView subclasses have a property that maintains a reference to a View object known as the empty view.  The point of this view is to be displayed when the Adapter bound to our data tells the AdapterView that the data structure has no data.  It does this either by just returning zero from getCount(), or through a more complex implementation of isEmpty().  Conceptually, it is then the role of the AdapterView to replace the view reserved for displaying content with this View, if it is not null.

Setting the Empty View for an AdapterView

You may call at any time AdapterView.setEmptyView(View v) on an AdapterView object to attach a view you would like to use as the empty view.  You may also call AdapterView.getEmptyView() at any time to verify if an empty view has been set.  That’s it…sort of.

There’s a trick…

As of this writing, one of the things the Android documentation doesn’t tell you is that the AdapterView simply manipulates the visibility properties of your content view and empty view based on the information it receives from the Adapter.  After determining which view to display, it simply calls setVisibility(View.VISIBLE) on that view, and calls setVisibility(View.GONE) on the other.  This is efficient and works just fine, but it requires that you create both views and insert them into a visible layout yourself.  AdapterView will not do this legwork for you.  You cannot assume that supplying an empty view to AdapterView means that it will insert that view in it’s place in the layout at runtime…

Example, please!

Let’s examine the basic ListView example to get a grip on things.  When you place a ListView in your layout per the SDK tutorials, it will look something like this:

File: main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layout1"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ListView
    android:id="@+id/list1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:drawSelectorOnTop="false"
  />
  <TextView
    android:id="@+id/empty1"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:text="Nothing in the List!"
  />
</LinearLayout>

and the setup code in your activity would be like this:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ListView list;
TextView empty;

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
 
  empty = (TextView)findViewById(R.id.empty1);
  list = (ListView)findViewById(R.id.list1);

  list.setEmptyView(empty);

  //...code to create and set an adapter of your choice...
}

The key here is this works because both the ListView and it’s “empty view” are defined as children of the displayed root layout, so they are both laid out when the Activity is displayed.  Because of this, AdapterView’s method of simply manipulating visibility works perfectly.  Let’s modify the example now so that the layout XML looks like this:

File: main.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layout1"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ListView
    android:id="@+id/list1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:drawSelectorOnTop="false"
  />
</LinearLayout>
File: empty.xml
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<TextView
  android:id="@+id/empty1"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:gravity="center_horizontal"
  android:text="Nothing in the List!"
/>

with the same setup code in our Activity.  Now, the layout process is not handling the setup and inflation of our “empty view”.  When our list is empty, AdapterView will manipulate the visibility of the two view objects…so the content view will become invisible.  However, with the empty view not attached to our layout, it will not show up anywhere on the screen (even though it has been set to View.VISIBLE)! In order for this second example to work properly, you would have to modify the Java code as follows:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ListView list;
TextView empty;

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
 
  empty = (TextView)findViewById(R.id.empty1);
  list = (ListView)findViewById(R.id.list1);

  //New code to add the empty view to the visible layout
  LinearLayout rootLayout = (LinearLayout)findViewById(R.id.layout1);
  rootLayout.addView(empty, 0);

  list.setEmptyView(empty);

  //...code to create and set an adapter of your choice...
}

Take-home reminder: AdapterView does nothing more for you than manage visibility between a content view and an empty view. You are responsible to making sure those views are laid out in the appropriate locations prior to utilizing this functionality. You could also subclass the AdapterView you are using to handle it, but many AdapterView classes are not as straightforward as you might think. I would highly recommend reading the docs and the Android source thoroughly before doing so.

Looking Forward

Knowing this about AdapterView is actually a bit exciting because it means we have yet even more flexibility at our fingertips.  This means that we are not forced to have the AdapterView content and empty views occupy the same space.  We could define our content at the top of the screen (like an image gallery), and display the empty view somewhere else, or even on top of the entire screen (like a popup saying “nothing to see here, move along”) restricting access to other UI elements in the view.

Android Layout Relationships

Developing UI layouts for Android is an experience unlike any other.  I have developed for both desktop and mobile on a variety of platforms, and the XML structure that Android uses is quite unique.  This can bring with it some issues and challenges as it runs contrary to the way many people think about designing their UI.  Today, I’m going to make some comments on questions that I see constantly regarding the XML keywords most commonly misused in creating layouts.

I’m going to focus on developing using XML, but the same rules apply when creating LayoutParams in code.

Me Or My Parent?

When laying out elements (or widgets) on the screen, it usually doesn’t take long for people to realize that there are two of many of the basic parameters associated with size and location.  Options like width, height, and gravity have parallel values named layout_width, layout_height, and layout_gravity; so what gives?  Parameters with the prefix layout_ are actually instructions that are to be passed to the PARENT of the object that they are placed on, while all other parameters are passed to the object itself.

This can be a source of confusion even when you want to do something as simple as center some text, so let’s look at an example.  Imagine we have a simple layout like the one listed below:


1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="My Text Content"
  />
</LinearLayout>

We placed the constraint layout_gravity=”center_horizontal” so that the text would be centered on the screen, but if this layout gets rendered, the results are not as expected!  Why?  Well, in the example the TextView told the following to its parent LinearLayout:

  • Make me as wide as you are (layout_width=”fill_parent”)
  • Make me just tall enough to fit the text inside (layout_height=”wrap_content”)
  • Lay me out centered horizontally (layout_gravity=”center_horizontal”)

Points #1 and #3 are actually in direct opposition to each other.  The TextView has asked to be centered within the LinearLayout, but also be just as wide (so there is really no room to center it).  What we really wanted to do was to either:

  1. Tell the TextView ITSELF to center its own contents horizontally within the view bounds
  2. Tell the TextView to be JUST as wide as its text, and them center the whole view horizontally in the parent.

Example Fix #1


1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:text="My Text Content"
  />
</LinearLayout>

Example Fix #2


1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="My Text Content"
  />
</LinearLayout>

Bottom Line: Remember that any parameter that starts with layout_ is actually an instruction for the parent view, and not the view where it is located!

Android Dialog, or is it?

We’re all familiar with the concept of providing a temporary modal view to the user in an application for the purposes of notification or basic data entry. In the Android framework (as in many computer systems) a class is provided to the developer to create and maintain these views called a Dialog. This class is then further extended for you to make some convenient versions that allow entry of date/time information and make basic list selections (AlertDialog, TimePickerDialog, etc.). These classes work really well, and I would encourage you to leverage them whenever possible.

But what if you want to use the Dialog interface, but you have some custom data entry needs? Android makes it pretty simple to extend the base Dialog class and create custom behavior with a custom layout, and many people would consider this as the next logical option if they can’t solve their problem with one of the canned Dialog solutions. I’m here to tell you today that there is a better way.

The Problem

There are some subtle, but fairly major issues with the custom Dialog classes and state persistence. By design, the Dialog does not have the same functionality available to it as the Activity for life-cycle management. In particular, this includes managing device orientation changes, which (by default) completely breaks down the Activity and rebuilds it for a new configuration in Android. This is exacerbated by the realization that, in many Android devices, rotation is required EXTREMELY often after an entry dialog is shown because the user will want access to the keyboard to enter the data! If you’re dialog doesn’t rotate, your app is broken to the user.

So what’s the issue?  Do some experimentation with this and monitor LogCat some day.  As soon as you do an orientation change with an active dialog, you will get an android.View.WindowLeaked exception from the WindowManager.  This occurs (usually) because of reference conflict between the new activity that gets created, and a reference existing to the previous version of the dialog when you try to show it again.  This is actually pretty difficult to manage, even with managed dialogs using the Activity (although, here’s a hint: dismissing a dialog and destroying it are not the same thing). The canned dialogs available in the framework do not have this problem, which is why I mentioned using them whenever it fits.

The other “problem” with this implementation is that the Dialog class doesn’t get a passed Bundle or other framework inside of which to persist data (like anything the user STARTED to do before they rotated) like the Activity does.  This means a lot of passing data back to the calling Activity so it can manage the persisted data.

…This is all doable, but inefficient.

The Solution

Short Answer: The Dialog Theme for the Activity class.  Personally, the only thing that I REALLY like about the dialog class itself is the presentation method.  I like how it partially covers and dims the current activity which, conceptually, links it to the Activity in the user’s mind.  But everything else on the back end is MUCH better handled with an Activity:

  • startActivityForResult() and finish() are simple replacements for showDialog() and dismiss()
  • Data can be passed to and from the modal session using Intents (a great habit to get into anyway)
  • All the state persistence exists in the framework to handle rotation, like onSaveInstanceState() and the passed Bundle

Plus, creating an Activity class isn’t any more work than a custom Dialog.  Both are going to need the Constructor ( or onCreate() ) to call up super and then set the view to a custom XML layout file to set themselves up.

So the only missing piece is the Theme.  The one extra thing that you must do when you create an Activity instead of a Dialog is you must declare it in your Manifest so the system knows it exists when it comes to things like Intent resolution.  Coincidentally, the Manifest is also where you would set the dialog theme.  Take a look at the example below:


1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...>
    <application>
        <activity android:name=".HomeActivity">...</activity>
        <activity android:name=".NiceDialog"
                     <strong>android:theme="@android:style/Theme.Dialog"></strong>...</activity>
        ...
    </application>
    ...
</manifest>

Here, the Activity NiceDialog has been skinned to look just like a dialog would using the theme attribute. It now will wrap the content tightly and only partially cover the view of the calling Activity. Now the user is none the wiser, and you get all the benefits of Activity life-cycle management from your custom Dialog.

Android Layouts Supporting Orientation

It’s been my experience that most developers don’t want to deal with screen rotation in their applications. They will either specifically code the app not to respond to rotation events (which can look weird when you have to slide the screen and use the keyboard), or they will forget that there are instances where the screen orientation rotates for you by default (and their portrait-centric layout goes nuts or gets cut off). This is one of those subtle details that can drag down the user experience in a big way if the developer is not careful.

If your platform of choice is Android, my only question if you’re afraid of screen rotation would be…why?  Android makes dealing with different orientations in your views very simple.  If you’ve opted to define your layouts with XML (which you probably should anyway), then all that is required is a second XML file defining the second orientation (probably landscape), and some resource syntax to describe to Android how and when to choose the proper layout.  Defining layouts in XML is so simple, adding another doesn’t requre significant effort.  The general steps are as follows:

  1. Create two folders in the resource bundle (“res” folder in the project): “layout” and “layout-land”
    • “layout” will hold all the XML layouts for portrait mode
    • “layout-land” will hold all the XML layouts for landscape mode
  2. Create an XML layout in both folders with the SAME name (i.e. main.xml)
    • There will now be a “res/layout/<name>.xml” and a “res/layout-land/<name>.xml”
  3. Build each layout to match the proper screen orientation
  4. POOF! You’re done.  Android will handle the rest at runtime.

So what’s happening?  Well, “land” is an alternate resource that Android recognizes for screen orientation.  Creating the resource directory “layout-land” tells Android specifics about how the contents of the directory are to be used…specifically layouts for landscape.

Let’s look at a quick example:

Let’s say I have a layout with four image buttons.  In portrait mode, I want those buttons to block in a 2×2 arrangement.  However, in landscape mode, that block runs off the screen, so I want them all in a single row.  I will define two layouts:

res/layout/main.xml

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
        <TableRow>
            <ImageButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/image1"/>
            <ImageButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/image2"/>   
    </TableRow>
    <TableRow>
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/image3"/>
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/image4"/>
    </TableRow>
</TableLayout>

res/layout-land/main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image1"/>
    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image2"/>
    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image3"/>
    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image4"/>
</LinearLayout>

Whenever the main.xml view is inflated, it will display either the TableLayout version or the LinearLayout version, depending on the screen orientation.