Android Layout Behavior is not called when child of CollapsingToolbarLayout

bolder5 picture bolder5 · Jun 11, 2015 · Viewed 12.9k times · Source

I am trying to set a layout_behavior on an element that is a child of the CollapsingToolbarLayout but the behavior is never called on the iv_header view. It works perfectly when anchored outside such as with the tv_follow view.

The documentation doesn't specifically say a layout_behavior cannot be applied within the AppBarLayout or CollapsingToolbarLayout so I'm at a loss for why it isn't working.

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

    <android.support.design.widget.AppBarLayout
        android:id="@+id/header"
        android:layout_width="match_parent"
        android:layout_height="@dimen/full_header_height"
        android:focusable="true"
        android:focusableInTouchMode="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/iv_header"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:contentDescription="@null"
                app:layout_behavior="com.package.view.HidingBehavior"
                app:layout_collapseMode="parallax"
                android:src="@drawable/profile_background"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/header_toolbar"
                android:layout_height="@dimen/action_bar_height"
                android:layout_width="match_parent"
                android:background="@drawable/toolbar_dark_gradient_half"
                android:gravity="top"
                app:layout_collapseMode="pin"/>

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

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

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    <TextView
        android:id="@+id/tv_follow"
        android:textSize="20sp"
        android:textColor="@android:color/white"
        android:text="@string/follow"
        android:drawableLeft="@drawable/comm_follow"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_anchor="@id/header"
        app:layout_anchorGravity="center"
        app:layout_behavior="com.package.view.HidingBehavior"
        android:drawablePadding="8dp"
        android:gravity="center"
        android:visibility="gone"
        android:fitsSystemWindows="true"/>

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

The Behavior was pulled out of the FloatingActionButton code in the design support library.

public class HidingBehavior extends CoordinatorLayout.Behavior<View>{
    private Rect tmpRect;
    private boolean isAnimatingOut;
    private FastOutSlowInInterpolator fastOutSlowInInterpolator = new FastOutSlowInInterpolator();

    public HidingBehavior() {
    }

    public HidingBehavior(Context context, AttributeSet attrs) {
        super();
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof AppBarLayout;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        if (dependency instanceof AppBarLayout) {
            AppBarLayout appBarLayout = (AppBarLayout) dependency;
            if (this.tmpRect == null) {
                this.tmpRect = new Rect();
            }

            Rect rect = this.tmpRect;
            ViewGroupUtils.getDescendantRect(parent, dependency, rect);
            if (rect.bottom <= getMinimumHeightForContext(appBarLayout)) {
                if(!this.isAnimatingOut && child.getVisibility() == View.VISIBLE) {
                    this.animateOut(child);
                }
            } else if(child.getVisibility() != View.VISIBLE) {
                this.animateIn(child);
            }
        }

        return false;
    }

    private int getMinimumHeightForContext(AppBarLayout appBarLayout) {
        int minHeight = ViewCompat.getMinimumHeight(appBarLayout);
        if(minHeight != 0) {
            return minHeight*2;
        } else {
            int childCount = appBarLayout.getChildCount();
            return childCount >= 1?ViewCompat.getMinimumHeight(appBarLayout.getChildAt(childCount - 1))*2:0;
        }
    }

    private void animateIn(View view) {
        view.setVisibility(View.VISIBLE);
        ViewCompat.animate(view)
                .scaleX(1.0F)
                .scaleY(1.0F)
                .alpha(1.0F)
                .setInterpolator(fastOutSlowInInterpolator)
                .withLayer()
                .setListener((ViewPropertyAnimatorListener)null).start();
    }

    private void animateOut(final View view) {
        ViewCompat.animate(view)
                .scaleX(0.0F)
                .scaleY(0.0F)
                .alpha(0.0F)
                .setInterpolator(fastOutSlowInInterpolator)
                .withLayer()
                .setListener(new ViewPropertyAnimatorListener() {
            public void onAnimationStart(View view) {
                HidingBehavior.this.isAnimatingOut = true;
            }

            public void onAnimationCancel(View view) {
                HidingBehavior.this.isAnimatingOut = false;
            }

            public void onAnimationEnd(View view) {
                HidingBehavior.this.isAnimatingOut = false;
                view.setVisibility(View.GONE);
            }
        }).start();
    }
}

Answer

rf43 picture rf43 · Jun 27, 2015

If I am not mistaken, this line is unnecessary where you currently have it...

app:layout_behavior="com.package.view.HidingBehavior"

The layout_behavior should be applied to the sibling of the of the AppBarLayout and not to a (nested) child. This is because it tells the siblings, inside the CoordinatorLayout, how they need to coordinate their behaviors in response to what they're doing.

In other words, where you have it, it is not coordinating any behavior with any other view.