Tags

, , , , , , , , , , ,

Every view has a list of attributes associated with it. They can either be set in JAVA by calling appropriate methods, or more easily in XML. The attributes are called styleable as they differentiate a view in its skin. A typical example of a View in XML is:

<View android:id="@+id/view"
      android:layout_weight="fill_parent"
      android:layout_height="wrap_content"
      android:background="#FF00FF00"/>

As we can see, the attributes are in Android namespace. Android also provides a way to define Custom Views. With custom views, we can add additional attributes in our own namespace, that makes a particular custom view reusable. Let’s say our custom view can take an argument underline to underline the TextViews. The XML markup will be:

<UnderlineTextView xmlns:sirius="http://schemas.android.com/apk/res/com.sriramramani.sirius"
                   android:id="@+id/text"
                   ... some more attributes ...
                   sirius:underline="true"/>

This new attribute will be define in res/values/attrs.xml as:

<declare-styleable name="UnderlineTextView">
    <attr name="underline" format="boolean"/>
</declare-styleable>

The default values can be obtained by calling super(), which the android TextView will do it for us. The value of the attribute in the namespace sirius can be obtained by

TypedArray a = _some_context_.obtainStyledAttributes(attrs, R.styleable.UnderlineTextView);
boolean isUnderlined = a.getBoolean(R.styleable.UnderlineTextView_underline, true);
a.recycle();

That’s easy! Now, let’s say we are writing a library to come up with a new Menu. Unfortunately Android doesn’t expose their implementation of MenuItem. So, we cannot call super() to obtain the styleable values. Couldn’t we just use the same technique as above to obtain the values in Android namespace? Unfortunately, again, Android doesn’t expose R.styleable to be used in an application code. So, we wouldn’t be able to do a call like below to obtain the values.

// android.R.styleable cannot be resolved.
TypedArray a = _some_context_.obtainStyledAttributes(attrs, android.R.styleable.MenuItem);

All these values seem to have an integer value associated with it. Couldn’t we just pass the raw value? We could and it would work, but just for only one of the versions. These values keep changing as and when new attributes are added for new views in new versions. Hence, there is no guarantee that an attribute, say android:showAsAction would have the same generated integer value in all versions of Android.

So, what’s the solution? One possible solution is to create the attributes in our namespace and use them in the menu’s XML file. That would mean all the attributes in menu definition file having our namespace and not android’s. This is fine as long as we enable our custom menu on all versions. Let’s say Key Lime Pie comes up with an awesome menu, and we wouldn’t want to change it there. Our menu files will fail, as Android’s MenuInflater doesn’t know about our namespace. How do we solve this?

As it turns out, we can create shadow copies of Android’s attributes in our attrs.xml. The only key difference is, while providing names for the attributes, we should be using android: namespace. A typical definition of MenuItem would look like:

<declare-styleable name="MenuItem">
    <attr name="android:id"/>
    <attr name="android:checkable"/>
    <attr name="android:showAsAction"/>
    <! -- some more attributes -->
</declare-styleable>

And we can obtain the values using the TypedArray as:

// The "android:" automagically adds an underscore character, when generated.
TypedArray a = _some_context_.obtainStyledAttributes(attrs, R.styleable.MenuItem);
boolean isCheckable = a.getBoolean(R.styleable.MenuItem_android_checkable, false);

This is not just for overriding a MenuItem. Let’s say our custom layout CoverFlow should provide an iOS like cover flow layout (hoping that Apple won’t sue us as they hold the patent). The CoverFlow needs a thumb to scroll. Though Android provides a way to specify a thumb drawable with android:scrollbarThumbHorizontal, we might not be able to get the value from View as it’s not exposed in any of the public or protected methods. Wouldn’t be it great if we can use the same attribute in android namespace instead of a custom namespace, but yet know the value of what the XML specified? The above logic will help us there …

<!-- in attrs. xml -->
<declare-styleable name="CoverFlow">
    <attr name="android:scrollbarThumbHorizontal"/>
</declare-styleable>

<!-- in layout.xml -->
<CoverFlow android:id="@+id/coverflow"
           ... more attributes ...
           android:scrollbarThumbHorizontal="@drawable/rounded_steel_skeu_bolt"/>

… and in more such custom layouts!

Advertisements