Nick Wilde
Nick Wilde

Reputation: 175

Dynamic view height in constraint layout with chain style packed/spread

It has been quite some time since I used XML layout for Android. I am struggling with what seems to be very simple thing, yet I cannot find a quick solution.

I have a constraintLayout with an image, title, description and a button:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
    tools:context=".FirstFragment">

    <ImageView
        android:id="@+id/image_cover"
        android:layout_width="200dp"
        android:layout_height="300dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:background="@android:color/holo_red_dark"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="5dp"
        android:textColor="@color/black"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/image_cover"
        app:layout_constraintTop_toTopOf="@id/image_cover"
        tools:text="Title" />

    <TextView
        android:id="@+id/description"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="@id/title"
        app:layout_constraintBottom_toTopOf="@id/button"
        app:layout_constraintEnd_toEndOf="@id/title"
        app:layout_constraintVertical_bias="0.0"
        app:layout_constraintVertical_chainStyle="spread_inside"
        android:text="@string/some_very_long_text"/>

    <Button
        android:id="@+id/button"
        app:layout_constraintTop_toBottomOf="@id/description"
        app:layout_constraintBottom_toBottomOf="@id/image_cover"
        app:layout_constraintStart_toStartOf="@id/title"
        android:layout_width="wrap_content"
        android:padding="0dp"
        android:layout_margin="0dp"
        android:background="@color/black"
        android:text="Button"
        android:layout_height="36dp"/>


</androidx.constraintlayout.widget.ConstraintLayout>
  1. I want to achieve the following result when the description is short:

enter image description here

  1. The following result when description is long:

enter image description here

I tried to create a chain with either packed (and vertical bias) or spread-inside, however I am only able to achieve either result 1 or 2 and not both.

The idea to do it in XML only, not in Java/Kotlin code.

Upvotes: 0

Views: 522

Answers (1)

Cheticamp
Cheticamp

Reputation: 62831

Since you specify that the button is 39dp in height, you can set a Space widget 39dp from the bottom of the description using a bottom margin. Now set a barrier with a direction = bottom to the Space widget and the description. Now the barrier will float between the bottom of the Space widget and the bottom of the description and will alight on whichever is lower.

Now you can set the top of the button to the bottom of the Barrier and the button will float. The XML is below.

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FirstFragment">

    <ImageView
        android:id="@+id/image_cover"
        android:layout_width="200dp"
        android:layout_height="300dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:background="@android:color/holo_red_dark"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="5dp"
        android:textColor="@color/black"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/image_cover"
        app:layout_constraintTop_toTopOf="@id/image_cover"
        tools:text="Title" />

    <TextView
        android:id="@+id/description"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/some_very_long_text"
        app:layout_constraintBottom_toTopOf="@id/button"
        app:layout_constraintEnd_toEndOf="@id/title"
        app:layout_constraintStart_toStartOf="@id/title"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintVertical_bias="0.0"
        app:layout_constraintVertical_chainStyle="spread_inside" />

    <Space
        android:id="@+id/space"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="36dp"
        android:padding="0dp"
        app:layout_constraintBottom_toBottomOf="@id/image_cover"
        app:layout_constraintStart_toStartOf="@id/title" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="36dp"
        android:layout_margin="0dp"
        android:background="@color/black"
        android:padding="0dp"
        android:text="Button"
        app:layout_constraintStart_toStartOf="@id/title"
        app:layout_constraintTop_toBottomOf="@id/barrier" />

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="bottom"
        app:constraint_referenced_ids="description,space" />

</androidx.constraintlayout.widget.ConstraintLayout>

If you don't explicitly know the height of the button, you can create an invisible clone of the button with the same constraints and use the invisible button for the barrier.

Here is another version that also works, but I am not sure why. It doesn't use a Space.

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FirstFragment">

    <ImageView
        android:id="@+id/image_cover"
        android:layout_width="200dp"
        android:layout_height="300dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:background="@android:color/holo_red_dark"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="5dp"
        android:text="Title"
        android:textColor="@color/black"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/image_cover"
        app:layout_constraintTop_toTopOf="@id/image_cover" />

    <TextView
        android:id="@+id/description"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/some_very_long_text1"
        app:layout_constraintBottom_toTopOf="@id/button"
        app:layout_constraintEnd_toEndOf="@id/title"
        app:layout_constraintStart_toStartOf="@id/title"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintVertical_bias="0.0"/>

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="36dp"
        android:layout_margin="0dp"
        android:background="@color/black"
        android:padding="0dp"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="@id/barrier"
        app:layout_constraintStart_toStartOf="@id/title" />

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="bottom"
        app:constraint_referenced_ids="image_cover,description" />

</androidx.constraintlayout.widget.ConstraintLayout>

Update: I was curious about the last layout above. Although it works, I wouldn't use it and I have come to the conclusion that it is a bug since the barrier is located below the button but should be immediately below either the description or the image cover. I am using "androidx.constraintlayout:constraintlayout:2.1.4".

More: I no longer think that this is a bug but a reasonable resolution to a, seemingly, impossible condition: The barrier is positioned to the bottom of the TextView and ImageView. The button's bottom is constrained to the barrier while the TextView's bottom is constrained to top of the button. If the barrier is positioned immediately below its referenced views then the wrap_content height of the TextView cannot be honored since the button will intrude into the TextView height (For the long text.)

What is pictured above could be a compromise. The barrier is still below its referenced views, although much farther below, and all constraints and layout sizes can be honored.

It's hard to tell if this is intentional or not or if it will continue to be the case going forward. I believe that the Space solution is the better solution.

Upvotes: 1

Related Questions