How to programmatically add views and constraints to a ConstraintLayout?
JavaAndroidAndroid LayoutAndroid ConstraintlayoutJava Problem Overview
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?
Java Solutions
Solution 1 - Java
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);```
Solution 2 - Java
Merging this https://stackoverflow.com/q/6369062/4991437 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
- Create the fragment_home.xml
- Add a LinearLayout to the bottom of fragment_home.xml
- Create an dynamic_view.xml that you will inflate
- Get elements from ViewModel/Array or whatever source of data available
- 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);
}
});