Tags

, , , , , , , , , , , , , , , , , , , ,

It’s a known fact in Android that themes cannot be changed during runtime. If the new theme change has to be reflected in the application, all the views have to be re-inflated, as the theme based values are parsed only once during inflation. However, the beautiful Pocket for Android app switches between black and white themes in a jiffy! How did they achieve something that has been declared by the Android community as impossible? The ActionBar background, the text colors, the image colors, and the Menu background have all changed. Since it’s not a single line change, let’s chop it into pieces.

Pocket's Themes

There are clearly two things that make it feel like the theme has changed from Holo Dark to a Light version — the ActionBar and the Menu. The good thing with menu is that it uses a ListView to show the items. Hence it gets re-created every time it is shown. Now a theme change during runtime will change the menu items for sure. A small theme change code will be like:

<!-- somewhere in themes.xml -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- light theme -->
    <style name="AppTheme" parent="@android:style/Theme.Holo.Light"/>

    <!-- dark theme -->
    <style name="AppTheme.Dark" parent="@android:style/Theme.Holo"/>

</resources>

And we could change the theme in Java as:

    // somewhere in the activity
    private void changeTheme(boolean isLight) {
        setTheme(isLight ? R.style.AppTheme : R.style.AppTheme_Dark);
    }

That solves the menu problem! How about ActionBar? This is where Pocket has a very clever approach. The ActionBar is clearly not a custom UI. Hence, a call like getActionBar().getCustomView() will return null. If we had such a view, we could easily change the background color, couldn’t we? In this case, ActionBar is fully owned by Android and we don’t have any control over it’s appearance, except that is defined in themes.xml. Also, we cannot re-inflate the ActionBar too — unless we plan to show a custom view.

Let’s take a step back. Where is the actual app content placed on phone? Based on the UI XML Snapshot (that’s so cool!), a typical app will have views like:

<!-- A bunch of FrameLayout as ancestors for core android ...and then -->
<!-- Window of the application -->
<FrameLayout>

    <!-- Content of the application -->
    <RelativeLayout>

        <!-- ActionBar owned by Android -->
        <LinearLayout />

        <!-- Actual content of the application defined in our layout.xml -->
        <LinearLayout or RelativeLayout or FrameLayout />

    </RelativeLayout>

</FrameLayout>

And we can get the Window of the application — which is a FrameLayout — and can set a background! So, if the views contained by it are translucent, the Window’s background will be seen. Choosing a mid-grey for the translucent layer and changing the background to be black or white could give magical effects.

Color Matrix

Did we just have two color themes by just changing the Window background? Changing the window background color is pretty simple.

    // somewhere in the activity
    private void changeTheme(boolean isLight) {
        getWindow().setBackgroundDrawable(new ColorDrawable(isLight ? Color.WHITE : Color.BLACK));
    }

And that’s the magic behind a two-color ActionBar in Pocket. The icons are all translucent, making them feel like they change based on the Holo theme used. The text part is pretty easy. Inflating the views again is unnecessary. Adding a custom state can switch themes in a jiffy. Firefox for Android uses this technique to swiftly change color scheme between normal and private browsing modes. One good trick here is to make the children duplicate their parent state.

<!-- some layout file for the activity -->
<LinearLayout ...attributes...>
    <Button ...attributes...
            android:duplicateParentState="true"/>

    <TextView ...attributes...
              android:duplicateParentState="true"/>
</LinearLayout>

In the above layout, changing the custom state, say “state_light_theme_enabled”, of LinearLayout will propagate down to its children too. So a single call on the root level element can do wonders. And thereby, we can change themes — or make it feel like — within 250ms. This technique is not restricted for monochrome. This works with normal colors too.

Advertisement