Showing posts with label UI. Show all posts
Showing posts with label UI. Show all posts

Wednesday, August 4, 2010

Easy method for formatting Android TextViews

Android TextViews don't have an easy method for changing the style of a substring of its text. You may have wanted to do something like textView.setTextColor(Color.RED, 10, 20); in order to set the 10th to the 20th characters red. I'll show you a method of making this easy to do; not just with colors, but with all sorts of styles.

Using regular HTML tags in strings

You do have a limited option to format your strings without any programmatic methods. In strings.xml, where you define your strings, you can use the simple HTML format tags <b>, <i>, and <u> for bold, italics, and underlining, respectively. For example,
<string name="text1">This text uses <b>bold</b> and <i>italics</i> by using inline tags such as <b> within the string file.</string>
A TextView with this string will appear as: This text uses bold and italics by using inline tags such as <b> within the string file.
Unfortunately, you cannot use any more HTML tags than that. In order to have some nice inline styles, we'll need to use CharacterStyles. This class has many sub-classes, and allows you to do everything from changing the text appearance (like ForegroundColorSpan, to more complicated things like making clickable links (ClickableSpan) and images (ImageSpan).

CharSequences

A quick note about how this works. Skip to the next section if you don't care. TextView, along with many other Android classes which use formatted text, don't just use simple Strings. They use CharSequences. Get this: a CharSequence is more abstract than a String; a String is a sub-class of CharSequence. A CharSequence defines a series of characters, such as a regular string, but it could also define a series of characters with formatting, such as a SpannableString. Internally, what we will do to change the middle of a TextView's text is to add spans to its text. More precisely, we will add CharacterStyles to the TextView's CharSequence (text), which is a SpannableString.

Format text dynamically

Here's a handy utility method which will take in some text, and return the text formatted. The key is to surround the text you want with tokens such as "##". The tokens will be removed in the returned result.
For example, if a TextView has its text set as "Hello, ##world##!" and you want world to be in red, call setSpanBetweenTokens("Hello ##world##!", "##", new ForegroundColorSpan(0xFFFF0000)); will return the text Hello, world! with world in red. Note that you can send multiple spans as parameters to this method.

/**
 * Given either a Spannable String or a regular String and a token, apply
 * the given CharacterStyle to the span between the tokens, and also
 * remove tokens.
 * <p>
 * For example, {@code setSpanBetweenTokens("Hello ##world##!", "##",
 * new ForegroundColorSpan(0xFFFF0000));} will return a CharSequence
 * {@code "Hello world!"} with {@code world} in red.
 *
 * @param text The text, with the tokens, to adjust.
 * @param token The token string; there should be at least two instances
 *             of token in text.
 * @param cs The style to apply to the CharSequence. WARNING: You cannot
 *            send the same two instances of this parameter, otherwise
 *            the second call will remove the original span.
 * @return A Spannable CharSequence with the new style applied.
 *
 * @see http://developer.android.com/reference/android/text/style/CharacterStyle.html
 */
public static CharSequence setSpanBetweenTokens(CharSequence text,
    
String tokenCharacterStyle... cs)
{
    
// Start and end refer to the points where the span will apply
    
int tokenLen = token.length();
    
int start = text.toString().indexOf(token) + tokenLen;
    
int end = text.toString().indexOf(tokenstart);

    
if (start > -1 && end > -1)
    {
        
// Copy the spannable string to a mutable spannable string
        
SpannableStringBuilder ssb = new SpannableStringBuilder(text);
        
for (CharacterStyle c : cs)
            
ssb.setSpan(cstartend, 0);

        
// Delete the tokens before and after the span
        
ssb.delete(endend + tokenLen);
        
ssb.delete(start - tokenLenstart);

        
text = ssb;
    }

    
return text;
}

Clickable spans

One of the spans you can set your text to is a ClickableSpan. You may have added this and wondered why anything didn't happen when you clicked on the link. Well, you need to have one extra step to let Android know that there is a clickable link and it needs to be navigated to. You need to set a MovementMethod to the TextView. You can investigate this further if you wish, or you can just see the sample code to make it work below (and it is also in the sample):


// Adapted from Linkify.addLinkMovementMethod(), to make links clickable.
//
MovementMethod 
m = textView.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod))
{
    
textView.setMovementMethod(LinkMovementMethod.getInstance());
}

Sample Application

The sample application is a simple application with a few TextViews and a Button. The first TextView shows how you can set a TextView's text just by using HTML tags in strings.xml. The second TextView demonstrates how to change text dynamically, using the above utility method. When the button is clicked, it will first set some text red using a ForegroundColorSpan. Second, it will set some text bold and italics using a StyleSpan. Third, it will make a generic link by setting some text to blue, underlining it (UnderlineSpan), and then creating an onClick method which executes some custom code using a ClickableSpan. The final click demonstrates both a ForegroundColorSpan and a RelativeSizeSpan.


Project source code - formattedtext.zip (7.22 Kb)

Thursday, June 17, 2010

Using Themes in Android Applications

As a developer, I understand how developers think when going about creating an application.  We start with an idea, with an end goal in mind, and start putting together pieces from the bottom-up until we reach our goal.  Like any engineer, we enjoy playing with the individual parts, tinkering with building blocks, until we have put them together to create something fantastic that works.  As much fun as this may be, experience shows that throwing together these building blocks without using a proper framework to build them is leads to bad designs, and applications which are difficult to maintain and change later on.

When it comes to design, we don't think in the same manner as a more artistically inclined designer would.  We work from the bottom up, and they work from the top down.  They will work with the entire endeavor as a whole, seeing the final product working without bothering with the individual nuances of the sum of its parts.  In their mind, those parts just work; in the engineer's mind, we think of how we can get those parts to work.  I believe this could be said about many types of work involving engineers and artists, like websites and buildings.

If we, the software developer, use too many individual parts to create our app that have been molded on their own, without concern for the other parts they will be fitting into, it is much harder to make them fit in the first place, and especially to later make changes to them. Besides, the less moving parts the better; as a software developer, it is simply good design to create your user interface with building blocks which conform together instead of changing numbers, colors, etc. here and there to force everything to eventually look good.

What I am dealing with here is UI; the graphical representation of your app. More specifically, UI design in Android applications.

Use Styles in UI

In the above screenshots from the sample application, you can see the exact same layout rendered differently by applying different themes.

In Android, you use Views to design your Activities.  An Activity is the central class which handles a specific, well, activity, in your application.  The class which handles the visual representation of activities is View. The Views themselves can be created programmatically within the Activity, but one should only do so in rare circumstances.  The better way to define them is using XML layout files.

The Android SDK for Eclipse gives us a nice visual editor to design views, which directly manipulates the XML for us.  The layout editor does not, unfortunately, give an easy way to customize the look and feel of the application.  By look and feel I mean the colors, the margins, the fonts, and any attributes that contribute to how all of the views display.

Remember in your beginner computer science classes learning about constants, and why you should use them?  One reason was that if you use a constant, you can reuse it in many places, but only have to change it one place.  In the same manner, we should be using constants to define the way our application looks, so we can easily change it from one central place, in order to change the entire application.  (On a side note, if you've ever dealt with creating web pages, you probably have realized that it is far better to use CSS than to change the attributes of individual elements of your HTML pages).

Creating Themes

Finally, onto the real meat of how to use themes!  What I will show you here is how to define a theme in your resources in an XML file, how to define attributes of the theme, how to apply those to your layout files, and finally how to dynamically change the theme of an activity.

Create a file themes.xml in res/values/.  Here you will create the name of your theme, and any customizable attributes you would like for your theme to define.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    
<style name="Theme" parent="android:Theme">
        <item name="android:windowTitleSize">20dip</item>
    
</style>
    
</resources>

Above you see I have created a theme called Theme, whose parent is the default Android theme. There is a custom attribute called pageMargin, and also a default OS attribute android:windowTitleSize which has been overridden. I recommend you set the theme's parent to the default Android OS theme, in order to inherit all of the system attributes. Otherwise, unless you only use your own custom views, the system views such as ListView will be missing attributes which are defined in the system theme which are not defined in your own theme. This will cause funny layout issues at best, but at worst it will cause exceptions due to attributes that the views could not find. Remember, not all Android devices are built the same.

Setting the Theme

We can set the theme of either our application or individual activities in the manifest file.  If you don't need to have more than one theme at all in your application, just set the theme of the activity.  Otherwise, you can define themes specific to an activity.

<application
    
android:icon="@drawable/icon"
    
android:label="@string/app_name"
   
 android:theme="@style/Theme">

You can also set the theme of an activity programmatically.  This is done by calling setTheme() in the activity's onCreate() method, before any call to setContentView().  Note that this approach should typically be avoided, especially from the main activities of your application, because the theme you set here may not be used for any animations the system uses to show the activity (which is done before your activity starts).  The exception to this rule is if you want to set your themes dynamically, such as in the case where the application can have more than one theme.

Custom Attributes

Although it is nice to be able to override the default system properties in some cases, what we'd really like to do is define custom properties of our own in our application's layouts.  Say we wanted the margins of all of our activities to be a certain dimension.  If we define it in one place, we can change it all at once. Below is an example of a custom attribute added to our custom theme in themes.xml which we can use to define a property called pageMargin:

        <item name="pageMargin">2sp</item>

If you simply copy the above text into your new file themes.xml, you will get a build error Error: No resource found that matches the given name: attr 'pageMargin'.  This is because we have not defined what pageMargin is to the build system.  android:windowTitleSize is OK, because it is already defined in the Android SDK.

Create a file called attrs.xml in res/values/.  Here you will create your style attributes, which are any customizable attributes you would like for your theme to define.  Below, I have created a new attribute called pageMargin, which I will use to define margins.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
<attr name="pageMargin" format="reference|dimension" />
</resources>

Now, the build system will not throw an error anymore because pageMargin is now defined. The format attribute indicates what type of values I can define for pageMargin; in this case, either a reference to another attribute, or a dimension such as 2sp or 4px.  Other examples of possible formats are color, boolean, integer, and float.

Now, I could set the margins in my views by referencing a single constant, instead of putting the same value piecemeal around the code:

<TextView
    
android:layout_width="fill_parent"
    
android:layout_height="wrap_content"
    
android:text="Hello"
    
android:layout_margin="?pageMargin"/>

I can change pageMargin in one place and all of the margins in my views which reference this attribute will be changed.  Unfortunately, this method of defining your view's styles has some issues.  If you open this view in the Eclipse layout editor, you will see an error: Unable to resolve dimension value "?pageMargin" in attribute "layout_margin".  In order to make any use of these attributes, we need to apply the theme to our activities, and this doesn't work in the layout editor.
If you'd like to see some more examples, you can look at the Android OS source code default res directory, in particular themes.xmlstyles.xml, and attrs.xml.

Styles

A more sophisticated method to setting the properties of your views in the layout is give a view a style, which is a group of attributes, instead of defining the values of individual attributes. For example, you could set the styles of all of your title TextViews to have the style textTitle.  This style could have custom text color, font, and margin properties. In addition, the layouts can be safely edited in the Eclipse layout editor, because the style attribute is not needed to render the views.

    <TextView
        
android:layout_width="fill_parent"
        
android:layout_height="wrap_content"
        
android:text="@string/title_text"
        
style="?textTitle"/>

Now this TextView has a custom style. You can assign multiple attributes to the textTitle style, such as android:textColor and android:textSize.

When using the style attribute in your layout files, the views will appear plain when using the Eclipse layout editor.  However, when the theme is applied in your application, your custom styles will be applied.

Dynamic Themes

If you want your application to have a customizable look and feel, then you will want to create multiple styles. An example of this is given in the sample application below. To have multiple themes, you will want to create multiple theme definitions in themes.xml.

    <style name="Theme.White">
      
<item name="textTitle">@style/text_title_wh</item>
      
<item name="textSubheader">@style/text_subheader_wh</item>
    
</style>

    <style name="Theme.Blue">
      
<item name="textTitle">@style/text_title_bl</item>
      
<item name="textSubheader">@style/text_subheader_bl</item>
    
</style>

To set the theme dynamically at runtime, call setTheme() in your activity's onCreate() method, before calling setContentView().  To change the theme, you simply need to restart your activity.
If you have a theme name with a dot in it, such as MyTheme.Blue, then you will create a theme called MyTheme_Blue whose parent is the theme MyTheme. It is a convenient way to inherit the properties of a parent theme.

Sample Application

This sample program shows how to use custom themes. The application has one Activity with one corresponding layout file. Most of the views in the layout have a custom style. You can change the style dynamically by pressing the buttons at the bottom of the screen.

Project Source Code - Themes.zip (16.4 Kb)
Project Package - Themes.apk (28.1 Kb)
Screenshots

Conclusion

1. Define one or more themes in themes.xml and set the definitions of your styles there.
2. Define custom attributes, a.k.a. custom styles, in attrs.xml.
3. Describe what the values of your custom styles are in styles.xml.
4. In your layout files, give your views a style attribute, which has a custom style name as their values.
5. Set the theme of your application or activity in either AndroidManifest.xml or in the Activity's onCreate().

As an Android application programmer you should now know how to stop placing look and feel attributes directly into your layout files and start using styles to design your UI.