DEV Community

Cover image for đŸȘ„ How to Define Your XML View: Demystifying Custom Views in Android
Denizhan Dalgıç
Denizhan Dalgıç

Posted on

đŸȘ„ How to Define Your XML View: Demystifying Custom Views in Android

Developing for Android provides access to a range of tools and features for creating engaging and personalized user interfaces. One such powerful capability is the ability to create custom views with attributes customized to meet specific application needs. These custom view attributes give developers the ability to define and manage various aspects of their custom views, making them more reusable and enabling a smoother user experience. In this blog post, we will explore the concept of custom view attributes in Android and show you how to use them to create highly flexible and customizable UI components.

Understanding Custom Views

Before diving into custom view attributes, let’s briefly review the concept of custom views in Android. A custom view is an extension of the base View class that allows developers to define their own UI components with unique behavior and appearance. Custom views provide an excellent way to encapsulate complex functionality and design patterns, making them reusable across different parts of an application or even multiple projects.

The Need for Custom View Attributes

While creating custom views, it’s often desirable to expose a set of configurable properties that can be modified by developers using XML layout files or programmatically. This is where custom view attributes come into play. By defining custom attributes, we can provide a convenient and declarative way for developers to customize the behavior and appearance of our custom views, without having to modify the view’s source code directly.

Creating custom views is a topic that is not covered well in official documentation. Hence, developers often need to take a look at Android’s source code in order to find out what is possible and how. Here, I’d like to fill this gap.

Implementing a Custom View

Now, create a class and name it MyCustomView.kt.

First things first, our new class needs to derive from a View class. Let’s derive from LinearLayout. This also makes it available in XML.

class MyCustomView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs)
Enter fullscreen mode Exit fullscreen mode

Don’t worry if it didn’t show up in the layout files’ XML yet. I’ve found that Android Studio’s custom view capabilities are kind of fragile, so we’ll be clicking on “Rebuild Project” a lot.

Create a layout resource file in res/layout folder named custom_view.xml. We’ll be putting a LinearLayout and a button inside to see how it works.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <Button
    android:id="@+id/btn_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    tools:text="Default text"/>

</LinearLayout>
Enter fullscreen mode Exit fullscreen mode

Now, let’s inflate this view in our MyCustomView.kt like this:

class MyCustomView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
  private var parentView: View = inflate(getContext(), R.layout.custom_view, this)
}
Enter fullscreen mode Exit fullscreen mode

This is enough to make our custom view available to be instanced from other views. If it’s enough for your case, you can customize custom_view.xml and have a reusable component in your project.

If you want more functionality and customization, read on.

Implementing Custom Attributes

When using a built-in view, we set attributes to it to customize it the way we want, ie. orientation attribute for LinearLayout. It is possible to define attributes for our custom view, too.

To define them, create an XML file in res/values folder that is conventionally named attrs.xml. Let’s create a styleable item like this:

<resources>
  <declare-styleable name="MyCustomView">
    <!-- Text of the button. -->
    <attr name="buttonText" format="string" />
  </declare-styleable>
</resources>
Enter fullscreen mode Exit fullscreen mode

The field “format” can be one of the following:

boolean: “true” or “false”
color: A color in hex format
dimension: A dimension value eg. “2dp” or “2sp”
float, integer or fraction
reference: A reference to a resource eg. “R.drawable.ic_okay”. Can be a reference to a drawable, a color, etc.
string
Enter fullscreen mode Exit fullscreen mode

For further information, you can visit the source code of builtin styleables.

Next step, we will get this attribute’s value and use it to change the button’s text in our custom view.

In MyCustomView.kt, let’s implement the init block.

private var buttonText = "Default text"

init {
  context.theme.obtainStyledAttributes(
      attrs,
      R.styleable.MyCustomView,
      0, 0
  ).apply {
      try {
          getString(R.styleable.MyCustomView_buttonText)?.let {
              buttonText = it
          }
      } finally {
          recycle()
      }
  }
}
Enter fullscreen mode Exit fullscreen mode

obtainStyledAttributes returns a TypedArray consisting of names and values of attributes we’ve declared earlier. We can access these values with getString, getInt, getBoolean, etc. Then, we assign these values to private properties. And finally, we call recycle() to release the typed array.

core-ktx library also provides some API in which you can throw exceptions when a specified attribute is not defined if it is vital for the operation of your custom view and you can’t assign a default value.

Setting the value we get from the attribute is naturally not enough to change the text of the button. To do this, we can utilize the setter function of the property:

private var buttonText = "Default text"
  set(value) {
      field = value
      parentView.findViewById(R.id.btn_text).text = value
  }
Enter fullscreen mode Exit fullscreen mode

This makes sure that every time the value of the property is changed, the corresponding view’s text is updated.

Usage

We can use our custom view like this:

<com.packagename.MyCustomView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:buttonText="Custom text"/>
Enter fullscreen mode Exit fullscreen mode

The custom attribute may cause an error, saying “Namespace app is not declared”. To fix this, add the following namespace declaration to your root view:

xmlns:app="http://schemas.android.com/apk/res-auto"
Enter fullscreen mode Exit fullscreen mode

Note that using a custom namespace name (instead of “app”) and pointing to your custom view’s package name is not the recommended way in the official documentation. The recommended way is to use res-auto, as I’ve given above.

Android developers can create highly flexible and customizable UI components by using custom view attributes. This is achieved by defining and exposing custom attributes, which enhances reusability, makes views easily configurable, and offers a seamless user experience.

This blog post provides an overview of custom view attributes, including their definition, usage, and how to apply them to custom views. By mastering this technique, developers can build visually appealing and interactive Android applications.

It is important to note that custom view attributes are just one aspect of Android’s extensive toolkit for UI development, and unlocking their potential opens up a world of possibilities for creating delightful user experiences. Happy coding!

Disclaimer: The cover image of this article is created with AI.

Top comments (1)

Collapse
 
kirilldedeshin profile image
Kirill Dedeshin

Very interesting