Statically Typed

because Hindley-Milner rocks

Android: Dynamic and Custom Title Bars


Arguably the worst part in playing around with Android is its insistence to put that ugly title bar above everything I do as a default to my Activities.  Really, Google, was that necessary?  I think you could have done better and I know that I can do better.  Thankfully you gave us free reign to substitute a custom variant or edit it out altogether.    This post is going to explore the ways in which we can create custom title bars and more importantly just how far we can push the limits (of bad taste?)

This post is going to go into the internals of some of the Android classes and platform.  We’ll focus our attention on things related to the Title Bar.  If that isn’t of interest to you there are several Q&A/Blog posts that could very easily be of interest:

They’ll provide you with some ideas and inspiration but they won’t give you the “why” it works that way.  Why almost always takes more time than most people are willing to spend.  Hopefully, I can spend that time for you.  Please feel free to correct me when I’m wrong (my ego isn’t large enough to not welcome criticism.)

Theme and Style Attributes

At the basic level Title bars are controlled by a combination of Style and Theme parameters.  Styles influence Layouts and Themes, Activities.  This distinction is important and good programming practice should promote separation of concerns; Android developers are no exception.

To understand how the Title Bar is laid out in an unmodified, plain, vanilla application look first at the standard Theme found in the Android kernel repository for Themes.  The following three attributes containing the word “Title” stand out in the “Theme” Theme:

<item name="windowTitleStyle">@android:style/WindowTitle</item>
<item name="windowTitleSize">25dip</item>
<item name="windowTitleBackgroundStyle">@android:style/WindowTitleBackground</item>

Following the references from the default Theme to the Android kernel repository for Styles, we first find the “WindowTitle” Style:

<style name="WindowTitle">
    <item name="android:singleLine">true</item>
    <item name="android:textAppearance">@style/TextAppearance.WindowTitle</item>
    <item name="android:shadowColor">#BB000000</item>
    <item name="android:shadowRadius">2.75</item>
</style>

The attributes “singleLine,” “shadowColor,” and “shadowRadius” imply that the Title Bar is nothing more than a TextView.  This is further backed up by the attributes in the “DialogWindowTitle” in the next Style group:

<style name="DialogWindowTitle">
    <item name="android:maxLines">1</item>
    <item name="android:scrollHorizontally">true</item>
    <item name="android:textAppearance">@style/TextAppearance.DialogWindowTitle</item>
</style>

For completeness, the “WindowTitleBackground” Style:

<style name="WindowTitleBackground">
    <item name="android:background">@android:drawable/title_bar</item>
</style>

contains a reference to a drawable.

As I said at a basic level this is what controls the default look and feel of the Title Bar within an Android App.  Anything that can be done within the XML resources of a TextView can be done in the Style attributes to yield something different from default.  You will have to write your own Theme which makes use of the new Style and tie it in the manifest file.

Dynamically Updating the Default Title Bar

The Android developers at Google realized people using their SDK might have the need to modify the text or the text color of the Title Bar.  Activity provides four methods, two mutators and two accessors handling the text and text color local state.  Changing either property of one Activity will not impact other Activities of the application.  To prove the point look at the Activity code on the Android git repository:

public void setTitle(CharSequence title) {
    mTitle = title;
    onTitleChanged(title, mTitleColor);
    if (mParent != null) {
        mParent.onChildTitleChanged(this, title);
    }
}

public void setTitleColor(int textColor) {
    mTitleColor = textColor;
    onTitleChanged(mTitle, textColor);
}

public final CharSequence getTitle() {
    return mTitle;
}

public final int getTitleColor() {
    return mTitleColor;
}

protected void onTitleChanged(CharSequence title, int color) {
    if (mTitleReady) {
        final Window win = getWindow();
        if (win != null) {
            win.setTitle(title);
            if (color != 0) {
                win.setTitleColor(color);
            }
        }
    }
}

Notice that the code delegates the process of setting the text and text color to the contained Window class using setTitle and setTitleColor.  This will be important later.

Thus, if all you want to do is dynamically update the text of the Title Bar or the color of that text use those two methods.  You can call them from anywhere in the Activity code and expect that the Title Bar will redraw itself.

Animated Title Bars

Getting back to the Styles and Themes, the background drawable seems like a perfect first step in adding something more profound to the Title Bar.  In the last post to create a dynamic Splash Screen I made the background transparent and placed an animation overlay across the entire view extent (with ample padding.)  A TextView is more limited as we are not dealing with a full Activity and probably should avoid placing anything transparent within its background for aesthetic reasons.  I say probably because there are some of you who may wish to limit the size of the Activities without limiting the sizes of the Views.  It’s a dirty hack.  Let’s not indulge our wicked desires and look at animating the Title Bar.

Adding an animation to the Title Bar is almost as simple as replacing the background attribute drawable resource with an animation resource.  The one catch is that we need to access the background of the TextView to set the animation in motion.  In order to do that, we can’t exactly have things hidden away in the Theme and Styles section.  We need it explicitly stated just as we would with any layout XML file and labled with an “id” attribute:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
   	android:layout_height = "match_parent"
   	android:layout_width = "match_parent"
   	android:background = "#222222"
   	android:text="My Title Bar"
        android:id="@+id/title_layout_bar"/>

What follows next seems almost ludicrous, we have to ask our phone if it supports a custom title bar.  I can only assume this is a carry over from earlier versions of Android as we’re perfectly safe to add in customized Title Bars through attributing Themes and Styles.  And if it’s not, well, that’s why they pay you to develop Apps.

The code goes something like this:

public class CustomBarActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        boolean titled = requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
        setContentView(R.layout.main);
        if(titled){
        	getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title_layout);
        }
    }
}

After the feature request check we set the layout to our XML file and then go about adding in the layout for the Title Bar.  We can not, however, attempt to call the “setContentView” method before requesting the new window feature.  If you do your App will crash.

To get the animation started we call on our dear ol’ friend the AnimationUtils class:

TextView view = (TextView)findViewById(R.id.title_layout_bar);
Animation anim = AnimationUtils.loadAnimation(this, R.anim.anim_bar);
view.startAnimation(anim);

I’ll leave out the XML for the animation resource.  (One comment:  This animation will run for the entire lifetime of the Activity and consume precious resources in doing so.  Android is powerful but it is still quite limited.  Animations aren’t easy on the CPU that ships with some phones.)

Dynamically Updating the non-Default Title Bar

Changing the Title Bar due to actions of the user is just as easy as dynamically adjusting any TextView as long as you can call upon it by id.  The only thing you can’t do is hide it.  That can only be done setting the FEATURE_NO_TITLE before the call to “setContentView” or within the Theme itself.  If you want the option to hide it dynamically, place a TextView within the layout and forget about a Title Bar.

What about the four methods linked to the Activity that were mentioned above?  Here’s the part that gets interesting:  don’t use them!  Go ahead and try to change the Title Bar text using the Activity methods.  While you can change the state contained within the Activity itself your custom Title Bar stays static.  But why?

Remember when I showed you that the call to Activity’s onTitleChanged delegates to Window‘s  setTitle and setTitleColor methods?  Let’s have a look at what those look like underneath the hood.  Again, we go to the Android git repository:

public abstract void setTitle(CharSequence title);
public abstract void setTitleColor(int textColor);

and meet an abstract base class.  No problem, right?  Consulting the documentation on the Android resource pages yields the catch-phrase:  “The only existing implementation of this abstract class is android.policy.PhoneWindow.”  And following that down to the Google Groups Android page gives us this conversation wherein we find out:

As you say, this is an internal class, so not appropriate to discuss on
android-developers (which is for the SDK)…

-Dianne Hackborn, Android framework engineer

This implies it’s not within the general repository where we’re accustomed to finding the implementation details.  Nor is it mentioned on the Android developer pages.  We’ll need to look at the highest level of the framework repository, within the “internal” section (Don your Dick Tracy hats.)

PhoneWindow is found in the framework base, policy section of the code.  It took a little sleuthing to find (why can’t they make it easy?) but eventually we can see the implementation details of setTitle and setTitleColor:

@Override
public void setTitle(CharSequence title) {
    if (mTitleView != null) {
        mTitleView.setText(title);
    }
    mTitle = title;
}

@Override
public void setTitleColor(int textColor) {
    if (mTitleView != null) {
        mTitleView.setTextColor(textColor);
    }
    mTitleColor = textColor;
}

And mTitleView is a TextView that appears to not have been set.  That’s why you can’t even attempt to trick the Activity by calling the getWindow method to set the title and title color directly.  If you do, nothing will happen to the UI but you’ll be able to see that the state of the Window is modified from the code.

A Custom Title Bar

You are not restricted to only a TextView for the Title Bar.  In fact, there’s even a way to place an icon on your Title Bar built into the Android Activity code.  I won’t get into that here because that’s what I would consider part of the “plain, old, vanilla” Title Bar.

Just like what was done up above in adding an animated background to the Title Bar, making a unique Title Bar involves creating a layout XML resource file.  I suggest wrapping your custom Title Bar within a LinearLayout horizontally positioned but you’re free to do whatever you wish:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
  	android:layout_width="match_parent"
  	android:layout_height="match_parent"
  	android:gravity="center_horizontal"
  	android:orientation="horizontal">
  	<Button
   		android:text="@string/back"
  		android:layout_width="wrap_content"
  		android:layout_height="match_parent"
  		android:id="@+id/back_button"/>
  	<TextView
  		android:text="@string/app_name"
  		android:layout_width="wrap_content"
  		android:layout_height="match_parent"
  		android:id="@+id/title_text"/>
  	<Button
  		android:text="@string/forward"
  		android:layout_width="wrap_content"
  		android:layout_height="match_parent"
  		android:id="@+id/forward_button"/>
</LinearLayout>

You don’t need to do anything further but if you’re going to go this far why not shoot for the moon?  I’ll modify the Theme so that it’s large enough to fit a Button and I’ll even link to new background Styles just to make this really custom:

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<style name="custom_titlebar">
		<item name="android:windowTitleStyle">@style/title_bar</item>
		<item name="android:windowTitleSize">35dp</item>
		<item name="android:windowTitleBackgroundStyle">@style/title_background</item>
	</style>
</resources>

Within the Title Bar Styles, let’s remove the single line feature of the Title Bar (you never know,) change the shadowing so it stands out from what will be the actual background, and provide the reference to the drawable resource that’ll be the background:

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<style name="title_bar">
		<item name="android:singleLine">false</item>
		<item name="android:textAppearance">@android:style/TextAppearance.WindowTitle</item>
		<item name="android:shadowColor">#BB0000FF</item>
		<item name="android:shadowRadius">2.75</item>
	</style>
	<style name="title_background">
		<item name="android:background">@drawable/background_square</item>
	</style>
</resources>

With all that in place it’s time to see what it looks like:

Yup, that’s pretty awful.  This would never go out to a client.  I think you can tell why.

What Really Happens to the Default TextView With a Custom Title Bar

Above I mentioned that it appears that the TextView of the PhoneWindow is never set.  I lied.  It’s not that simple but the explanation would have detracted from the flow of this article.  This is where it belongs for those that are truly curious to the inner workings of Android.

There is no place where I could find where the TextView is set to null either in the PhoneWindow or Window source code.  However, I did find that the TextView is set to GONE if the mLocalFeatures variable is set to just the right value.  GONE means that the TextView is not only invisible but also occupies no space within the layout.  Technically speaking, setting the text and text color of the Title Bar really did change the state of the TextView.  You just couldn’t see it.

Here is the code in PhoneWindow responsible for setting the state of the internal TextView:

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor();
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);

        mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
        if (mTitleView != null) {
            if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                View titleContainer = findViewById(com.android.internal.R.id.title_container);
                if (titleContainer != null) {
                    titleContainer.setVisibility(View.GONE);
                } else {
                    mTitleView.setVisibility(View.GONE);
                }
                if (mContentParent instanceof FrameLayout) {
                    ((FrameLayout)mContentParent).setForeground(null);
                }
            } else {
                mTitleView.setText(mTitle);
            }
        }
    }
}

The getLocalFeatures function returns the mLocalFeatures variable.  Down in the source code for Window the mLocalFeatures variable is set:

public boolean requestFeature(int featureId) {
    final int flag = 1<<featureId;
    mFeatures |= flag;
    mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
    return (mFeatures&flag) != 0;
}

This is why you need to request the window feature before it setting the Content of the Activity.  The PhoneWindow needs to check the variables in the proper order to flag the TextView  GONE.

Conclusion

Android provides you wish ample means to modify not only the UI of the Title Bar but also it’s behavior.  You are limited only by your imagination but it’s important to understand that some convenience methods are lost when working with customized applications.  Underneath the hood there are several assumptions made about an application, the UI state, and appearance that if not flagged in the right order will cause your App to crash before it has a chance to load.

If you have any more questions or want an explanation of some other facet working with Title Bars let me know.  My blog posts have slowed down recently but they’re also much, much longer than they used to be and take an inordinate amount of time to write between side projects and life.  That said, if asked I will attempt to answer, it just might take a bit.

About these ads

9 comments on “Android: Dynamic and Custom Title Bars

  1. William Kida
    March 30, 2012

    Awesome.

  2. heyman
    April 10, 2012

    first off thanks for posting this content, will play around and update with my experience – heyman

  3. Pingback: Using custom title bar on select activitys within an app | PHP Developer Resource

  4. liushuwoo
    June 6, 2012

    can you give that example java source?

    • owreese
      June 8, 2012

      I’ve been meaning to get it up into github for, I don’t know how long. I’m actively working on a project code named “Squealer” at the moment so I can present it at a conference. After that I’m scheduled to begin working on another in-memory database in another language. That and I’m part of a start-up. So, in short, my time is very limited. Let me see what I can do as most of my readership is Android related. No promises.

  5. Pingback: Android Status Update 23/10 « Moodifier

  6. Batman
    May 10, 2013

    My conclusion is that Android is a billion years behind Flex / Flash.
    Probably at the time when we are running on a space ships Android development will be close to Flex.
    Thanks the ultimate moron, and oligophrene Steve Jobs for trying killing the best and ultimate cross-platform language/platform, because his shitty hardware is unable to run it.

  7. Cecile
    July 5, 2013

    We absolutely love your blog and find nearly all of your post’s to be exactly what I’m looking for.

    Does one offer guest writers to write content for you?
    I wouldn’t mind producing a post or elaborating on most of the subjects you write regarding here. Again, awesome web log!

    • owreese
      July 5, 2013

      I’m happy to link to your an article in your blog as long as you’re not pumping spam, hot links to products, selling a product, selling a service, stealing someone else’s content, etc. In short, I don’t have the time to turn this into a full blown magazine nor do I have time to blog much anymore. It’s a sad fact that in joining a start-up, I’ve taken on many of the unhealthy lifestyles trait people associate with start-ups.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Information

This entry was posted on March 18, 2011 by in Android, Java.
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: