How to programmatically add views and constraints to a ConstraintLayout?
Asked Answered
M

4

94

I'm having a problem to programmatically add views to a ConstraintLayout, and set up all the constraints required for the layout to work.

What I have at the moment doesn't work:

ConstraintLayout layout = (ConstraintLayout) findViewById(R.id.mainConstraint);
ConstraintSet set = new ConstraintSet();
set.clone(layout);

ImageView view = new ImageView(this);
layout.addView(view,0);
set.connect(view.getId(), ConstraintSet.TOP, layout.getId(), ConstraintSet.TOP, 60);
set.applyTo(layout);

The ImageView doesn't even appear on the layout. When adding to a RelativeLayout, it works like a charm.

What can I do to create the constraints I need, so that my layout work again?

Martingale answered 27/10, 2016 at 2:29 Comment(0)
B
154

I think you should clone the layout after adding your ImageView.

ConstraintLayout layout = (ConstraintLayout)findViewById(R.id.mainConstraint);
ConstraintSet set = new ConstraintSet();

ImageView view = new ImageView(this);
view.setId(View.generateViewId());  // cannot set id after add
layout.addView(view,0);
set.clone(layout);
set.connect(view.getId(), ConstraintSet.TOP, layout.getId(), ConstraintSet.TOP, 60);
set.applyTo(layout); // apply to layout
Buckish answered 10/11, 2016 at 12:15 Comment(5)
Why does this work? Some explanation would be appreciated. Thanks!Quintuplet
@YonahKarp You are first adding all of the children to the constraint layout before cloning it to the set, so all the views you've added are included. I'd like to point out that you need to set your id programmatically as well, otherwise all getId()s will return -1Cascara
I spent so many hours and the solution was too simple, there is some place where is this pointed in documentation? how do you know this?Domenic
@DiegoFernandoMurilloValenci it seems that applying a constraintset clears previously applied constraintsets and knowing that one could first extract the old constraintset using clone and then add the new constraint and apply it. In other words, you can add one thing to an existing constraintlayout but you can't add one constraint to the existing constraintset. This is just my inference based solely on this answer. I am very new to this stuff and Android in general.Washbowl
It's giving me exception: java.lang.RuntimeException: All children of ConstraintLayout must have ids to use ConstraintSetBismuthinite
L
2

Merging this How do I add elements dynamically to a view created with XML with https://mcmap.net/q/189199/-how-to-programmatically-add-views-and-constraints-to-a-constraintlayout

I have discovered an 'easier' solution. This works best for adding multiple consistent views that uses Viewbinding and ViewModel

  1. Create the fragment_home.xml
  2. Add a LinearLayout to the bottom of fragment_home.xml
  3. Create an dynamic_view.xml that you will inflate
  4. Get elements from ViewModel/Array or whatever source of data available
  5. Inflate and add elements

In this case I am creating a textview(title) with a recyclerview[there are libs for this, however, I found more control doing this]

Step 1,2

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView 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"
    tools:context=".ui.hometab.HomeFragment">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/constraint_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- otherviews here -->

        <LinearLayout
            android:id="@+id/dynamic_linear_layout"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/margin_large"
            android:orientation="vertical"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/curated_recycler" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

Step 3

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

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="@dimen/margin_large"
        app:layout_constraintStart_toStartOf="parent" />

    <View
        android:id="@+id/title_view"
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:layout_gravity="center"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <TextView
        android:id="@+id/shop_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/diary"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        android:textColor="@android:color/black"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="@+id/title_view"
        app:layout_constraintStart_toStartOf="@id/guideline"
        app:layout_constraintTop_toTopOf="@+id/title_view" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/shop_recycler"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:clipToPadding="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@id/guideline"
        app:layout_constraintTop_toBottomOf="@+id/title_view" />

</androidx.constraintlayout.widget.ConstraintLayout>

Step 4,5

    binding = FragmentHomeBinding.inflate(getLayoutInflater());

    ShopViewModel shopViewModel = new ViewModelProvider(this).get(ShopViewModel.class);
    shopViewModel.getAllShops(false).observe(getViewLifecycleOwner(), shopEntities -> {
        List<ConstraintLayout> shopConstraints = new ArrayList<>();

        for (ShopEntity shopEntity : shopEntities) {
            // get an instance of the dynamic_view.xml
            DynamicViewBinding dynamicViewBinding = DynamicViewBinding.inflate(getLayoutInflater());
            // add text view content
            dynamicViewBinding.shopName.setText(shopEntity.getName());
            // initialize the recycler layout adapter
            dynamicViewBinding.shopRecycler.setLayoutManager(new ProductCardLayoutManager(getContext(), 1,
                    GridLayoutManager.HORIZONTAL, false, 80));

            // get all products and add them to recycler view
            productViewModel.getAllProductsByShop(shopEntity.getShopId(), 10).observe(getViewLifecycleOwner(), productEntities ->
                    dynamicViewBinding.shopRecycler.setAdapter(new ProductAdapter(getActivity(), productEntities)));

            // attach dynamic view to list of dynamic constraint layouts
            shopConstraints.add(dynamicViewBinding.getRoot());
        }

        // remove all previous views from the linear layout
        binding.dynamicLinearLayout.removeAllViews();

        // add the new views
        for (ConstraintLayout shopConstraint : shopConstraints) {
            binding.dynamicLinearLayout.addView(shopConstraint);
        }
    });
Longcloth answered 29/7, 2021 at 12:28 Comment(0)
P
0

MotionLayout Solution

MotionLayout is trickier as it requires special handling due to it having multiple ConstraintSets. If you use the solution intended for ConstraintLayout, for some reason it will position your views somewhere in the bottom right. The correct way to add view in MotionLayout is to use updateState instead of applyTo:

/* ...up until this point the steps are the same: create constraint set,
set view's id, clone, connect, etc. */

layout.updateState(R.id.start, set)

In order to make your view visible in other defined ConstraintSets (in this case the end ConstraintSet, aka R.id.end), use the dedicated cloneConstraintSet method and set the view's size:

// Clone the constraint set using MotionLayout's `cloneConstraintSet`
val setEnd = layout.cloneConstraintSet(R.id.end)
// Set the view's width and height
setEnd.constrainWidth(view.id, ConstraintLayout.LayoutParams.WRAP_CONTENT)
setEnd.constrainHeight(view.id, ConstraintLayout.LayoutParams.WRAP_CONTENT)
// Update state
layout.updateState(R.id.end, setEnd)
Popele answered 9/2, 2023 at 7:19 Comment(0)
E
0

Adding constraints to a programmatically generated view is also possible without creating a new constraint set, but simply by just setting the layout parameters of the view. Here is an example in Kotlin and Java:

val view = ImageView(this)
view.layoutParams = ConstraintLayout.LayoutParams(
    ConstraintLayout.LayoutParams.WRAP_CONTENT,
    ConstraintLayout.LayoutParams.WRAP_CONTENT
).apply {
    topToTop = R.id.mainConstraint
    topMargin = 60
}

val layout = findViewById<ConstraintLayout>(R.id.mainConstraint)
layout.addView(view)
ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(
    ConstraintLayout.LayoutParams.WRAP_CONTENT,
    ConstraintLayout.LayoutParams.WRAP_CONTENT
);
layoutParams.topToTop = R.id.mainConstraint;
layoutParams.topMargin = 60;

ImageView view = new ImageView(this);
view.setLayoutParams(layoutParams);

ConstraintLayout layout = findViewById(R.id.mainConstraint);
layout.addView(view);

It is also easy to modify the layout parameters after a view has already been added to a layout. Again an example in Kotlin and Java:

view.layoutParams = (tv.layoutParams as ConstraintLayout.LayoutParams).apply {
    topMargin = 120
}
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) view.getLayoutParams();
layoutParams.topMargin = 120;
view.setLayoutParams(layoutParams);

Just don't forget to re-assign the modified layout parameters to the view. Otherwise the changes won't be applied.

Electrokinetic answered 22/11, 2023 at 20:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.