Search code examples
androidxmlandroid-linearlayoutandroid-constraintlayout

ConstraintLayout content stuck in middle of screen


After adding a ConstraintLayout inside a ScrollView, I noticed that the content remains stuck in the middle of the screen (both horizontally and vertically). I already tried add android:layout_gravity="top" to the main ConstraintLayout but that didn't work. Is there something else I can use to position the content at the top?

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fillViewport="true">
    <androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/cl_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingStart="@dimen/activity_horizontal_margin"
        android:paddingEnd="@dimen/activity_horizontal_margin">

        <androidx.cardview.widget.CardView
            android:id="@+id/cv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="30dp"
            app:cardUseCompatPadding="true"
            app:contentPadding="10dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/tv_disclaimer">

            <LinearLayout
                android:id="@+id/ll_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <ImageView
                    android:id="@+id/iv_title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="top"
                    android:importantForAccessibility="no"
                    android:paddingEnd="10dp" />

                <TextView
                    android:id="@+id/tv_title"
                    style="@android:style/TextAppearance.Medium"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="?android:attr/textColorPrimary" />
            </LinearLayout>
        </androidx.cardview.widget.CardView>

        <TextView
            android:id="@+id/tv_disclaimer"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:gravity="center"
            android:textColor="?android:attr/textColorPrimary"
            app:layout_constraintTop_toBottomOf="@+id/cv_title"
            app:layout_constraintBottom_toTopOf="@+id/cv_description"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            style="@android:style/TextAppearance.Medium" />

        <androidx.cardview.widget.CardView
            android:id="@+id/cv_description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:cardUseCompatPadding="true"
            app:contentPadding="10dp"
            app:layout_constraintTop_toBottomOf="@+id/tv_disclaimer"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent">
            <androidx.constraintlayout.widget.ConstraintLayout
                xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:id="@+id/cl_description"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <LinearLayout
                    android:id="@+id/ll_allergies"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintBottom_toTopOf="@+id/ll_ingredients">

                    <ImageView
                        android:id="@+id/iv_allergies"
                        android:importantForAccessibility="no"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:paddingEnd="5dp"/>

                    <TextView
                        android:id="@+id/tv_allergies"
                        style="@android:style/TextAppearance.Large"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center_vertical"
                        android:textColor="?android:attr/textColorPrimary" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_allergyinfo"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    app:layout_constraintTop_toBottomOf="@+id/ll_allergies"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintBottom_toTopOf="@+id/ll_ingredients">
                    <TextView
                        android:id="@+id/tv_allergyinfo"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textColor="?android:attr/textColorPrimary"
                        android:layout_marginBottom="2dp"
                        android:gravity="start"
                        style="@android:style/TextAppearance.Medium" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_ingredients"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    app:layout_constraintTop_toBottomOf="@+id/ll_allergyinfo"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent">
                    <ImageView
                        android:id="@+id/iv_ingredients"
                        android:importantForAccessibility="no"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:paddingEnd="5dp" />

                    <TextView
                        android:id="@+id/tv_ingredients"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center_vertical"
                        android:textColor="?android:attr/textColorPrimary"
                        style="@android:style/TextAppearance.Large" />
                </LinearLayout>
            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>
    </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

Current result

enter image description here


Solution

  • Two important things are going on here.

    The first is that your ScrollView specifies android:fillViewport="true". This means that whenever the contents of the ScrollView are "smaller" than the screen size, they will be stretched to fill the screen. In other words, your wrap_content ConstraintLayout will only actually wrap its content when you have to scroll. In all other cases, your ConstraintLayout will functionally be match_parent in height.

    The second is that the constraints on your CardView, TextView, and second CardView form a vertical chain. Chains have special behavior regarding spacing when the parent is larger than the sum of the views in the chain (which it is due to the fillViewport behavior discussed above); the default is "spread", which will position the three views such that all the remaining space is evenly divided into four chunks (one at the start of the chain, one between the first two views, one between the second two views, and the last at the end of the chain).

    How to fix it, though?

    The best thing to do is to break the chain. Assuming you just want the views all stacked one after the other, starting from the top of the screen, there's no need to use a chain. To break the chain, remove the bottom constraint from each view:

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView
        ...>
    
        <androidx.constraintlayout.widget.ConstraintLayout
            ...>
    
            <androidx.cardview.widget.CardView
                android:id="@+id/cv_title"
                ...
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent">
    
                ...
    
            </androidx.cardview.widget.CardView>
    
            <TextView
                android:id="@+id/tv_disclaimer"
                ...
                app:layout_constraintTop_toBottomOf="@+id/cv_title"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"/>
    
            <androidx.cardview.widget.CardView
                android:id="@+id/cv_description"
                ...
                app:layout_constraintTop_toBottomOf="@+id/tv_disclaimer"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent">
    
                ...
    
            </androidx.cardview.widget.CardView>
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    
    </ScrollView>
    

    Alternatively, you could delete the fillViewport attribute from your ScrollView. Without this, the child ConstraintLayout won't ever be stretched to fill the screen, and wrap_content will mean that there's never any extra space to spread out between your chain views.