How to have CoordinatorLayout resize its dependent view instead of move it
P

1

4

I'm trying to restyle my app's AppBars to use the CollapsingToolbarLayout pattern. As the content is scrolled, the toolbar reduces in size and the visible content area increases.

My app uses Fragments extensively. The Activity layouts contain little more than the AppBar, and the Fragment views are inserted at runtime.

The standard sample code for CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout works for most of my UI, but some screens have items positioned at the bottom of the scrollable area. These are meant to be fixed and always visible, regardless of the scroll state.

The problem I have is that the CoordinatorLayout does its collapsing toolbar magic by offsetting the whole dependent view vertically downwards when the toolbar is fully expanded. This means the bottom elements are not visible. They become visible as the scroll bar collapses, but I need them to always be visible.

What I really need, I think, is a different behavior where the dependent view is not offset as the toolbar changes height, but is resized so it is always fully visible. What is the best way to achieve this?

Here is the current behavior (Android Studio Layout Inspector on the left so you can see the content frame being hidden by the system nav bar). I need that red text view to stay on screen as the toolbar expands:

Bottom of view hidden when scrolled

Here is demo layout code:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
    android:fitsSystemWindows="true"
    tools:context=".ScrollingActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/Theme.ProminentBarTest.AppBarOverlay">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:toolbarId="@+id/toolbar">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/Theme.ProminentBarTest.PopupOverlay" />

        </com.google.android.material.appbar.CollapsingToolbarLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <!-- Content of this FrameLayout is really a Fragment layout and inserted
        at runtime. Child Views can't be moved out into the activity layout -->

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_margin"
                android:text="@string/large_text" />

        </androidx.core.widget.NestedScrollView>

        <TextView
            android:id="@+id/problem_view"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:text="Bottom item"
            android:layout_gravity="bottom"
            android:gravity="center"
            android:background="@color/design_default_color_error"
            android:textColor="@color/white" />

        </LinearLayout>
    </FrameLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

I realise that a solution for this simple demo is to move @id/problem_view out of its parent FrameLayout to be a direct child of the CoordinatorLayout, but the real app uses a contained Fragment in the Activity and so the view needs to be in the same layout as the rest of the Fragment views.

Preform answered 10/3, 2021 at 12:22 Comment(2)
Did you try taking out the textview from the frame layout and have it as a child of the CoordinatorLayout and setting the anchors? See the FloatingActionButton in this example guides.codepath.com/android/…Theurer
Thanks, yes I know that works in this demo example. As I mentioned in the question, in my real app code the view is in part of a more complex fragment layout and breaking it out into the Activity layout is difficult.Preform
L
2

I needed something similar - I show a rounded edge background behind my content list and it looks awful if the background is moved down when the header expands. My solution is following:

I simply update the bottom padding of the view that should stay fixed, e.g. like following:

binding.appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
    // llBottomContent is my view that should be resized instead of moved
    binding.llBottomContent.updatePadding(bottom = appBarLayout.totalScrollRange+ verticalOffset)
})

Depending on the views container you may use margin instead of padding but the principle is the same. And off course you may need to add the initial padding as w

Lahey answered 23/9, 2021 at 12:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.