javaandroidandroid-constraintlayout

ConstraintLayout Custom view refusing to be used as a contraint


I've created some custom views based on ContraintLayout, which is basically a pairing of a textview and some other component.

EditTextCompat and SwitchCompat. Both of them are basically created the same way but only the "interactive" part differs.

When I use the CustomEditTextCompat and use it as layout_constraintTop_toBottomOf="CustomEditTextCompat" it works as expected. But when I do the same and try to use the CustomSwitchCompat as a constraint the view just jumps to the top.

Any suggestions?

custom_switch_compat.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:background="@color/black">

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/centerLeft_line"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="0.49" />

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/centerRight_line"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="0.51" />

<androidx.appcompat.widget.AppCompatTextView
    android:id="@+id/switchTextLabel"
    style="@style/ColorAndFontTheme"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="13dp"
    android:layout_marginEnd="8dp"
    app:layout_constraintEnd_toStartOf="@+id/centerLeft_line"
    app:layout_constraintTop_toTopOf="parent"
    tools:text="Label" />

<androidx.appcompat.widget.SwitchCompat
    android:id="@+id/switchValue"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:checked="false"
    android:theme="@style/menuSwitchTheme"
    app:layout_constraintBottom_toBottomOf="@+id/switchTextLabel"
    app:layout_constraintStart_toEndOf="@+id/centerRight_line"
    app:layout_constraintTop_toTopOf="@+id/switchTextLabel" />
 </androidx.constraintlayout.widget.ConstraintLayout>

CustomSwitchCompat.java

public class CustomSwitchCompat extends ConstraintLayout {
private TextView label;
private SwitchCompat switchCompat;

public CustomSwitchCompat(Context context, AttributeSet attrs) {
    super(context);
    init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
    LayoutInflater.from(context).inflate(R.layout.custom_switch_compat, this, true);
    label = findViewById(R.id.switchTextLabel);
    switchCompat = findViewById(R.id.switchValue);
    TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CustomSwitchCompat);


    String labelText = attributes.getString(R.styleable.CustomSwitchCompat_switchLabelText);
    label.setText(labelText);
    // Additional attributes such as onText and offText can be set here
    attributes.recycle();
}
}

custom_edit_text_compat.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:background="@color/black">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/centerLeft_line"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.49" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/centerRight_line"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.51" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textLabel"
        style="@style/ColorAndFontTheme"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="18dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toStartOf="@+id/centerLeft_line"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Label" />

    <androidx.appcompat.widget.AppCompatEditText
        android:id="@+id/editInput"
        style="@style/ColorAndFontTheme"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="3dp"
        android:textColorHint="@color/gray"
        app:layout_constraintBottom_toBottomOf="@+id/textLabel"
        app:layout_constraintStart_toStartOf="@+id/centerRight_line"
        app:layout_constraintTop_toTopOf="@id/textLabel"
        tools:text="Editbox" />

</androidx.constraintlayout.widget.ConstraintLayout>

CustomEditTextCompat.java

public class CustomEditTextCompat extends ConstraintLayout {
    protected AppCompatEditText editInput;
    private AppCompatTextView textLabel;

    public CustomEditTextCompat(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        LayoutInflater.from(context).inflate(R.layout.custom_edit_text_compat, this, true);

        textLabel = findViewById(R.id.textLabel);
        editInput = findViewById(R.id.editInput);

        TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CustomEditTextCompat);
        String labelText = attributes.getString(R.styleable.CustomEditTextCompat_labelText);
        String hintText = attributes.getString(R.styleable.CustomEditTextCompat_hintText);

        textLabel.setText(labelText);
        editInput.setHint(hintText);

        attributes.recycle();
    }

    public String getEditTextValue() {
        return editInput.getText().toString();
    }

    public void setLabelText(String text) {
        textLabel.setText(text);
    }

    public void setHintText(String hint) {
        editInput.setHint(hint);
    }
}

Usage:

<com..menuFragments.components.CustomEditTextCompat
        android:id="@+id/custom_attacks_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:hintText="Cool fire wave"
        app:labelText="Attack Name"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com..menuFragments.components.CustomSwitchCompat
        android:id="@+id/custom_attacks_switch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/custom_attacks_name"
        app:switchLabelText="Finish when done" />

    <com..menuFragments.components.CustomEditTextCompat
        android:id="@+id/custom_attacks_box_width"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:hintText="Default"
        app:labelText="Box width"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/custom_attacks_switch" />

Solution

  • As you state in a comment: "The only strange thing I notice in the layout inspector is that for the CustomSwitchCompat the id is not set, but how could that be possible?" It is a clue that the id is not set.

    The id of the view is picked up from the attributes which must be parsed. This is accomplished in one of the ConstraintLayout constructors, namely, public ConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs).

    In your implementation, you don't actually parse out the id directly but expect to leave that work to the super class of ConstraintLayout as stated above. Unfortunately, you call super(context) and not super(context, attrs), so the id (and all other attributes that would be picked up from the attributes are not picked up at all.

    Call through to super(context, attrs) to correct your code.