javaandroidandroid-layoutandroid-constraintlayoutconstraintset

measured layout size is zero when using ConstraintSet


This is driving me crazy! measured layout is always 0 when i try to build layout programmatically using constraint layout and constraint set.

I tried requestLayout, forceLayout, invalidate on all child and parents. but width and height is 0 rendering whole view invisible.

here is my layout builder. my problem is that views are rendering invisible when i apply constraints.

public final class FormBuilder {

    private Context context;
    private ArrayList<ArrayList<TextInputLayout>> fields;

    private FormBuilder(Context context) {
        this.context = context;
        fields = new ArrayList<>();
        fields.add(new ArrayList<TextInputLayout>());
    }

    private ArrayList<TextInputLayout> getLastRow() {
        return fields.get(fields.size() - 1);
    }

    public static FormBuilder newForm(Context context) {
        return new FormBuilder(context);
    }

    public FormBuilder addField(@NonNull String hint) {
        TextInputLayout field = newTextField(context, hint);
        getLastRow().add(field);
        return this;
    }

    public FormBuilder nextRow() {
        if (getLastRow().size() > 0) fields.add(new ArrayList<TextInputLayout>());
        return this;
    }

    public FormView build() {
        FormView formView = new FormView(context);
        ConstraintSet constraints = new ConstraintSet();

        int topId = ConstraintSet.PARENT_ID;
        for (ArrayList<TextInputLayout> row : fields) {
            if (row.size() == 0) continue;

            int[] rowIds = new int[row.size()];
            for (int i = 0; i < row.size(); i++) { // constraint top
                TextInputLayout field = row.get(i);
                rowIds[i] = field.getId();
                ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(
                        ConstraintLayout.LayoutParams.WRAP_CONTENT,
                        ConstraintLayout.LayoutParams.WRAP_CONTENT
                );
                formView.addView(field, params);

                if (topId == ConstraintSet.PARENT_ID) {
                    constraints.connect(rowIds[i], ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP);
                } else {
                    constraints.connect(rowIds[i], ConstraintSet.TOP, topId, ConstraintSet.BOTTOM);
                }
            }
            if (row.size() >= 2) { // constraint start and end
                constraints.createHorizontalChainRtl(
                        ConstraintSet.PARENT_ID, ConstraintSet.START,
                        ConstraintSet.PARENT_ID, ConstraintSet.END,
                        rowIds, null, ConstraintSet.CHAIN_SPREAD);
            } else { // row.size = 1
                constraints.connect(rowIds[0], ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START);
                constraints.connect(rowIds[0], ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END);
            }

            topId = rowIds[0];
        }
        constraints.applyTo(formView); // this turns layout size to zero
        return formView;
    }

    private static TextInputLayout newTextField(Context context, String hint) {
        TextInputLayout.LayoutParams layoutParams = new TextInputLayout.LayoutParams(
                TextInputLayout.LayoutParams.MATCH_PARENT,
                TextInputLayout.LayoutParams.WRAP_CONTENT
        );
        TextInputLayout inputLayout = new TextInputLayout(context);
        TextInputEditText editText = new TextInputEditText(context);
        inputLayout.addView(editText, layoutParams);
        inputLayout.setHint(hint);
        inputLayout.setId(ViewCompat.generateViewId());
        return inputLayout;
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FormView form = FormBuilder.newForm(this)
                .addField("First name").addField("Last name")
                .nextRow()
                .addField("Phone numnber")
                .build();

        FrameLayout layout = findViewById(R.id.main_content);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT
        );
        layout.addView(form, params);
    }
}

FormView.java

public class FormView extends ConstraintLayout {

    public FormView(Context context) {
        super(context);
    }
}

main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Solution

  • I had to clone ConstraintSet. not only that, cloning must be done after views are added. after adding views and cloning ConstraintSet, it knows size of the views and can set constraints accordingly.

    private FormViewBuilder(Context context) {
        this.context = context;
        formView = new FormView(context); // initialize
        // ...
    }
    
    // ...
    
    public FormViewBuilder addField(int id, @NonNull String key, @NonNull String hint, String requiredMessage) {
        TextInputLayout field = newTextField(context, id, key, hint, requiredMessage);
        getLastRow().add(field);
    
        // add views before build <<================ important
        ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(
                ConstraintLayout.LayoutParams.CHAIN_SPREAD,
                ConstraintLayout.LayoutParams.WRAP_CONTENT
        );
        formView.addView(field, params);
    
        return this;
    }
    
    
    public FormView build() {
        ConstraintSet constraints = new ConstraintSet();
        constraints.clone(formView); // clone after views are added <<================ important
    
        // only set constraints. do not add or remove views
    
        constraints.applyTo(formView);
        return formView;
    }