如何在ConstraintLayout中以编程方式添加视图和约束条件?

94

我遇到了一个问题,需要通过编程的方式向ConstraintLayout中添加视图,并设置布局所需的所有约束条件。

目前我的代码无法正常工作:

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);
ImageView 在布局上甚至都没有出现。当添加到 RelativeLayout 时,它就能正常工作。
我应该怎么做才能创建所需的约束条件,使得我的布局再次起作用呢?
4个回答

153

我认为在添加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

15
为什么这个有效?能够提供一些解释吗?谢谢! - Yonah Karp
10
在将约束布局克隆到集合中之前,您首先要将所有子视图添加到约束布局中,因此所有已添加的视图都被包括在内。我想指出的是,您还需要以编程方式设置ID,否则所有的getId()都将返回-1。 - Allan W
3
我花了很多小时才发现解决方法非常简单,文档中有指向这个地方的说明吗?你是怎么知道的? - Diego Fernando Murillo Valenci
1
@DiegoFernandoMurilloValenci,似乎应用约束集会清除之前应用的约束集,并且可以使用克隆先提取旧的约束集,然后添加新的约束并应用它。换句话说,您可以将一件事情添加到现有的约束布局中,但无法将一个约束添加到现有的约束集中。这只是基于这个答案的推论。我对这些东西和Android一般都很陌生。 - user2297550
2
它给我抛出了异常:java.lang.RuntimeException: ConstraintLayout的所有子元素必须具有ID才能使用ConstraintSet。 - Ali Azaz Alam

2

如何动态向使用XML创建的视图添加元素https://dev59.com/d1kS5IYBdhLWcg3wKzqq#40527407合并。

我发现了一个“更简单”的解决方案,这对于添加多个一致的视图非常有效,它使用 ViewbindingViewModel

  1. 创建fragment_home.xml
  2. fragment_home.xml底部添加LinearLayout
  3. 创建您要填充的dynamic_view.xml
  4. 从ViewModel / Array或任何可用的数据源中获取元素
  5. 填充并添加元素

在这种情况下,我正在创建一个标题文本视图和一个RecyclerView [有适用于此的库,但是我发现通过这样做能够获得更多的控制权]。

步骤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>

第三步

    <?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>

步骤四、五

    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);
        }
    });

0

MotionLayout解决方案

MotionLayout比较棘手,因为它具有多个ConstraintSets,所以需要特殊处理。如果您使用针对ConstraintLayout的解决方案,由于某种原因,它会将您的视图定位在右下角的某个位置。在MotionLayout中添加视图的正确方法是使用updateState而不是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)

为了使您的视图在其他已定义的ConstraintSets中可见(在本例中为end ConstraintSet,即R.id.end),请使用专用的cloneConstraintSet方法并设置视图的大小:
// 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)

0
通过仅设置视图的布局参数,还可以在以编程方式生成的视图中添加约束,而无需创建新的约束集。以下是使用Kotlin和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);

在将视图添加到布局后,修改布局参数也很容易。以下是Kotlin和Java的示例:
view.layoutParams = (tv.layoutParams as ConstraintLayout.LayoutParams).apply {
    topMargin = 120
}

ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) view.getLayoutParams();
layoutParams.topMargin = 120;
view.setLayoutParams(layoutParams);

只是别忘了将修改后的布局参数重新分配给视图。否则,更改将不会生效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接