This post will be about an interesting observation I made when working on a custom View that did not extend
EditText, but contained one.
- it contains an
- toggling the CheckBox enables/disables the EditText
- it overrides
View‘s setEnabled(boolean) method to achieve the same result as toggling the checkbox
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
<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?
To find out the cause of the unexpected behavior, two basic concepts have to be revised about custom Views:
- a custom View declares its attributes via a
- the values of the attributes are read and applied by using TypedArrays, typically in the constructor
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?
attrs.xml and searching for the
enabled attribute, we can finally see that this attribute is not declared for
View, but for
<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;
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.
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.