Using custom views with layouts

One of the most important parts is the user interface, as it is the most visible part of an app. When we create apps for Android devices, sometimes we may need to create custom views.

How to do it...

Creating a new UI control involves creating a new type, inheriting either directly or indirectly from one of the View types. Here's how it's done:

  1. If we want to create more advanced button control, we must create a new type inheriting from the Button type. We also need to ensure that the namespace is user friendly as it will be used in the layout files:
    namespace XamarinCookbook.Views {
      public class TimedButton : Button {
      }
    }
  2. Now we can implement the functionality that this button provides:
    public int Interval { get; set; }
    
    private async void OnClicked(object sender, EventArgs e) {
      // disable the button for several seconds
      if (Interval > 0) {
        Enabled = false;
        await Task.Delay(Interval * 1000);
        Enabled = true;
      }
    }
  3. As this control can be created in code, we must implement the constructor that accepts a Context instance:
    public TimedButton(Context context)
    : base(context) {
      Interval = 5; // default
      Click += OnClicked;
    }
  4. Additionally, this control will be inflated from layout resource files, so we implement the constructor that accepts an IAttributeSet instance:
    public TimedButton(Context context, IAttributeSet attrs)
    : base(context, attrs) {
      Interval = 5; // default
      Click += OnClicked;
    }

To make use of the view in our UI layouts, we can either instantiate the control in code or we can add them to our layout resource files.

  1. Like any other control, we can instantiate the custom control:
    var button = new TimedButton(this);
  2. Similarly, we can add our custom control to layout resource files as well:
    <xamarincookbook.views.TimedButton
      android:id="@+id/myButton"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="@string/hello"/>

Although we can add properties to custom controls, we cannot set them in the layout resource files. To do this, we must create an attributes file:

  1. To set attributes in the XML files, we need to declare them by adding a value resource (Resources/values/TimedButtonAttr.xml):
    <?xml version="1.0" encoding="UTF-8" ?>
    <resources>
      <declare-styleable name="TimedButton">
        <attr name="interval" format="integer" />
      </declare-styleable>
    </resources>
  2. Once we have this resource file, we need our custom button to actually use the values specified in the XML attributes, and this just requires an update to the constructor with the IAttributeSet parameter:
    public TimedButton(Context context, IAttributeSet attrs)
    : base(context, attrs) {
      // get our custom style
      TypedArray styled = context.Theme.ObtainStyledAttributes(
        attrs, Resource.Styleable.TimedButton, 0, 0);
    
      try {
        // set the property from the attribute
        Interval = styled.GetInt(
          Resource.Styleable.TimedButton_interval, 5);
      } finally {
        // clean up
        styled.Recycle();
        styled.Dispose();
      }
    
      Click += OnClicked;
    }
  3. Finally, we can add the custom attributes to the view instance in our layout resource file:
    <xamarincookbook.views.TimedButton
      ...
      xmlns:app="http://schemas.android.com/apk/res-auto" 
      app:interval="3"/>

How it works...

Creating a custom view is no different from subclassing an existing type and adding functionality. However, to use this new type, we have to add support for it so that it can be used in layouts that will be inflated. This is just an extra, optional constructor, but it is recommended to add an attributes resource file.

Using this view in a layout resource is no different from adding the standard Android views to the layout. But we can see that we have to specify the full namespace of the button, and this is lowercase. This is exactly the same as adding views from the Android support libraries.

Note

Android requires that views provide the full namespace, in lowercase, in the layout resource files. The type name should keep original casing.

All Android views require at least a constructor that accepts an instance of Context. This allows the view to properly handle the various lifecycle events.

The additional constructor takes the usual Context property, but additionally, it also takes the IAttributeSet parameter. This is used by the layout inflator to pass the attributes from the XML to our custom view, which in turn we can use to initialize any members.

The attributes are declared in a resource, <declare-styleable>, so that they can be used in code to retrieve the attribute values. This element will contain all the <attr> elements that represent each available XML attribute that can be used in the layout.

Custom attributes are not prefixed with android: but with a local, app-specific namespace from http://schemas.android.com/apk/res-auto.

When the view is inflated from an XML layout, all the attributes from that element are passed to the constructor via IAttributeSet. Although it's possible to read values from IAttributeSet directly, doing so does not allow resource values to be resolved, such as when using string resources.

We can obtain the resolved and styled attributes from the result of the ObtainStyledAttributes() method on the Context themes. This method returns a TypedArray object, from which we can easily retrieve the values that we can use to initialize members.

Note

The TypedArray objects are a shared resource and must be recycled after use.

There's more...

Sometimes we wish to create a new view that is actually just a collection of other views, with some relationship between them. This is very easily done by simply subclassing a layout type, such as LinearLayout, instead of a button. This allows us to add subviews that will make up our new compound view.

See also

  • Preserving view and fragment state
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.214.32