Growing up with Android

Monday 8 March 2010

Order precedence in styling

Android calculates the effective style of a widget by merging the individual style definitions from multiple sources (viz., view definition, styles.xml, theme and Android defaults) using the following order precedence:

  1. Widget's inline attributes, which override the ...
  2. styles in the styles.xml file, which override the ...
  3. default styles of the widget, which override the ...
  4. override the styles specified in a theme

This order precedurce rule is evaluated on a "per attribute" basis. For ex:- the textColor specified in a style is overridden by textColor attribute specified "inline" on the widget (similar to CSS).

Let's see it action

Source # 1: Styles in the "styles.xml" file

"Styles.xml" file contains zero or more styles in it. A style is identified by it's name, and is a collection of zero or more widget attributes (viz., textColor, background color etc). A style has no knowledge of where it will be used. This is unlike CSS, which has a concept of selectors. A style is attached to a widget in a view definition XML file or in code.

We will use the following "styles.xml" file to experiment with the order precedence rules:

  1. <resources>
    1. <style name="DesiredApplicationTheme">
      1. <item name="android:typeface">monospace</item>
      2. <item name="android:textSize">20px</item>
      3. <item name="android:textColor">#993333</item>
      4. <item name="android:textStyle">normal</item>
      5. <item name="android:background">#C3C3C3</item>
    2. </style>
    3. <style name="MediumFontActivityTheme" parent="DesiredApplicationTheme">
      1. <item name="android:textSize">30px</item>
    4. </style>
    5. <style name="LargeFontActivityTheme"><
      1. <item name="android:textSize">40px</item>
    6. </style>
    7. <style name="BrightYellow" >
      1. <item name="android:textColor">#FFFF66</item>
      2. <item name="android:background">#FF3399</item>
    8. </style>
    9. <style name="SoftYellow" parent="BrightYellow">
      1. <item name="android:textColor">#FFFFCC</item>
    10. </style>
  2. </resources>
Source # 2: Themes in ApplicationManifest.xml"

A theme is a special kind of style that affects the look and feel of an entire applicatin on activity. The definition of the theme (which is a style) is defined in the styles.xml file, but is applied to an activity or application in the "ApplicationManifest.xml" file.

We will use the following "ApplicationManifest.xml" file to experiment with the order precedence rules. These there actvities will paint the same view./p>

  1. <application android:theme="@style/DesiredApplicationTheme" android:icon="@drawable/icon" android:label="@string/app_name">
    1. <activity android:name=".SmallFontActivity"/>
    2. <activity android:name=".MediumFontAcvitity" android:theme="@style/MediumFontActivityTheme"/>
    3. <activity android:name=".LargeFontActitiy" android:theme="@style/LargeFontActivityTheme"/>
    4. </application>
Source # 3: Widget attributes in view definition XML

A widget's look and feel can be manipulated using it's attributes (ex: android:textColor) in a view layout definition file (or in code).

We will use the following view definition file to experiment with the order precedence rules. This view will be painted by all the activities defined above:

  1. <TextView id="@+id/TextView01" android:text="Style Me." android:layout_width="fill_parent" android:layout_height="wrap_content"/>
  2. <TextView id="@+id/TextView02" android:text="Style Me." style="@style/BrightYellow" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
  3. <TextView id="@+id/TextView03" android:text="Style Me." style="@style/SoftYellow" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
  4. <TextView id="@+id/TextView04" android:text="Style Me." style="@style/SoftYellow" android:textStyle="italic" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
Order precendence effect
When the SmallFontActivity is launched:

This activity does not define a theme of its own. It inherits the application's theme. The effective style for each text view is calculated as follows:

TextView01
  • The text color, #993333, is from DesiredApplicationTheme
  • The background color, #C3C3C3, is from DesiredApplicationTheme
  • The textStyle, "normal", is from DesiredApplicationTheme
  • The font size is 20px from DesiredApplicationTheme
  • The font family, monospace, i sfrom DesiredApplicationTheme

DesiredApplicationTheme is attached to the application in the "ApplicationManifest.xml"

TextView02
  • The text color, #FFFF66, defined in BrightYellow overrides the application's text color, #993333, defined in DesiredApplicationTheme
  • The background color, ##FF3399, defined in BrightYellow overrides the application's background color, #C3C3C3, defined in DesiredApplicationTheme
  • The textStyle is application's default, "normal" , defined in DesiredApplicationTheme
  • The font size is applicaiton's default, 20px, defined in DesiredApplicationTheme
  • The font family is application's default, monospace, defined in DesiredApplicationTheme
TextView03
  • The text color, #FFFFCC, defined in SoftYellow overrides the text color, #FFFF66, defined in BrightYellow which in turn overrides the application's text color #993333, defined in DesiredApplicationTheme
  • The background color, #FF3399, is from BrightYellow style (SoftYellow extends BrightYellow), which overrides the application's background color, #C3C3C3, defined in DesiredApplicationTheme
  • li>The textStyle is application's default, "normal" , defined in DesiredApplicationTheme
  • The font size is applicaiton's default, 20px, defined in DesiredApplicationTheme
  • The font family is application's default, monospace, defined in DesiredApplicationTheme
TextView04
  • The text color, #FFFFCC, defined in SoftYellow overrides the text color, #FFFF66, defined in BrightYellow which in turn overrides the application's text color #993333, defined in DesiredApplicationTheme
  • The textStyle defined inline, italic, overrides the appication's default textSytle, normal, defined in DesiredApplicationTheme
  • The background color, #FF3399, is from BrightYellow style (SoftYellow extends BrightYellow), which overrides the application's background color, #C3C3C3, defined in DesiredApplicationTheme
  • The font size is applicaiton's default, 20px, defined in DesiredApplicationTheme
  • The font family is application's default, monospace, defined in DesiredApplicationTheme
When the MediumFontActivity is launched:

This activity uses the MediumFontActivityTheme. In the "styles.xml" MediumFontActivityTheme extends the DesiredApplicationTheme styles. It retains all the properties of DesiredApplicationTheme and only overrides font size.

The order precedence rule analysis will be similar to our previous analysis except that for this fact:

TextView01-04
  • The font size of 30px defined in MediumFontActivityTheme overrides the font size 20px from DesiredApplicationTheme
When the LargeFontActivity is launched:

This activity uses the LargeFontActivityTheme. In the "styles.xml" LargeFontActivityTheme does not extend the DesiredApplicationTheme style, hence it overides all the properties of DesiredApplicationTheme and retains none.

The effective styles for each text view is now calculated as follows:

TextView01
  • The text color is default system color becuase the LargeFontActivityTheme does not specify it nor inherit it
  • The background color is default background color becuase the LargeFontActivityTheme does not specify it nor inherit it
  • textStyle is default style becuase the LargeFontActivityTheme does not specify it nor inherit it
  • The font size is 40px from LargeFontActivityTheme that was attached to the activity in the ApplicationManifest.xml
  • The font family is default familty from becuase the LargeFontActivityTheme does not specify it nor inherit it
TextView02
  • The text color of #FFFF66 defined in BrightYellow overrides the default text color
  • The background color is #FF3399 defined in BrightYellow overrides the default background color
  • textStyle is unaffected, which the default textStyle
  • The font size is 40px is unaffected, and is from LargeFontActivityTheme
  • The font family is unaffected, which is the default family
TextView03
  • The text color of #FFFFCC defined in SoftYellow overrides the text color, #FFFF66, defined in BrightYellow overrides the default text color
  • The background color is #FF3399 is unaffected, and is from BrightYellow style
  • textStyle is unaffected, which the default textStyle
  • The font size is 40px is unaffected, and is from LargeFontActivityTheme
  • The font family is unaffected, which is the default family
TextView04
  • The text color of #FFFFCC defined in SoftYellow overrides the text color, #FFFF66, defined in BrightYellow overrides the default text color
  • textStyle is italic defined inline overrides the default textStyle
  • The background color #FF3399 is unaffected, and is from BrightYellow style
  • The font size is 40px is unaffected, and is from LargeFontActivityTheme
  • The font family is unaffected, which is the default family
Source # 4: The default styles

Default styles are the ones provided by Android on a per widget basis. They have higher precedence than themes, which is quite interesting. To see this source kick in order precedence evaluation, let us replace the TextViews in our view with Buttons (Button inherits from TextView) and launch the SmallFontActivity actvity.

TextViewButton

TextView01 and Button01 have no inline style attributes nor style attribute specifind on them. Hence, it is upto either the theme or the Android's widget default to provide the styling.

The theme suggests the textColor should be #993333. It makes it to the TextView, but not to the Button. This is becuase the widget defaults have a higher precedence over theme, and the button default text color is black.

In contrast, Button02-04 which either have the style attribute or inline style attributes specified, look exactly like their TextView counterparts and very different than Button01 with its default look and feel. This is becuase, the style attribute and inline style attribute higher precedence than widget defaults.

Wednesday 3 February 2010

Androing Emulator: Is the slow launch time killing the buzz for you?

In a typical Android application development session one would have the need to iteratively make changes, launch emulator, deploy, test and debug his/her application. And if you are in the habit of nuking the emulator between iterations, you could be in a very frustrating and "pull your hair out" type of experience.

The launch of an emulator session is pretty expensive process and one shouldn't need to incur this penalty unless absolutely necessary. When an emulator comes to life it is available for debugger connections on a well known port. Upon being asked to run an application, Eclipse probes for an emulator and if found connects to it (via the aforementioned port), else it starts a new emulator. The conditional launch saves a lot of development time.

To drive home the point, below are logs of activities that happen between when an user clicks on the "run" button in Eclipse and the application is available for use. The idea is to avoid the "Reds" and stay in the "Green".

Initial run

Since the eclipse cannot find an emulator, it starts one and uses it.

  1. [2010-01-25 05:50:15 - HelloWorld]------------------------------
  2. [2010-01-25 05:50:15 - HelloWorld]Android Launch!
  3. [2010-01-25 05:50:15 - HelloWorld]adb is running normally.
  4. [2010-01-25 05:50:15 - HelloWorld]Performing keepItSimple.android.gwa.Practice activity launch
  5. [2010-01-25 05:50:15 - HelloWorld]Automatic Target Mode: launching new emulator with compatible AVD 'TestVirtualDevice201'
  6. [2010-01-25 05:50:15 - HelloWorld]Launching a new emulator with Virtual Device 'TestVirtualDevice201'
  7. [2010-01-25 05:50:46 - HelloWorld]New emulator found: emulator-5554
  8. [2010-01-25 05:50:46 - HelloWorld]Waiting for HOME ('android.process.acore') to be launched...
  9. [2010-01-25 05:52:50 - HelloWorld]HOME is up on device 'emulator-5554'
  10. [>[2010-01-25 05:52:50 - HelloWorld]Uploading HelloWorld.apk onto device 'emulator-5554'
  11. [2010-01-25 05:52:51 - HelloWorld]Installing HelloWorld.apk...
  12. [2010-01-25 05:53:28 - HelloWorld]Success!
  13. [2010-01-25 05:53:28 - HelloWorld]Starting activity keepItSimple.android.gwa.Practice on device
  14. [2010-01-25 05:53:43 - HelloWorld]ActivityManager: Starting: Intent { cmp=keepItSimple.android.gwa/.Practice }
Second run, after closing the emulator

Since, we closed the emulator from the previous session, Eclipse has no choice but to launch a new instance. Now is a good time to "pull you hair".

  1. [2010-01-25 05:54:06 - HelloWorld]------------------------------
  2. [2010-01-25 05:54:06 - HelloWorld]Android Launch!
  3. [2010-01-25 05:54:06 - HelloWorld]adb is running normally.
  4. [2010-01-25 05:54:06 - HelloWorld]Performing keepItSimple.android.gwa.Practice activity launch
  5. [2010-01-25 05:54:07 - HelloWorld]Automatic Target Mode: launching new emulator with compatible AVD 'TestVirtualDevice201'
  6. [2010-01-25 05:54:07 - HelloWorld]Launching a new emulator with Virtual Device 'TestVirtualDevice201'
  7. [2010-01-25 05:54:35 - HelloWorld]New emulator found: emulator-5554
  8. [2010-01-25 05:54:35 - HelloWorld]Waiting for HOME ('android.process.acore') to be launched...
  9. [2010-01-25 05:56:08 - HelloWorld]HOME is up on device 'emulator-5554'
  10. [2010-01-25 05:56:08 - HelloWorld]Uploading HelloWorld.apk onto device 'emulator-5554'
  11. [2010-01-25 05:56:08 - HelloWorld]Installing HelloWorld.apk...
  12. [2010-01-25 05:56:39 - HelloWorld]Success!
  13. [2010-01-25 05:56:39 - HelloWorld]Starting activity keepItSimple.android.gwa.Practice on device
  14. [2010-01-25 05:56:52 - HelloWorld]ActivityManager: Starting: Intent { cmp=keepItSimple.android.gwa/.Practice }
Thrid run, deploy to an existing emulator instance

Now we a bit smarter and deployed our application onto an existing emulator instance. Life is better once again

  1. [2010-01-25 05:57:11 - HelloWorld]------------------------------
  2. [2010-01-25 05:58:16 - HelloWorld]Android Launch!
  3. [2010-01-25 05:58:16 - HelloWorld]adb is running normally.
  4. [>[2010-01-25 05:58:16 - HelloWorld]Performing keepItSimple.android.gwa.Practice activity launch
  5. [2010-01-25 05:58:16 - HelloWorld]Automatic Target Mode: using existing emulator 'emulator-5554' running compatible AVD 'TestVirtualDevice201'
  6. [2010-01-25 05:58:16 - HelloWorld]Uploading HelloWorld.apk onto device 'emulator-5554'
  7. [2010-01-25 05:58:16 - HelloWorld]Installing HelloWorld.apk...
  8. [2010-01-25 05:58:23 - HelloWorld]Success!
  9. [>[2010-01-25 05:58:23 - HelloWorld]Starting activity keepItSimple.android.gwa.Practice on device
  10. [2010-01-25 05:58:29 - HelloWorld]ActivityManager: Starting: Intent { cmp=keepItSimple.android.gwa/.Practice }
Now, stay away from the close button.

Sunday 24 January 2010

How does TableLayout handle column overflow

Tell me...
In case of an overflow, TableLayout uses the following simple formula to reduce the width of the columns such that all columns are visible to the user (most of the time)
r=(g-w)/n
  • r= The amount (in pixels) by which the shrinkable columns must be reduced.
  • g= The sum of a widths and paddings of all columns
  • w=The maximum width of the table (constrained by its parent)
  • n=The number of shrinkable columns in the shrinkColumn attribute

On close review of the above, a few things jump out:

What is ShrinkColumns attribute?
This attribute is a comma separated values of column indexes (starting at 0) that should be narrowed in case of an overflow. All other columns are not touched to handle the overflow condition.
Show me...
I have two very wide cells, TextView01 and TextView04, which will trigger the overflow handling logic in TableLayout. I chose columns 0 and 1 (i.e both) to be shrinkable.
  1. <TableLayout android:shrinkColumns="0,1" android:id="@+id/TableLayout01" android:layout_width="wrap_content" android:layout_height="wrap_content">
    1. <TableRow android:id="@+id/TableRow01" >
      1. <TextView android:id="@+id/TextView01" android:text="Exploring andoring is fun. Let's keep going for some more time."></TextView>
      2. <TextView android:id="@+id/TextView02" android:text="Go for it"></TextView>
      3. </TableRow>
    2. <TableRow android:id="@+id/TableRow01">
      1. <TextView android:id="@+id/TextView03" android:text="Hello"></TextView>
      2. <TextView android:id="@+id/TextView04" android:text="There are seven days in a week."></TextView>
      </TableRow>
    </TableLayout>

You can see both columns have been fit into the available width. Also, think about the resultant width distribution. We it visit again in the next section.

Descendant views can help too!
As we noted earlier, it could be good idea (and it is) to limit the overflow in the first place. Sometimes, this could be done by making some simple design changes to the views in the table layout. In this example, I have added "layout_width=wrap_content" attribute to the the text views. They are less demanding than the counterparts in the previous example, thus causing lesser overflow.
  1. <TableLayout android:shrinkColumns="0,1" android:id="@+id/TableLayout01" android:layout_width="wrap_content" android:layout_height="wrap_content" >
    1. <TableRow android:id="@+id/TableRow01">
      1. <TextView android:layout_width="wrap_content" android:id="@+id/TextView01" android:text="Exploring andoring is fun. Let's keep going for some more time."></TextView>
      2. <TextView android:layout_width="wrap_content" android:id="@+id/TextView02" android:text="Go for it"></TextView>
      </TableRow>
    2. <TableRow android:id="@+id/TableRow01">
      1. <TextView android:layout_width="wrap_content" android:id="@+id/TextView03" android:text="Hello"></TextView>
      2. <TextView android:layout_width="wrap_content" android:id="@+id/TextView04" android:text="There are seven days in a week."></TextView>
      </TableRow>
    </TableLayout>

Notice, the difference! The columns seem to have a better balance and are closer to their natural size. Of course, whether this desired or not is matter of design choice.

Issue with one size fits all approach

As we noted earlier, all shirnkable columns are reduced by the same amount. And neither is the columns' initial width taken into consideration. Sometimes bad things can happen due this "one size fits all" approach. In the exmaple below, the second column's width is much smaller than the overflow caused by the first column.

  1. <TableLayout android:shrinkColumns="0,1" android:id="@+id/TableLayout01" android:layout_width="wrap_content" android:layout_height="wrap_content" >
    1. <TableRow android:id="@+id/TableRow01">
      1. <TextView android:id="@+id/TextView01" android:text="Exploring andoring is fun. Let's keep going for some more time."></TextView>
      2. <TextView android:id="@+id/TextView02" android:text="H"></TextView>
      </TableRow>
    2. <TableRow android:id="@+id/TableRow01">
      1. <TextView android:id="@+id/TextView03" android:text="Hello"></TextView>
      2. <TextView android:id="@+id/TextView04" android:text="2"></TextView>
      </TableRow>
    </TableLayout>

Notice how ugly it looks? Not only is column # 2 missing, also the wrapping is all messed up.

Looking up the widgets in the heirarchy viewer (below) gives us comfort that all widgets exist, its just that the redering that has gone kaput. Based on this observation, in my opinion shrinkColumns should be a collection of relatively equal columns.

Remind me again, why is shrinkColumn important?

Just to wrap up this column, let's see what happen when we drop the "shinkColumns" attribute?

  1. <TableLayout android:shrinkColumns="0,1" android:id="@+id/TableLayout01" android:layout_width="wrap_content" android:layout_height="wrap_content">
    1. <TableRow android:id="@+id/TableRow01" >
      1. <TextView android:id="@+id/TextView01" android:text="Exploring andoring is fun. Let's keep going for some more time."></TextView>
      2. <TextView android:id="@+id/TextView02" android:text="Go for it"></TextView>
      3. </TableRow>
    2. <TableRow android:id="@+id/TableRow01">
      1. <TextView android:id="@+id/TextView03" android:text="Hello"></TextView>
      2. <TextView android:id="@+id/TextView04" android:text="There are seven days in a week."></TextView>
      </TableRow>
    </TableLayout>

Because there the TableLayout cannot determine which columns to shrink, it leaves all columns untouched. The result is that column # 2 is pushed out of the viewport.

Once again, the heirarchy viewer confirm that all the text views exist.

Thursday 31 December 2009

Windows: How to attach debugger to Android framework source code

Browsing and stepping through the android sources is a great way of learning the Android API. However, setting up your development environment to do this is not as easy and straight forward as it should be (more so on windows).
You will need to jump over two hurdles...

  1. Download the sources


    • You will need Git tool to pull down sources onto your computer. I got the windows port of Git from http://code.google.com/p/msysgit/
    • From a folder of your choice..


      •  git clone git://android.git.kernel.org/platform/frameworks/base.git will download the "platform/frameworks/base.git" onto you computer. 


    • http://android.git.kernel.org/ lists all the android projects you could download


  2. Attaching sources to eclipse




Blog Archive

Followers