Search code examples
androidxmlandroid-constraintlayoutandroid-scrollview

Android ScrollView: How to create a scrolling view with static header and footer using constraint layout


I am attempting to create a scroll view with a static header and footer like this. Here is the source code for this layout

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout 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">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:text="Title"
            android:textColor="#000"
            android:textSize="64sp"
            android:textStyle="bold"/>

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_above="@+id/divider"
            android:layout_below="@+id/title">

            <TextView
                android:id="@+id/description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/description"
                android:textColor="#000"
                android:textSize="50.7sp" />

        </ScrollView>

        <ImageView
            android:id="@+id/divider"
            android:layout_width="match_parent"
            android:layout_height="4dp"
            android:background="#000"
            android:layout_above="@+id/buttons"/>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/buttons"
            android:layout_alignParentBottom="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true">

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/btnPos"
                android:text="Ok"
                android:textAllCaps="false"
                android:textColor="#000"
                android:textSize="45.7sp"
                android:textStyle="bold"
                android:onClick="home"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="@+id/buttons"/>

            <Button
                android:id="@+id/btnNeg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Cancel"
                android:textAllCaps="false"
                android:textColor="#000"
                android:textSize="45.7sp"
                android:textStyle="bold"
                android:onClick="home"
                app:layout_constraintStart_toEndOf="@id/btnPos"
                app:layout_constraintTop_toTopOf="@+id/buttons"/>

        </androidx.constraintlayout.widget.ConstraintLayout>

    </RelativeLayout>

</RelativeLayout>

This is a solved issue using Relative Layout but I have not been able to make it work using Constraint Layout. You can see here that the scroll view is not constrained above the footer. Here is the source for this layout

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

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

        <TextView
            android:id="@+id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Title"
            android:textColor="#000"
            android:textSize="64sp"
            android:textStyle="bold"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ScrollView
            android:id="@+id/scroll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toTopOf="@id/divider"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/title"
            app:layout_constraintVertical_bias="0.0">

            <TextView
                android:id="@+id/description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/description"
                android:textColor="#000"
                android:textSize="50sp" />
        </ScrollView>

        <ImageView
            android:id="@+id/divider"
            android:layout_width="match_parent"
            android:layout_height="4dp"
            android:background="#000"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/buttons"/>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/buttons"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingTop="10dp"
            android:paddingBottom="10dp"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent">

            <Button
                android:id="@+id/btnPos"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Ok"
                android:textSize="45.7sp"
                android:textAllCaps="false"
                android:onClick="home"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent" />

            <Button
                android:id="@+id/btnNeg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Cancel"
                android:textAllCaps="false"
                android:textSize="45.7sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@+id/btnPos"/>

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

According to this post the conversion of properties from Relative to Constraint layout is the following

android:layout_above="" -> app:layout_constraintBottom_toTopOf=""

and android:layout_below="" -> app:layout_constraintTop_toBottomOf=""

The repo for a demo application is here.

The relative layout works but I want to use the latest android layouts for best performance and this should be possible. Thanks in advance!


Solution

  • There a couple of issues with the layout. One of the purposes of ConstraintLayout is that is provides for a flat layout structure. It is not wrong to nest a ConstraintLayout inside another ConstraintLayout, but it defeats one of the desirable attributes of the layout.

    match_parent for a view's width or height is discouraged in a ContraintLayout. Instead of match_parent, use match_constraints (0dp) with the appropriate constraints set. You have some match_parent in your layout. (match_parent is OK on the ConstraintLayout itself since it will be placed inside of another type of layout.

    I went ahead and made some adjustments to the layout below.

    <androidx.constraintlayout.widget.ConstraintLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:id="@+id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Title"
            android:textColor="#000"
            android:textSize="64sp"
            android:textStyle="bold"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <ScrollView
            android:id="@+id/scroll"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toTopOf="@+id/btnNeg"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/title">
    
            <TextView
                android:id="@+id/description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/description"
                android:textColor="#000"
                android:textSize="50sp" />
        </ScrollView>
    
        <ImageView
            android:id="@+id/divider"
            android:layout_width="match_parent"
            android:layout_height="4dp"
            android:background="#000"
            app:layout_constraintBottom_toTopOf="@+id/buttons"
            app:layout_constraintStart_toStartOf="parent" />
    
        <Button
            android:id="@+id/btnPos"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="home"
            android:text="Ok"
            android:textAllCaps="false"
            app:layout_constraintHorizontal_chainStyle="packed"
            android:textSize="45.7sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/btnNeg"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent" />
    
        <Button
            android:id="@+id/btnNeg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Cancel"
            android:textAllCaps="false"
            android:textSize="45.7sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/btnPos" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>