Search code examples
androidandroid-studioandroid-constraintlayoutbarrierconstraintset

Adding barriers programmatically via ConstraintSet doesn't work when I change id's of elements?


I've been trying to add a barrier programmatically to a constraint layout in Android Studio using ConstraintSet but I just can't get my code to work, the end result should just be a textView, then a barrier to its right, and another textview that starts from the right of the barrier. I'm adding the barrier using the method ConstraintSet.createBarrier, this is what the result should look like in the app:

"TextView1""TextView2"

That is, textView2 starts at the end of textView1, however long textView1 is. This is activity_main.xml:


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

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView 1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

This is my code in MainActivity.java:


package com.example.test_application;

import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.Barrier;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;


public class MainActivity extends AppCompatActivity {

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    @SuppressLint("ResourceType")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LayoutInflater inflater = getLayoutInflater();
        ConstraintLayout myConstraintLayout = (ConstraintLayout) findViewById(R.id.content);

        View textView1 = findViewById(R.id.textView1);
        textView1.setId(1);

        TextView textView2 = new TextView(this);
        textView2.setId(2);
        textView2.setText("HELLO");

        myConstraintLayout.addView(textView2);

        ConstraintSet constraintSet = new ConstraintSet();
        constraintSet.clone(myConstraintLayout);
        int[] createBarrier_ids = {textView1.getId()};
        constraintSet.createBarrier(3, Barrier.END,0, createBarrier_ids);
        constraintSet.connect(textView2.getId(), ConstraintSet.START, 3, ConstraintSet.END,0);
        constraintSet.connect(textView2.getId(), ConstraintSet.TOP, textView1.getId(), ConstraintSet.TOP,0);
        constraintSet.applyTo(myConstraintLayout);
    }
}

This is the result when I run it:

result changing id

Note how in line textView1.setId(1) I'm changing the id of textView1 to 1 (I've also tried changing the id using textView1.setId(View.generateViewId()) and get the same result), when I disable that line and don't change the id of textView1 and run the code again I get the result I want:

result not changing id

What am I doing wrong? Why does it work when I don't change the id? I want to be able to change id's in my code.


Solution

  • I believe that ConstraintLayout maintains internal view-related structures apart from the view hierarchy itself. When you change the id of textView1, the id is not being propagated to these other internal structures. (A guess, but I believe it is a good one.)

    If you must change the id of a view, I suggest that you remove it from the hierarchy and re-add it. This will force ConstraintLayout to accept your changes into its structures. Something like this:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        LayoutInflater inflater = getLayoutInflater();
        ConstraintLayout myConstraintLayout = (ConstraintLayout) findViewById(R.id.content);
    
        View textView1 = findViewById(R.id.textView1);
        myConstraintLayout.removeView(textView1); // So long old textView1!
        textView1.setId(1);
        myConstraintLayout.addView(textView1); // Hello new textView1.
    
        TextView textView2 = new TextView(this);
        textView2.setId(2);
        textView2.setText("HELLO");
    
        myConstraintLayout.addView(textView2);
    
        ConstraintSet constraintSet = new ConstraintSet();
        constraintSet.clone(myConstraintLayout);
        // Make sure to reconnect textView1 since it was added earlier.
        constraintSet.connect(textView1.getId(),ConstraintSet.START,ConstraintSet.PARENT_ID,ConstraintSet.START);
        constraintSet.connect(textView1.getId(),ConstraintSet.TOP,ConstraintSet.PARENT_ID,ConstraintSet.TOP);
        int[] createBarrier_ids = {textView1.getId()};
        constraintSet.createBarrier(3, Barrier.END,0, createBarrier_ids);
        constraintSet.connect(textView2.getId(), ConstraintSet.START, 3, ConstraintSet.END,0);
        constraintSet.connect(textView2.getId(), ConstraintSet.TOP, textView1.getId(), ConstraintSet.TOP,0);
        constraintSet.applyTo(myConstraintLayout);
    }