chrisbanes/insetter

setOnApplyInsetsListener always returns insets with 0

MobilefactoryAT opened this issue · 4 comments

I had the issue that the listener received window insets always with 0px
My layout looks like this and I applied the doOnApplyWindowInsets to BottomNavigationView.
I fixed it for me by waiting until it is attached and then listen for insets on the top most parent view.

<androidx.coordinatorlayout.widget.CoordinatorLayout
            android:fitsSystemWindows="false"
            ...>
<com.google.android.material.bottomnavigation.BottomNavigationView
                ... />

Fixed code in kotlin...

/**
     * A wrapper around [ViewCompat.setOnApplyWindowInsetsListener] which stores the initial view state, and provides them whenever a
     * [android.view.WindowInsets] instance is dispatched to the listener provided.
     *
     *
     * This allows the listener to be able to append inset values to any existing view state
     * properties, rather than overwriting them.
     */
    fun setOnApplyInsetsListener(view: View, listener: OnApplyInsetsListener) {
        val tagState = view.getTag(R.id.insetter_initial_state) as ViewState?
        val initialState: ViewState

        if (tagState != null) {
            initialState = tagState
        } else {
            initialState = ViewState(view)
            view.setTag(R.id.insetter_initial_state, initialState)
        }

        if (ViewCompat.isAttachedToWindow(view)) {
            registerForWindowInsets(view, getMostParentView(view), initialState, listener)
        } else {
            view.addOnAttachStateChangeListener(
                object : View.OnAttachStateChangeListener {
                    override fun onViewAttachedToWindow(v: View) {
                        v.removeOnAttachStateChangeListener(this)
                        registerForWindowInsets(v, getMostParentView(v), initialState, listener)
                    }

                    override fun onViewDetachedFromWindow(v: View) {
                        // no-op
                    }
                })
        }
    }

    /**
     * Register for window insets.
     * Commits result through wrapper.
     */
    private fun registerForWindowInsets(
        view: View,
        mostParent: View,
        initialState: ViewState,
        listener: OnApplyInsetsListener
    ) {
        ViewCompat.setOnApplyWindowInsetsListener(
            mostParent
        ) { v, insets ->
            listener.onApplyInsets(view, insets, initialState)
            // Always return the initial insets instance
            insets
        }

        ViewCompat.requestApplyInsets(mostParent)
    }

    /**
     * Returns the most parent view in its hierarchy
     */
    private fun getMostParentView(view: View): View =
        view.parent.run {
            if (this != null && this is View && this.id != android.R.id.content) {
                getMostParentView(this)
            } else view
        }

Hello!
Could you share a complete example of the project?

Activity:
Insetter.setEdgeToEdgeSystemUiFlags(window.decorView, true)
Activity layout:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:id="@+id/navFragment"
          android:fitsSystemWindows="true"
          android:name="androidx.navigation.fragment.NavHostFragment"
          app:defaultNavHost="true"
          app:navGraph="@navigation/activity_home_navigation"
          android:layout_height="match_parent"
          android:layout_width="match_parent"/>

Fragment Layout

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
....
    </data>


    <androidx.coordinatorlayout.widget.CoordinatorLayout
            android:id="@+id/coordinator"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="false"
            android:keepScreenOn="true">

        <fragment
                android:id="@+id/homeNavFragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                app:defaultNavHost="false"
                app:layout_behavior="@string/appbar_scrolling_view_behavior"
                app:navGraph="@navigation/home_content" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
                android:id="@+id/mainBottomNavigation"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:layout_marginStart="0dp"
                android:layout_marginEnd="0dp"
                app:elevation="8dp"
                app:labelVisibilityMode="labeled"
                android:visibility="visible"
                app:hideOnScroll="true"
                paddingBottomSystemWindowInsets="@{true}"
                app:itemIconTint="@color/bottom_navigation_colors"
                app:itemTextColor="@color/bottom_navigation_colors"
                app:layout_behavior=".ui.fragments.BottomBehaviour"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:menu="@menu/navigation" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="@{(view) -> viewModel.onFabClicked(view)}"
                android:transitionName="upload"
                android:tint="?attr/colorOnSecondary"
                app:backgroundTint="?attr/colorSecondary"
                android:fitsSystemWindows="true"
                app:elevation="12dp"
                android:visibility="visible"
                app:layout_anchor="@id/mainBottomAppbarHidden"
                app:layout_anchorGravity="center"
                app:rippleColor="?attr/colorError"
                app:srcCompat="@drawable/ic_fab_upload" />

        <androidx.coordinatorlayout.widget.CoordinatorLayout
                android:id="@+id/snackbar_coordinator"
                android:layout_gravity="top"
                paddingTopSystemWindowInsets="@{true}"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

It makes no sense to use the attribute fitsSystemWindows everywhere
Here is a list of layouts that support it:
DrawerLayout, CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout, NavigationView

You have a NavHostFragment (R.id.homeNavFragment). Maybe some of the nested fragments have already consumed the insets?

I don't think this is really an issue in Insetter, so marking as closed.