How to maintain (replace) Drawable XML is a perennial topic in Android development. Following standard Android layout development patterns, we have to create different XML files to describe various UI effects, even a simple rounded corner. As the project iterates, hundreds of thousands of XML files, along with ambiguous file names, not only make reuse or cleanup costs hard to calculate, but also make the project size soar. So let’s explore an elegant and well-suited drawable alternative.

Summary of Traditional schemes

We first summarize the existing schemes on the market, which can be roughly divided into two ways of implementation.

One is to inherit one (or several) of the commonly used controls, then use the commonly used properties in drawable. XML as the custom properties of the current control, and finally dynamically generate drawable within the control as the background for that control. The advantages of this approach are obvious: you can intuitively define the drawable effect description as a property of the control in the layout XML, which is very readable; The downside, however, is that these properties can’t be applied to any control, resulting in the creation of drawable.xml files in many cases.

Another option is to encapsulate the commonly used properties of drawable as code apis that are dynamically generated in code and assigned to controls. In theory, this scheme completely abandons drawable. XML and can be adapted to any control. However, if you want to completely replace XML in this way, I think it is impossible. However, a combination of the two would be a good complement to the first option.

New scheme Exploration

We looked at both options and asked: Why can’t there be an alternative to drawable.xml that is both “highly readable and fully adaptable”? That is, the ability to combine the advantages of both approaches mentioned earlier, high readability means that the description of a drawable needs to be defined as a property in the layout file, and full adaptation means that these properties are valid for any control. On second thought, there seems to be only one answer: DataBinding. Here, some friends may have guessed vaguely, but don’t worry, let me explain.

DataBinding is the official DataBinding library for Android. Although it’s been around for a few years, I’m sure there are still a few developers who haven’t embraced it or even resisted it. I won’t go into details, but I hope you’ll embrace it for the time being and keep reading. DataBinding allows data changes to be reflected directly in the layout. For existing properties of the control, such as the android:text property of TextView, once the DataBinding binding is passed:

<TextView
    android:text="@{name}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
Copy the code

The setText method inside the TextView is called at runtime. The key is that DataBinding uses the @BindingAdapter annotation to associate any specified property with any specified method. DataBinding dynamically generates call relationships at compile time. For common controls, DataBinding already presets annotation methods, such as the setText method for TextView:

@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
    final CharSequence oldText = view.getText();
    if (text == oldText || (text == null && oldText.length() == 0)) {
        return;
    }
    if (text instanceof Spanned) {
        if (text.equals(oldText)) {
            return; // No change in the spans, so don't set anything. } } else if (! haveContentsChanged(text, oldText)) { return; // No content changes, so don't set anything.
    }
    view.setText(text);
}
Copy the code

What we need to focus on is the @BindingAdapter annotation, “Any specified attribute.” The attribute does not specifically refer to the standard attribute that we provide in the layout on Android. That is, we can provide any string as a attribute, and the arbitrary method is easy to understand. The only thing we need to focus on are the parameters of this method: the first parameter specifies the scope of the attribute in the annotation, and the following parameters correspond to the attribute declared in the annotation.

New scheme implementation

Provide a @bindingAdapter annotated method scoped to View (that is, any control); The convention is an attribute in drawable. XML. Do you feel a little subtle? Now that we have the solution, let’s look at the implementation.

Due to the richness of drawable attributes, this paper takes common attributes solid and Corner as examples. As shown in the following snippet:

@BindingAdapter(value = {
        "drawable_solidColor"."drawable_radius",
}, requireAll = false)
public static void setViewBackground(View v, int color, int radius) {
    GradientDrawable drawable = new GradientDrawable();
    drawable.setColor(color);
    drawable.setCornerRadius(radius);
    view.setBackground(drawable);
}

Copy the code

The above code snippet defines two attributes: drawable_solidColor and drawable_radius, which represent the solid color and corner RADIUS attributes, respectively. This means that we can later specify this attribute for each View in the layout file.

Drawable (drawable) {drawable (drawable) {drawable (drawable) {drawable (drawable) {drawable (drawable) {drawable (drawable) {drawable (drawable) There is also the requireAll parameter, which indicates whether the setViewBackground method is required to bind data to each property. When set to false, you can specify only the required properties in the layout file.

These lines of code complete the basic definition, so let’s see how it can be used:

<layout>
    <TextView
        drawable_radius="@ {10}"
        drawable_solidColor="@{0xffff0000}"
        
        android:layout_width="60dp"
        android:layout_height="60dp" />
<layout/>
Copy the code

Do not doubt, is so simple, even if here does not post the effect map, I think we have emerged in the mind, is it clear at a glance? Similarly, other drawable attributes can be implemented one by one in this scheme.

conclusion

In retrospect, there is no complex code or deep logic combination, just a clever alternative to drawable.xml that is “both highly readable and fully adaptable.”

This solution should be minimal in terms of cost (especially for projects that are already using DataBinding) : you only need to define one method, and the results are optimal: in theory, you can reduce drawable.xml creation by 99% by implementing this solution. If I had to say something about the downside of this solution, the DataBinding core library on which it relies for implementation might be unacceptable to some developers.

Do you feel like you haven’t finished reading this? Yes, I have compiled almost all the commonly used drawable attributes and submitted them to GitHub according to the solution in this article. The core is still only one method, which can be used directly.

Github address: github.com/whataa/noDr…