Glitch with CollapsingToolbarLayout when popping up a fragment

253 Views Asked by At

I have a fragment A with a CollapsingToolbarLayout containing a view that collapses when scrolling the page.

At the end of the page, a button navigates to another fragment B.

When I press back on fragment B, fragment B is removed from the navigation stack and fragment A is shown again from the previous position (scroll at the bottom).

Right now everything is ok, but the toolbar of fragment A is initially visible and then collapses during the transition, even if it should not be visible.

Glitch

This is the layout of Fragment A:

<?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:id="@+id/coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            style="?attr/collapsingToolbarLayoutLargeStyle"
            android:layout_width="match_parent"
            android:layout_height="280dp"
            android:fitsSystemWindows="true"
            app:contentScrim="?android:colorBackground"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
            app:scrimVisibleHeightTrigger="70dp"
            app:statusBarScrim="?android:colorBackground">

            <View
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:background="#ff0000"
                android:fitsSystemWindows="true" />

            <com.google.android.material.appbar.MaterialToolbar
                android:id="@+id/topAppBar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:elevation="0dp"
                android:fitsSystemWindows="false"
                app:layout_collapseMode="pin"
                app:layout_scrollFlags="scroll|enterAlways"
                app:navigationIcon="@drawable/chevron_back"
                app:navigationIconTint="#00ff00" />
        </com.google.android.material.appbar.CollapsingToolbarLayout>

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

    <androidx.core.widget.NestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:clipToPadding="false"
        android:fillViewport="true"
        android:orientation="vertical"
        android:paddingHorizontal="@dimen/content_padding"
        android:paddingTop="0dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

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

            <TextView
                android:id="@+id/short_description"
                style="@style/body_gray"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginVertical="8dp"
                android:text="@string/lorem_ipsum" />
            <Button
                android:id="@+id/next"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginVertical="8dp"
                android:text="Next" />

        </LinearLayout>
    </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

And this is its code:

class FragmentA: Fragment() {
    private var _binding: FragmentABinding? = null

    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View {

        _binding = FragmentABinding.inflate(inflater, container, false)

        binding.topAppBar.setNavigationOnClickListener {
            findNavController().popBackStack()
        }

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.next.setOnClickListener {
            findNavController().navigate(R.id.action_A_to_B)
            )
        }
    }
}

This is the action used for the navigation:

<action
            android:id="@+id/action_A_to_B"
            app:destination="@id/fragmentb"
            app:enterAnim="@anim/slide_in"
            app:exitAnim="@anim/slide_out"
            app:popEnterAnim="@anim/pop_in"
            app:popExitAnim="@anim/pop_out"
            app:launchSingleTop="true"/>

This is the video of the glitch: https://youtube.com/shorts/0UKtAwOSZQI

This is a minimal project that replicates the problem: https://mega.nz/file/lq5AEY6T#A-PxuVcWGDLSSktGHLwmtfMBgfUYu659EF7LbP770S8

3

There are 3 best solutions below

3
Zain On BEST ANSWER

The problem causing that glitch is that the scrim animation of the CollapsingToolbarLayout is enabled by default, so it takes time to fade out the CollapsingToolbarLayout color.

This can be solved by disabling the scrim animation by setting its duration to 0 using app:scrimAnimationDuration attribute:

<androidx.coordinatorlayout.widget.CoordinatorLayout
    ....>

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

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            app:scrimAnimationDuration="0">
            
...

Edit

Well, although disabling the scrim animation will solve the issue, but it will raise another whenever the CollapsingToolbarLayout get collapsed or expanded by dragging it up or down; so we need to conditionally disable the scrim animation.

Now remove the app:scrimAnimationDuration="0" from the layout.

We need to keep the scrim animation ON by default, but only when we returned back from Fragment B to Fragment A we need to disable it and re-enable it again:

// In Fragment A
override fun onResume() {
    super.onResume()

    // Make sure to save that on a permanent storage like ViewModel to avoid losing it.
    val duration = binding.collapsingToolbar.scrimAnimationDuration 
   
    // Disable the animation
    binding.collapsingToolbar.scrimAnimationDuration = 0

    Handler(Looper.getMainLooper()).postDelayed({
        // Re-Enable the animation
        binding.collapsingToolbar.scrimAnimationDuration = duration
    }, duration)
}
4
Vinay On

Add below lines in your First Fragment-

binding.appbar.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
    val isToolbarVisible = abs(verticalOffset) != appBarLayout.totalScrollRange
    binding.topAppBar.isVisible = isToolbarVisible
}

In the addOnOffsetChangedListener, we check the verticalOffset to determine if the toolbar is fully collapsed or not. If the absolute value of verticalOffset is equal to the totalScrollRange, it means the toolbar is fully collapsed, and we hide it by setting its visibility to false. Otherwise, we show the toolbar by setting its visibility to true.

1
Sohaib Ahmed On

Try setting 'match_parent' to NestedScrollView

<androidx.core.widget.NestedScrollView
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"