Reputation: 7113
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?
Upvotes: 94
Views: 76945
Reputation: 6827
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.
Upvotes: 0
Reputation: 1711
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
Upvotes: 156
Reputation: 392
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)
Upvotes: 0
Reputation: 1489
Merging this How do I add elements dynamically to a view created with XML with https://stackoverflow.com/a/40527407/4991437
I have discovered an 'easier' solution. This works best for adding multiple consistent views that uses Viewbinding and ViewModel
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);
}
});
Upvotes: 2