Xamarin Android Compound Control - Styling
25 May 2017Last time around I showed how to create a Xamarin Android compound TextInputLayout
control with support for capturing user input with an associate Header
and Error
labels. This post looks at how to style the same control in Android.
Style attributes
As mentioned previously, I want all my compound controls to support a common set of attributes to create a consistent look-and-feel. Every control will have a Header
(i.e. description), Text
(i.e. value) and Error
properties. To style these attributes, we create three associated attributes in our Resources/values/attrs.xml
file to assign the styles to:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="ItcTextAppearanceLabel" format="reference"/>
<attr name="ItcTextAppearanceText" format="reference"/>
<attr name="ItcTextAppearanceError" format="reference"/>
In addition to these attributes we also associate these new style attributes with our existing ITTextInputLayout
definition in the attrs.xml:
<declare-styleable name="ITTextInputLayout" >
<attr name="android:inputType"/>
<attr name="android:maxLines"/>
<attr name="android:text"/>
<attr name="android:imeOptions"/>
<attr name="ItcLabel"/>
<attr name="ItcError"/>
<attr name="ItcTextAppearanceLabel"/>
<attr name="ItcTextAppearanceError"/>
</declare-styleable>
Notice that we don’t include the ItcTextAppearanceText
reference style in the definition. The reason being that we will re-use the already defined style attribute on the android.support.design.widget.TextInputLayout
that our ITTextInputLayout
control inherits from.
To define the default, global look-and-feel for all our controls, we also create a styles container in the attrs.xml:
<declare-styleable name="ITControls" >
<attr name="ItcTextAppearanceLabel"/>
<attr name="ItcTextAppearanceError"/>
<attr name="ItcTextAppearanceText"/>
<attr name="ItcTextInputLayoutStyle" format="reference"/>
</declare-styleable>
Widget and TextAppearance styles
With the attributes defined, we can now define a style for our compound control in the Resources/values/styles.xml
file. As our control extends the android.support.design.widget.TextInputLayout
control, it is important to first consider what style attributes is already being used so that we can associate our own styles with the existing styles. From the existing Android styles definition we see that Android widget has the following styles definition:
<style name="Widget.Design.TextInputLayout" parent="android:Widget">
<item name="hintTextAppearance">@style/TextAppearance.Design.Hint</item>
<item name="errorTextAppearance">@style/TextAppearance.Design.Error</item>
<item name="counterTextAppearance">@style/TextAppearance.Design.Counter</item>
<item name="counterOverflowTextAppearance">@style/TextAppearance.Design.Counter.Overflow</item>
<item name="passwordToggleDrawable">@drawable/design_password_eye</item>
<item name="passwordToggleTint">@color/design_tint_password_toggle</item>
<item name="passwordToggleContentDescription">@string/password_toggle_content_description</item>
</style>
The hintTextAppearance
and errorTextAppearance
is being used by the Android widget to style the label and error text. With this in mind, we can now create a style definition for our own control:
<style name="Widget.InTouch.TextInputLayout" parent="Widget.Design.TextInputLayout">
<item name="android:textColorHint">@color/accentColor</item>
<item name="hintTextAppearance">?attr/ItcTextAppearanceLabel</item>
<item name="errorTextAppearance">?attr/ItcTextAppearanceError</item>
</style>
We override the default style styles and associate them with our new common-look-and-feel style attributes that we have defined. With our control style defined, we also create the default text appearance styles to use for our common-look-and-feel:
<style name="TextAppearance.InTouch" parent="TextAppearance.AppCompat">
</style>
<style name="TextAppearance.InTouch.Label">
<item name="android:textSize">14sp</item>
</style>
<style name="TextAppearance.InTouch.Label.Error">
<item name="android:textSize">12sp</item>
<item name="android:textColor">@color/errorColor</item>
</style>
Themes
With the styles defined, we now set out to create a default Theme where we assign the default styles to the style attributes we created to create our common look-and-feel. Themes are created in a Resources/values/themes.xml
file:
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="Theme.InTouch" parent="Theme.InTouch.Base">
</style>
<style name="Theme.InTouch.Base" parent="Theme.Design.Light.NoActionBar">
<item name="colorPrimary">@color/primaryColor</item>
<item name="colorPrimaryDark">@color/primaryColorDark</item>
<item name="colorAccent">@color/accentColor</item>
<!-- Composites -->
<item name="ItcTextAppearanceLabel">@style/TextAppearance.InTouch.Label</item>
<item name="ItcTextAppearanceError">@style/TextAppearance.InTouch.Label.Error</item>
<item name="ItcTextInputLayoutStyle">@style/Widget.InTouch.TextInputLayout</item>
</style>
Notice that our theme associates the default text appearance with a specific style. With the theme definition in place, we can now use it our applications in Xamarin by typically assigning the Theme
property as an attribute on our startup Activity
or on the Application
instance as illustrated below:
[Application(Name = "phil.AndroidApp", Theme = "@style/Theme.InTouch")]
public class AndroidApp : Application, Application.IActivityLifecycleCallbacks
{
Control changes
With the style attributes and themes defined, we still need to enhance our control to make use of the styles defined. To cater for the styles and themes we need to change our existing control to get the style definitions and use them for changing the look-and-feel of its internal widgets:
The three different constructors cater for different scenarios of creating our compound control. The first constructor on Line 3 is typically used when I create a control programmatically. The second constructor on Line 7 is called when I define my control in a layout without specifying a style.
<intouch.controls.ITTextInputLayout
android:id="@+id/layout_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext" ...
The third constructor is called when I define my control in a layout with specifying a style:
<intouch.controls.ITTextInputLayout
style="@style/MyCustom.Style"
android:id="@+id/layout_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext" ...
Notice how the constructors call into each other and the use of the Resource.Styleable.ITControls_ItcTextInputLayoutStyle
style attribute on Line 7 that will use the style setup in our theme as the default if the user does not specify their own style for our control in a layout. Our theme is setup to use @style/Widget.InTouch.TextInputLayout
as style which makes use of the default look-and-feel style attributes to style the control.
As we are re-using the existing styles defined on the Android TextInputLayout
control, we simply have to call into the base class constructor to get these styles applied to the Error
and Hint
widgets. Feel free to browse the TextInputLayout.java source code if you want to see how this is done.
Summary
Styles and Themes in Android along with style inheritance is a really powerful tool for creating a common look-and-feel across your application. I hope this post highlighted some ways in which you can make use of this powerful functionality to create a common look-and-feel for your own compound controls.
Comments