Search code examples
androidxmlandroid-viewpagerandroid-constraintlayout

Make ViewPager Fill The Remaining Space In a ConstraintLayout


I have the following XML code , I couldn't make 'summaryViewPager' fill the remaining space vertically, until the bottom of the screen :

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

        <com.google.android.material.card.MaterialCardView
            android:id="@+id/mainCardView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:layout_marginRight="10dp"
            android:background="#666266"
            android:padding="10dp"
            app:cardCornerRadius="20dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

                <TextView
                    android:id="@+id/textView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="3dp"
                    android:layout_marginLeft="3dp"
                    android:layout_marginTop="2dp"
                    android:text="@string/category"
                    android:textColor="#FFBA93"
                    android:textSize="20sp"
                    android:textStyle="bold"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />
        </com.google.android.material.card.MaterialCardView>

        <com.google.android.material.card.MaterialCardView
            android:id="@+id/cardView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:layout_marginRight="10dp"
            android:background="#666266"
            android:padding="10dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/mainCardView">


                <Button
                    android:id="@+id/shareButton"
                    android:layout_width="30dp"
                    android:layout_height="30dp"
                    android:layout_marginStart="5dp"
                    android:layout_marginLeft="5dp"
                    android:layout_marginTop="8dp"
                    android:layout_marginEnd="5dp"
                    android:layout_marginRight="5dp"
                    android:background="@drawable/ic_share"
                    app:layout_constraintEnd_toStartOf="@+id/copyButton"
                    app:layout_constraintHorizontal_bias="0.5"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />


                <ImageView
                    android:id="@+id/seenImageView"
                    android:layout_width="20dp"
                    android:layout_height="20dp"
                    android:layout_marginStart="3dp"
                    android:layout_marginLeft="3dp"
                    android:layout_marginTop="15dp"
                    android:layout_marginEnd="5dp"
                    android:layout_marginRight="5dp"
                    android:contentDescription="@string/todo"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintHorizontal_bias="0.5"
                    app:layout_constraintStart_toEndOf="@+id/favoriteButton"
                    app:layout_constraintTop_toTopOf="parent"
                    app:srcCompat="@drawable/unseen" />
        </com.google.android.material.card.MaterialCardView>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/summaryViewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="2dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/cardView2" />

    </androidx.constraintlayout.widget.ConstraintLayout>

The above code doesn't show the PageViewer at all below cardView2 , however when I set android:layout_height="500dp it does show PageViewer, but it doesn't fill the whole screen.


Solution

  • Initial Thoughts

    1. I copy pasted your Layout into Android Studio 4.x, replaced all @string references with "something", and all @drawables with an existing one (from @android:drawable/) so my layout would render.

    From what I see, it looks correct:

    android-studio-preview

    1. I noticed some inconsistencies in your Layout.

    CardView Number 1 (top)

    • Careful with Padding on a CardView (don't know your min/max API so, not sure if this applies to you).
    • Your CardView has no constrain for its BOTTOM, it's left to "whatever size you compute after wrapping" (Height = wrap, Bottom_To = null).
    • This ^ means that for the CardView to have a height, all its innerwidgets must compute their sizes, so the top card view knows how much size + margin + padding it needs. It doesn't depend on OTHER widgets (that are not children) because its constrains are all to the parent. (rather it only needs its parent).

    CardView Number 2 (bottom)

    • This cardView wraps all its dimensions but has the same issue as the one above, it doesn't constraint its height (except to be pinned at the bottom of the previous one, CardView 1). Again, this is fine, so as long as nobody depends on this (which is not true). This one has to wait on more widgets to know its size, because it has to wait on the CardView1 to know how much space is left, and also needs to know how much its children need. It's not too hard because both (the ImageView and the Button) have fixed sizes (20 and 30 dp respectively) + margins/padding.
    • The children of a MaterialCardView cannot/should not/must not use constraints because a CardView is not a ConstraintLayout, rather a glorified FrameLayout, which can only hold ONE child (or if it has more, it will put them on on top of the other). So all those constraints (for the shareButton and seenImageView) are ignored.
    • The solution to the above, is to have an inner ConstraintLayout as the sole child of a CardView, and inside this inner CL, put all your children and their constraints. The inner ConstraintLayout should have its width/height as either wrap_content or match_parent so they use the constraints of the parent. Since htey have fixed sizes, this is not an issue. (and if they didn't it wouldn't be an issue, but would need another measure pass).
    • Regardless of this, this second CardView is able to calculate its height, because its children are reporting a size of 20+30 (overlapped) + 15 margin top (image) so.. all this combined is likely 45dp~ of height (since they overlap only the biggest numbers apply here).
    • All the constraints are ignored for these two children.

    ViewPager (bottom)

    Finally we reach the VPager. This viewPager is match_parent for its width (since you have constraints to the start/end, you should just use 0dp). And it has wrap_content for its height.

    • layout_height=wrap_content -> this is a problem here. Because the viewPager doesn't (at the time of layout pass/measure) yet know what its contents are going to be. So you probably want this to be 0dp and let the ViewPager use all available space after the above has been calculated.

    • The marginTop you have here, will not work as it is, because the CardView 1 and 2 have no bottom constrain, so this one would have to create yet another layout/measure pass after it's all said and done to be able to apply a margin (that's how it works).

    Ok Enough Rant - what can you do?

    1. I'd "fix" the layout by adding the correct constraints and -if needed- use a VerticalChain and biasing for all widgets.

    2. I'd fix the middle (Cardview 2) contents by wrapping the textview and imageView in a ConstraintLayout.

    3. I'd remove left/right and replace with start/end (unless you target API 16 or below).

    4. I'd set the ViewPager to 0dp on both dimensions.

    5. The children of your CardView2 refer to copyButton and favoriteButton but these don't exist in the Layout you pasted, so I assume you have more buttons there).

    Full Version (modified)

    And in case you wonder, here's what I did ( I added a color to the VP's background so it was easier to "see").

    <?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"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.google.android.material.card.MaterialCardView
            android:id="@+id/mainCardView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            app:layout_constraintVertical_chainStyle="packed"
            android:background="#666266"
            android:padding="10dp"
            app:cardCornerRadius="20dp"
            app:layout_constraintBottom_toTopOf="@id/cardView2"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">
    
            <TextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="3dp"
                android:layout_marginTop="2dp"
                android:text="Category"
                android:textColor="#FFBA93"
                android:textSize="20sp"
                android:textStyle="bold" />
        </com.google.android.material.card.MaterialCardView>
    
        <com.google.android.material.card.MaterialCardView
            android:id="@+id/cardView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:layout_marginRight="10dp"
            android:background="#666266"
            android:padding="10dp"
            app:layout_constraintBottom_toTopOf="@id/summaryViewPager"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/mainCardView">
    
            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/innerCardView2Root"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <Button
                    android:id="@+id/shareButton"
                    android:layout_width="30dp"
                    android:layout_height="30dp"
                    android:layout_marginStart="5dp"
                    android:layout_marginTop="8dp"
                    android:layout_marginEnd="5dp"
                    android:background="@android:drawable/ic_menu_share"
                    app:layout_constraintEnd_toStartOf="@+id/seenImageView"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />
    
    
                <ImageView
                    android:id="@+id/seenImageView"
                    android:layout_width="20dp"
                    android:layout_height="20dp"
                    android:layout_marginStart="3dp"
                    android:layout_marginTop="15dp"
                    android:layout_marginEnd="5dp"
                    android:contentDescription="@null"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toEndOf="@+id/shareButton"
                    app:layout_constraintTop_toTopOf="parent"
                    app:srcCompat="@android:drawable/ic_menu_search" />
            </androidx.constraintlayout.widget.ConstraintLayout>
    
        </com.google.android.material.card.MaterialCardView>
    
        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/summaryViewPager"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginTop="2dp"
            android:background="@color/colorSecondary"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/cardView2" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>