Search code examples
androidandroid-coordinatorlayout

Can CoordinatorLayout Behavior be applied only on direct children of CoordinatorLayout?


Based on my reading so far, Behavior can only be applied to direct children of CoordinatorLayout. But the following code is confusing me:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.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">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="@android:color/holo_red_light"
            app:layout_scrollFlags="scroll"
            app:toolbarId="@+id/toolbar">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="parallax"
                app:popupTheme="@style/AppTheme.PopupOverlay" />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>


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

        <android.support.v4.widget.NestedScrollView
            android:id="@+id/nestedScrollView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            tools:context=".ScrollingActivity"
            tools:showIn="@layout/activity_scrolling">

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

        </android.support.v4.widget.NestedScrollView>
    </FrameLayout>
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Test Button"
        android:visibility="visible"
        android:layout_gravity="bottom"
        app:layout_anchor="@id/nestedScrollView"
        app:layout_behavior="am.i.coord.CustomBehavior"
        />

</android.support.design.widget.CoordinatorLayout>

The NestedScrollView is not a direct child of CoordinatorLayout (being wrapped in a FrameLayout), but still has a Behavior applied, which is properly triggering changes in AppBarLayout.

The Button I added at the same level with a custom behavior is working only if I make it a direct child of CoordinatorLayout. If I move it inside the FrameLayout as a sibling of NestedScrollView, then none of the callbacks are executed.

Why this difference? If Behavior is not meant to be used by CoordinatorLayout's non-direct children, is there another way for the non-direct children to be notified on scroll changes in NestedScrollView? I can only think of adding a dummy view to the CoordinatorLayout which will emit scroll events to indirect children, but it makes the code messy.


Solution

  • Yes, behaviors must be attached to the direct children of a CoordinatorLayout. Why it works for NestedScrollView is detailed in the Intercepting everything with CoordinatorLayout blog post, which states:

    Nested scrolling can originate not only on direct children of a CoordinatorLayout, but on any child View (a child of a child of a child of a CoordinatorLayout, for example)

    This is a separate property of nested scrolling itself, not something unique to CoordinatorLayout: nested scrolling events propagate up the view hierarchy through each parent ViewGroup. This is how CoordinatorLayout gains access to those events and Behaviors are what allows CoordinatorLayout to dispatch those events to other children not directly in the view hierarchy of the scrolling view.

    As you surmised, if you want an indirect child to be aware of scroll changes, you must have a direct child of CoordinatorLayout, even if it is a dummy view. You can, of course, create a subclass of CoordinatorLayout and hook into the same event stream if you want as all of the methods can be overridden.