How can I make a ScrollView fill the rest of its parent's remaining space?

Viewed 365

I have two TextViews and a ScrollView nested in a ConstraintLayout. Inside the ScrollView there is a TableLayout. The ScrollView's left, right and bottom constraints are set to "parent". The top constraint is set to the TextView just before it. Here is the XML:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom">

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:textSize="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView6">

        <TableLayout
            android:layout_width="fill_parent"
            android:layout_height="match_parent"
            android:layout_marginStart="32dp"
            android:layout_marginTop="32dp"
            android:layout_marginEnd="32dp"
            android:layout_marginBottom="32dp"
            android:gravity="center"
            android:stretchColumns="0,1,2,3,4">

            <TableRow
                android:layout_width="fill_parent"
                android:layout_weight="1">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:text="1" />
                    ... 4 more TextViews
            </TableRow>
            ... many more TableRows
        </TableLayout>
    </ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

The problem is that the ScrollView's width does not match its constraints. Instead, when more rows are added such that the ScrollView is taller than the its parent's remaining height, it overlaps the TextView just before it. The two screenshots below illustrate the problem:

No overlap:

enter image description here

Overlap:

enter image description here

When I set the ScrollView height to "0dp" (match constraints) it renders literally as 0dp (The ScrollView shrinks and none of its contents are shown).

What should I change to make the ScrollView expand and fill the remaining height of its parent container?

2 Answers

You need to correctly constrain your widgets for ConstraintLayout to be able to correctly measure and laid out your views.

In short:

  1. The Top TextView needs to be the head of the vertical chain (app:layout_constraintVertical_chainStyle="packed"), and must have a bottom constraint (bottom_to_Topof...):
    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="TextView"
        app:layout_constrainedHeight="true"
        app:layout_constraintVertical_chainStyle="packed"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/textView6" />

The textView is constrainedHeight = true because you want it to wrap_content, and widgets that have text, can have some problems when wrapped in constraint layouts (see this other answer for more info about why that may be needed).

The Second TextView, needs to be pinned as well to some bottom, in this case the scrollView...

    <TextView
        android:id="@+id/textView6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constrainedHeight="true"
        android:text="TextView"
        android:textSize="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2"
        app:layout_constraintBottom_toTopOf="@id/scrollView" />

Now they are chained together, but the ScrollView needs some action...

Simply pin the scrollview to the bottom, so it can pull from somewhere, and give it the 0dp you need, so it can calculate its size/position based on the constrains.

<ScrollView
        android:id="@+id/scrollView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView6">

(also give it a name/id...)

This causes CL to calculate space/sizing correctly and to avoid the overlaps you complain about, since it now has clear rules about what can go where.

When you omit a constrain (like bottom), especially when there are stupid widgets around like ScrollViews, you can have unpredictable behavior based on the contents, something that is hard to anticipate and fix. Always, when in doubt, try to give all widgets all the constrains you can.

I would suggest several points (I couldn't figure out which one is critical):

1. You shouldn't use match_parent for child views of a ConstraintLayout. Instead, use '0dp'.

Official training:

Note: You cannot use match_parent for any view in a ConstraintLayout. Instead use "match constraints" (0dp).

2. You should not use @+id/xxx but use @id/xxx to specify other views. The former is for creating a new id.

  1. Change ConstraintLayout's layout_height to match_parent.

  2. Change TableLayout's layout_height to wrap_content.

My result:

screenshot

layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="bottom">

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:text="TextView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/textView6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="TextView"
            android:textSize="16dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/textView2" />

        <ScrollView
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/textView6">

            <TableLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="32dp"
                android:layout_marginTop="32dp"
                android:layout_marginEnd="32dp"
                android:layout_marginBottom="32dp"
                android:gravity="center"
                android:stretchColumns="0,1,2,3,4">

                <TableRow
                    android:layout_width="fill_parent"
                    android:layout_weight="1">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:text="1" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:text="2" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:text="3" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:text="4" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:text="5" />
                </TableRow>

               <!-- plenty of TableRows... -->

            </TableLayout>
        </ScrollView>
    </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
Related