This post will be about an interesting observation I made when working on a custom View that did not extend EditText, but contained one.

To show an example, I created a project that contains a widget called ToggleEditText. This simple widget has the following features:

  • it contains an EditText and a CheckBox
  • toggling the CheckBox enables/disables the EditText
  • it overrides View‘s setEnabled(boolean) method to achieve the same result as toggling the checkbox

The problem

Starting with the previous example, let’s say that I would like to make the ToggleEditText start with a disabled state. Since the View class contains the setEnabled method, one could think that this is just as simple as adding android:enabled="false" to our edit text in the layout:

<com.gyulajuhasz.example.enabledattribute.view.ToggleEditText
    android:id="@+id/main_activity_toggle_edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:enabled="false"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

After trying it with eagerness, we may get a little surprise: it just does not work. You can download the code from here.

The expected result

To illustrate how this should have worked I created a CustomEditText that extends AppCompatEditText and added it to the layout with android:enabled="false":

<com.gyulajuhasz.example.enabledattribute.view.CustomEditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:enabled="false"
    android:hint="@string/should_be_disabled_by_default"
    app:layout_constraintBottom_toTopOf="@+id/main_activity_toggle_button"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/main_activity_toggle_edit_text" />

When running the code, You can verify that the custom edit text in the center of the screen is disabled. The main question is… what is the difference between the two cases that makes one of them succeed and the other fail?

The explanation

To find out the cause of the unexpected behavior, two basic concepts have to be revised about custom Views:

This means that the attributes and code of the View class has to be checked for the answer. The latest version of the Android framework’s attributes can be found here, and the code of the View class can be seen here.

Examining the code of the View class, we can notice that R.styleable.View_enabled is not referenced anywhere in the class. How is this possible? Is this attribute not processed?

Looking at attrs.xml and searching for the enabled attribute, we can finally see that this attribute is not declared for View, but for TextView instead!

<declare-styleable name="TextView">
            ...
    <!-- Specifies whether the widget is enabled. The interpretation of the enabled state varies by subclass.
         For example, a non-enabled EditText prevents the user from editing the contained text, and
         a non-enabled Button prevents the user from tapping the button.
         The appearance of enabled and non-enabled widgets may differ, if the drawables referenced
         from evaluating state_enabled differ. -->
    <attr name="enabled" format="boolean" />
            ...
</declare-styleable>

Checking the source code of TextView, we can easily find the code that uses the attribute:

case com.android.internal.R.styleable.TextView_enabled:
    setEnabled(a.getBoolean(attr, isEnabled()));
    break;

The workaround

Fortunately, it is not too hard to add ToggleEditText the ability to use the attribute. First, the attribute has to be declared for it and processed in its constructor(s). The declaration can be seen below. Please note that the type of the attribute is not specified, because the attribute is reused. Specifying its type will result in a compilation failure.

<!-- Attributes for the ToogleEditText widget. -->
<declare-styleable name="ToggleEditText">
    <!-- Reusing android:enabled attribute. -->
    <attr name="android:enabled" />
</declare-styleable>

All we have to do is process the value of the attribute this way (notice the android_ part in the name of the styleable attribute):

final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ToggleEditText, defStyleAttr, 0);
setEnabled(typedArray.getBoolean(R.styleable.ToggleEditText_android_enabled, isEnabled()));
typedArray.recycle();

The code that has this workaround can be seen here. After building and running the app, it can be seen that the ToggleEditText is disabled by default, just as we wanted.

Conclusion

In my opinion, the enabled attribute should be declared and processed by the View class, since the corresponding setEnabled(boolean) method is there. I don’t know if this was an intentional decision by the Android framework developers or it was just accidentally done. To find out, I issued a bug report for Google about this situation in hopes of getting some explanation or a fix for this issue. Until that happens, the desired behavior can still be achieved by redeclaring and reusing the attribute in every custom View where it is needed. However, I think that this should not be necessary.