Handling Android resources with non-standard formats
In Android you have to deal with a large variety of screen sizes. The current solution is to use adaptive design to handle all the screens.
One concept of adaptive design is that you have ever-growing content frames... up to a point. Once the device is too large or wide, you setup margins instead so that your app doesn't look ridiculous.
In the above example (from Trello), we use the full screen width for cards on smaller devices, but once we get to tablet-sized devices we treat it like a dialog.
That's all well and good, but how do you actually go about implementing it?
An initial attempt might be to do this:
<FrameLayout android:id="@+id/content" android:layout_width="@dimen/responsive_width" android:layout_height="match_parent" />
The idea is that
responsive_width should be
match_parent when the screen is small, but a specific width (say,
800dp) when the screen gets wide enough:
<!-- Inside /res/values/dimens.xml --> <dimen name="responsive_width">match_parent</dimen> <!-- Inside /res/values-sw800dp/dimens.xml --> <dimen name="responsive_width">800dp</dimen>
You'll quickly discover this doesn't work:
String types not allowed (at 'responsive_width' with value 'match_parent').
The core problem is that
match_parent is an enum constant for a particular attribute. It's defined only in the context of layout width or height, so a generic dimension field doesn't know how to interpret it.
You can find the definition of these enums in
attrs.xml of the platform source code. For example, here's
<attr name="layout_width" format="dimension"> <enum name="fill_parent" value="-1" /> <enum name="match_parent" value="-1" /> <enum name="wrap_content" value="-2" /> </attr>
Interesting - what happens if we use its underlying constant value (which, in this case, is -1)?
Nice try, buddy! You'll get this error message instead:
Integer types not allowed (at 'responsive_width' with value '-1').
The core problem is that
<dimen> is essentially shorthand for this:
<item name="responsive_width" type="dimen" format="dimension">800dp</item>
item is a more generic resource, where you can define the
format separately. Notice how, in this case, we're specifying the
format to be
dimension. This is why the compiler rejects string and integer values; they're not formatted correctly.
It turns out you don't actually have to be that strict; you can leave that out and it'll still work.
Let's try defining both
wrap_content without a
<item name="match_parent" type="dimen">-1</item> <item name="wrap_content" type="dimen">-2</item>
We can reference those in any other dimension value:
<!-- Inside /res/values/dimens.xml --> <dimen name="responsive_width">@dimen/match_parent</dimen> <!-- Inside /res/values-sw800dp/dimens.xml --> <dimen name="responsive_width">800dp</dimen>
Ta-da! Now your responsive width is easy to implement.
I wouldn't go hog wild with this trick because using constants indirectly means you lose some type-checking. But in some cases, it's the best solution because it can save you from having to write multiple layout files for attributes you could've otherwise been switching in resources.
This trick isn't limited to just enums;
<item> can be used anytime you want a resource with a special format.
View has the attribute
alpha, defined thus:
<declare-styleable name="View"> <attr name="alpha" format="float" /> </declare-styleable>
But if you try this you get an error:
Error:(6, 25) Float types not allowed (at 'view_alpha' with value '.5').
Again, we can use
<item> to better define our dimension:
<item name="view_alpha" type="dimen" format="float">.5</item>
Now we can use it in a
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/click_me" android:alpha="@dimen/view_alpha"/>
(What's weird here is that you can actually use any
type for the item, as long as it's one you can reference in code. I could have used
string or even
bool above and it would still work. Again, it makes me a bit hesitant to code minus the type checking, but AFAIK there's no other way to do it...)
The important pattern here is to know the format of a View's attributes. Yet again the Android open source project saves the day: I highly recommend reading through the system's
attrs.xml to find attribute formats when you're working with
item. That way you can confirm that what you're creating matches the expected format for the attribute.