214

I need help with ConstraintSet. My goal is to change view's constraints in code, but I cant figure out how to do this right.

I have 4 TextViews and one ImageView. I need to set ImageView constraints to one of the TextViews.

check_answer4 = (TextView) findViewById(R.id.check_answer4);
check_answer1 = (TextView) findViewById(R.id.check_answer1);
check_answer2 = (TextView) findViewById(R.id.check_answer2);
check_answer3 = (TextView) findViewById(R.id.check_answer3);

correct_answer_icon = (ImageView) findViewById(R.id.correct_answer_icon);

If 1st answer is right, I need to set constraints of ImageView to

app:layout_constraintRight_toRightOf="@+id/check_answer1"
app:layout_constraintTop_toTopOf="@+id/check_answer1"

If 2nd answer is right, I need to set constraints of ImageView to

app:layout_constraintRight_toRightOf="@+id/check_answer2"
app:layout_constraintTop_toTopOf="@+id/check_answer2"

And so on.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
Big Coach
  • 2,485
  • 2
  • 12
  • 31

10 Answers10

323
  1. To set constraints of image view to:

     app:layout_constraintRight_toRightOf="@+id/check_answer1"
     app:layout_constraintTop_toTopOf="@+id/check_answer1"
    

    use:

     ConstraintLayout constraintLayout = findViewById(R.id.parent_layout);
     ConstraintSet constraintSet = new ConstraintSet();
     constraintSet.clone(constraintLayout);
     constraintSet.connect(R.id.imageView,ConstraintSet.RIGHT,R.id.check_answer1,ConstraintSet.RIGHT,0);
     constraintSet.connect(R.id.imageView,ConstraintSet.TOP,R.id.check_answer1,ConstraintSet.TOP,0);
     constraintSet.applyTo(constraintLayout);
    
  2. To set constraints of image view to:

     app:layout_constraintRight_toRightOf="@+id/check_answer2"
     app:layout_constraintTop_toTopOf="@+id/check_answer2"
    

    use:

     ConstraintLayout constraintLayout = findViewById(R.id.parent_layout);
     ConstraintSet constraintSet = new ConstraintSet();
     constraintSet.clone(constraintLayout); 
     constraintSet.connect(R.id.imageView,ConstraintSet.RIGHT,R.id.check_answer2,ConstraintSet.RIGHT,0);      
     constraintSet.connect(R.id.imageView,ConstraintSet.TOP,R.id.check_answer2,ConstraintSet.TOP,0);
     constraintSet.applyTo(constraintLayout);
    
DavidB.
  • 99
  • 1
  • 4
vishakha yeolekar
  • 3,294
  • 1
  • 9
  • 9
143

Assume we want to change constraints during runtime, making button1 to be aligned with button2 when clicked:

enter image description here

Then, having this layout:

<android.support.constraint.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="match_parent"
    android:id="@+id/root"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">


    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
        app:layout_constraintTop_toTopOf="@+id/button3"
        app:layout_constraintBottom_toBottomOf="@+id/button3"
        app:layout_constraintStart_toEndOf="@+id/button3"
        android:layout_marginStart="0dp"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginEnd="0dp" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:text="Button 2"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginStart="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginEnd="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="8dp"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintVertical_bias="0.5" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:text="Button 3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginStart="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginEnd="8dp"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="8dp"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintVertical_bias="0.223" />
</android.support.constraint.ConstraintLayout>

We can do following:


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button1.setOnClickListener {
            val params = button1.layoutParams as ConstraintLayout.LayoutParams
            params.leftToRight = button2.id
            params.topToTop = button2.id
            params.bottomToBottom = button2.id
            button1.requestLayout()
        }
    }

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • My friend, I don't get your code.. what is `layoutParams` and `val`? Is this even Java? – leonheess Oct 19 '18 at 15:18
  • 73
    Sir, it's kotlin programming language. Java equivalent will be `ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) button1.getLayoutParams();` – azizbekian Oct 19 '18 at 19:02
  • 1
    I think you forgot to write button1.layoutParams = params – sumit sonawane May 20 '19 at 10:52
  • 2
    @sumitsonawane, it's not needed, because we are mutating that instance and after that we perform `button1.requestLayout()` which then will inspect the instance of `LayoutParams` that we have mutated. – azizbekian May 20 '19 at 11:48
  • assume I have a custom view, inside the custom view layout there are Button (with id @+id/btn) and ImageView(with id @+id/imageView), then I add my custom view into my activity layout, in my activity layout, I have TextView, how about changing the TextView constraint to View inside my customView? I tried your solution but it didn't work – adrianekafikri Jun 21 '19 at 09:29
  • @azizbekian, it might be strange, but for me it didn't work without assigning new layoutParams object to the View. – qwertyfinger Jul 08 '19 at 15:20
  • @qwertyfinger, indeed strange, because `setLayoutParams()` will eventually call `requestLayout()`. – azizbekian Jul 08 '19 at 15:27
  • 4
    @azizbekian, yes, for me the final solution is replacing `requestLayout()` call with `setLayoutParams()` and then it works. Simply mutating `layoutParams` and requesting layout myself doesn't seem to do the trick. – qwertyfinger Jul 10 '19 at 07:09
  • How do you unset some constraints? Say I don't want the left side of the button to be constrained to anything. – Oleg Yablokov Aug 04 '20 at 14:50
  • This solution works for me. One more little thing, how can I make it with animation – Scofield Tran Nov 24 '20 at 02:22
  • @ScofieldTran, as simple as performing `TransitionManager.beginDelayedTransition()` before layout parameters. See an example [here](https://stackoverflow.com/a/43629482/1083957). – azizbekian Nov 24 '20 at 07:50
  • It's enough to call `setLayoutParams()` , method `requestLayout()` is calling inside it. – vmayatskii Jul 20 '22 at 07:59
105

Another approach is to update the layout params of view like this (without requesting Layout):

yourView.updateLayoutParams<ConstraintLayout.LayoutParams> {
        startToEnd = targetView.id
        topToTop = targetView.id
        bottomToBottom = targetView.id
        //add other constraints if needed
      }
Astrit Veliu
  • 1,252
  • 1
  • 12
  • 22
  • 3
    Wow. I had no idea `View.updateLayoutParams` was a native extension. This makes updating layout parameters programmatically much more clean and readable. – Tamarisk Jun 07 '22 at 00:56
  • Resolved my problem. Thank you, your code very clean and goal – Diego Farias Apr 19 '23 at 14:54
24

In addition to azizbekian's answer, let me point out two things:

  1. If left/right didn't work, try start/end like this:

params.startToEnd = button2.id

  1. If you want to remove a constraint, use UNSET flag like this:

params.startToEnd = ConstraintLayout.LayoutParams.UNSET

Oleg Yablokov
  • 736
  • 8
  • 19
13

In Kotlin you can simply extend ConstraintSet class and add some methods to take advantage of dsl in Kotlin and produce a more readable code. Like this

class KotlinConstraintSet : ConstraintSet() {

    companion object {
        inline fun buildConstraintSet(block:KotlinConstraintSet.()->Unit) =
            KotlinConstraintSet().apply(block)
    }
    //add this if you plan on using the margin param in ConstraintSet.connect
    var margin: Int? = null
        get() {
            val result = field
            margin = null //reset it to work with other constraints
            return result
        }

    inline infix fun Unit.and(other: Int) = other // just to join two functions

    inline infix fun Int.topToBottomOf(bottom: Int) =
        margin?.let {
            connect(this, TOP, bottom, BOTTOM, it)
        } ?: connect(this, TOP, bottom, BOTTOM)

    inline fun margin(margin: Int) {
        this.margin = margin
    }

    inline infix fun Int.bottomToBottomOf(bottom: Int) =
        margin?.let {
            connect(this, BOTTOM, bottom, BOTTOM, it)
        } ?: connect(this, BOTTOM, bottom, BOTTOM)

    inline infix fun Int.topToTopOf(top: Int) =
        margin?.let {
            connect(this, TOP, top, TOP, it)
        } ?: connect(this, TOP, top, TOP)

    inline infix fun Int.startToEndOf(end: Int) =
        margin?.let {
            connect(this, START, end, END, it)
        } ?: connect(this, START, end, END)

            ...
    //TODO generate other functions depending on your needs

    infix fun Int.clear(constraint: Constraints) =
        when (constraint) {
            Constraints.TOP -> clear(this, TOP)
            Constraints.BOTTOM -> clear(this, BOTTOM)
            Constraints.END -> clear(this, END)
            Constraints.START -> clear(this, START)
        }

    //inline infix fun clearTopCon
    inline infix fun appliesTo(constraintLayout: ConstraintLayout) =
        applyTo(constraintLayout)

    inline infix fun clones(constraintLayout: ConstraintLayout) =
        clone(constraintLayout)

    inline fun constraint(view: Int, block: Int.() -> Unit) =
        view.apply(block)
}

enum class Constraints {
    TOP, BOTTOM, START, END //you could add other values to use with the clear fun like LEFT
}

And use it like this

        buildConstraintSet {
            this clones yourConstraintLayout
            constraint(R.id.view1) {
                margin(value:Int) and this topToBottomOf R.id.view2
                margin(30) and this bottomToBottomOf ConstraintSet.PARENT_ID
            }
            constraint(R.id.view2) {
                this clear Constraints.BOTTOM
                margin(0) and this topToTopOf R.id.topGuide
            }
            constraint(R.id.view4) {
                this topToTopOf R.id.view2
                this bottomToBottomOf R.id.view3
                this startToEndOf R.id.view2
            }
            //or you could simply do
            R.id.view1 startToEndOf R.view2
            R.id.view1 toptToBottomOf R.view3
            R.id.view3 bottomtToBottomOf R.view2
            R.id.view3 clear Constraints.END

            // and finally call applyTo()
            this appliesTo yourConstraintLayout
        }
Kofi
  • 525
  • 6
  • 15
8

I know my answer is very late, yet I'm sure It'd help others that stop by here a lot. This article is not mine but I made a few changes, that being said, you should endeavor to check out the full article here

Constraint Sets

The key to working with constraint sets in Java code is the ConstraintSet class. This class contains a range of methods that allow tasks such as creating, configuring and applying constraints to a ConstraintLayout instance. In addition, the current constraints for a ConstraintLayout instance may be copied into a ConstraintSet object and used to apply the same constraints to other layouts (with or without modifications).

A ConstraintSet instance is created just like any other Java object:

ConstraintSet set = new ConstraintSet();

Once a constraint set has been created, methods can be called on the instance to perform a wide range of tasks. The following code configures a constraint set in which the left-hand side of a Button view is connected to the right-hand side of an EditText view with a margin of 70dp:

set.connect(button1.getId(), ConstraintSet.LEFT, 
        editText1.getId(), ConstraintSet.RIGHT, 70);

Applying Constraints to a Layout Once the constraint set is configured, it must be applied to a ConstraintLayout instance before it will take effect. A constraint set is applied via a call to the applyTo() method, passing through a reference to the layout object to which the settings are to be applied:

set.applyTo(myLayout);

There are lot more stuffs you can do with the ConstraintSet API, Setting horizontal and vertical bias, center horizontally and vertically, manipulate Chains and a lot more.

Really nice read.

Again, this is just an adaptation.

5

You can create constraints into ConstraintLayout with ConstraintSet or ConstraintLayout.LayoutParams properties.

I created a ConstraintSet kotlin extension for simple usage. This is simple and robust.

Usage

 applyConstraint {
            centerHorizontallyParent(imageView)
            centerHorizontallyParent(textView)
            topToParent(imageView, px(12))
            topToBottom(textView, imageView, px(4))
        }

Code

fun ConstraintLayout.applyConstraint(block: ConstraintSet.() -> Unit) {
    ConstraintSet().apply {
        clone(this@applyConstraint)
        block(this)
    }.applyTo(this)
}

fun ConstraintSet.centerParent(v1: View) {
    centerHorizontallyParent(v1)
    centerVerticallyParent(v1)
}

fun ConstraintSet.centerHorizontallyParent(v1: View) {
    centerHorizontally(v1.id, PARENT_ID)
}

fun ConstraintSet.centerVerticallyParent(v1: View) {
    centerVertically(v1.id, PARENT_ID)
}

fun ConstraintSet.topToTop(v1: View, v2: View, @Px margin: Int = 0) {
    connect(v1.id, TOP, v2.id, TOP, margin)
}

fun ConstraintSet.topToParent(v1: View, @Px margin: Int = 0) {
    connect(v1.id, TOP, PARENT_ID, TOP, margin)
}

fun ConstraintSet.bottomToBottom(v1: View, v2: View, @Px margin: Int = 0) {
    connect(v1.id, BOTTOM, v2.id, BOTTOM, margin)
}

fun ConstraintSet.bottomToParent(v1: View, @Px margin: Int = 0) {
    connect(v1.id, BOTTOM, PARENT_ID, BOTTOM, margin)
}

fun ConstraintSet.topToBottom(v1: View, v2: View, @Px margin: Int = 0) {
    connect(v1.id, TOP, v2.id, BOTTOM, margin)
}

fun ConstraintSet.bottomToTop(v1: View, v2: View, @Px margin: Int = 0) {
    connect(v1.id, BOTTOM, v2.id, TOP, margin)
}

fun ConstraintSet.startToStart(v1: View, v2: View, @Px margin: Int = 0) {
    connect(v1.id, START, v2.id, START, margin)
}

fun ConstraintSet.startToParent(v1: View, @Px margin: Int = 0) {
    connect(v1.id, START, PARENT_ID, START, margin)
}

fun ConstraintSet.endToEnd(v1: View, v2: View, @Px margin: Int = 0) {
    connect(v1.id, END, v2.id, END, margin)
}

fun ConstraintSet.endToParent(v1: View, @Px margin: Int = 0) {
    connect(v1.id, END, PARENT_ID, END, margin)
}

fun ConstraintSet.startToEnd(v1: View, v2: View, @Px margin: Int = 0) {
    connect(v1.id, START, v2.id, END, margin)
}

fun ConstraintSet.endToStart(v1: View, v2: View, @Px margin: Int = 0) {
    connect(v1.id, END, v2.id, START, margin)
}
MJ Studio
  • 3,947
  • 1
  • 26
  • 37
2

@vishakha yeolekar 's solution does not work for me.

To change constraints, we need to follow these steps:

  • clone parent layout
  • clear previous constraint
  • connect constraint
  • apply constraint to parent layout

Solution code (in Kotlin)

val clParent = findViewById<ConstraintLayout>(R.id.parent_layout)
val mConstraintSet = ConstraintSet()
mConstraintSet.clone(clParent)
mConstraintSet.clear(R.id.imageView, ConstraintSet.END)
mConstraintSet.connect(R.id.imageView, ConstraintSet.END, R.id.check_answer, ConstraintSet.END)
mConstraintSet.applyTo(clParent)

Here is the link for more info and methods of ConstraintSet - Click Here.

Nitin Patel
  • 1,605
  • 13
  • 31
2

You can also use TransitionManager to animate the changes:

public static void animateConstraintLayout(ConstraintLayout constraintLayout, ConstraintSet set, long duration) {
    AutoTransition trans = new AutoTransition();
    trans.setDuration(duration);
    trans.setInterpolator(new AccelerateDecelerateInterpolator());

    TransitionManager.beginDelayedTransition(constraintLayout, trans);
    set.applyTo(constraintLayout);
}

You can call it like this after updating the layout with ConstraintSet:

ConstraintSet set = new ConstraintSet();
set.clone(constraintLayout);
set.connect(R.id.example, ConstraintSet.BOTTOM, R.id.anotherExample, ConstraintSet.BOTTOM);
set.connect(R.id.example, ConstraintSet.TOP, R.id.anotherExample, ConstraintSet.TOP);

animateConstraintLayout(constraintLayout, set, 500);
Mohamed Salah
  • 868
  • 1
  • 15
  • 34
0

For example:

app:layout_constraintRight_toRightOf = "parentView"

If you liked to declared it with programe, it will be...

ConstraintLayout.LayoutParams.rightToRight = parentView.getId()

If you used programe to declared the parentview(viewgroup), don't forget to give the parentview a generated id.

parentView.setId(View.generatedId());
hsu.tw
  • 148
  • 2
  • 10