CoordinatorLayout ignores margins for views with anchor

coordinatorlayout anchor
android-coordinatorlayout layout_below
coordinatorlayout behavior
android bottom sheet anchor
the view is not a child of coordinatorlayout
coordinatorlayout appbarlayout
mastering coordinator layout
coordinatorlayout fitssystemwindows

Given I'm using a layout like this:

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/main_content"
    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:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/flexible_space_image_height"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:statusBarScrim="@android:color/transparent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                />

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

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

    <android.support.v7.widget.RecyclerView
        android:id="@+id/mainView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        />

    <android.support.design.widget.FloatingActionButton
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_marginBottom="20dp"
        app:fabSize="normal"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|center_horizontal"
        />

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

Which is pretty much the standard Cheesesquare sample - except the FloatingActionButton, which I would like to move up by about 20dp.

However, this will not work. No matter if I use margin, padding etc - the button will always be centered at the edge of the anchor, like this:

How can I move the FAB up by 20dp as intended?

Try putting it in a linear layout that have padding:

<LinearLayout
  width=".."
  height=".."
  paddingBottom="20dp"
  app:layout_anchor="@id/appbar"
  app:layout_anchorGravity="bottom|center_horizontal">

  <android.support.design.widget.FloatingActionButton
        android:layout_width="70dp"
        android:layout_height="70dp"
        app:fabSize="normal" />

</LinearLayout>

CoordinatorLayout Basic, By specifying Behaviors for child views of a CoordinatorLayout you can If you specify an Anchor using app:layout_anchor, then this attribute would be ignored. Questions: Given I'm using a layout like this: &lt;android.support.design

As there might be bugs in the design-support-lib concerning the CoordinatorLayout & margins, I wrote a FrameLayout that implements/copies the same "Behavior" like the FAB and allows to set a padding to simulate the effect:

Be sure to put it in the android.support.design.widget package as it needs to access some package-scoped classes.

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.widget.FrameLayout;

import com.company.android.R;

import java.util.List;

@CoordinatorLayout.DefaultBehavior(FrameLayoutWithBehavior.Behavior.class)
public class FrameLayoutWithBehavior extends FrameLayout {
    public FrameLayoutWithBehavior(final Context context) {
        super(context);
    }

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

    public FrameLayoutWithBehavior(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public FrameLayoutWithBehavior(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public static class Behavior extends android.support.design.widget.CoordinatorLayout.Behavior<FrameLayoutWithBehavior> {
        private static final boolean SNACKBAR_BEHAVIOR_ENABLED;
        private Rect mTmpRect;
        private boolean mIsAnimatingOut;
        private float mTranslationY;

        public Behavior() {
        }

        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, FrameLayoutWithBehavior child, View dependency) {
            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
        }

        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, FrameLayoutWithBehavior child, View dependency) {
            if (dependency instanceof Snackbar.SnackbarLayout) {
                this.updateFabTranslationForSnackbar(parent, child, dependency);
            } else if (dependency instanceof AppBarLayout) {
                AppBarLayout appBarLayout = (AppBarLayout) dependency;
                if (this.mTmpRect == null) {
                    this.mTmpRect = new Rect();
                }

                Rect rect = this.mTmpRect;
                ViewGroupUtils.getDescendantRect(parent, dependency, rect);
                if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
                    if (!this.mIsAnimatingOut && child.getVisibility() == VISIBLE) {
                        this.animateOut(child);
                    }
                } else if (child.getVisibility() != VISIBLE) {
                    this.animateIn(child);
                }
            }

            return false;
        }

        private void updateFabTranslationForSnackbar(CoordinatorLayout parent, FrameLayoutWithBehavior fab, View snackbar) {
            float translationY = this.getFabTranslationYForSnackbar(parent, fab);
            if (translationY != this.mTranslationY) {
                ViewCompat.animate(fab)
                          .cancel();
                if (Math.abs(translationY - this.mTranslationY) == (float) snackbar.getHeight()) {
                    ViewCompat.animate(fab)
                              .translationY(translationY)
                              .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
                              .setListener((ViewPropertyAnimatorListener) null);
                } else {
                    ViewCompat.setTranslationY(fab, translationY);
                }

                this.mTranslationY = translationY;
            }

        }

        private float getFabTranslationYForSnackbar(CoordinatorLayout parent, FrameLayoutWithBehavior fab) {
            float minOffset = 0.0F;
            List dependencies = parent.getDependencies(fab);
            int i = 0;

            for (int z = dependencies.size(); i < z; ++i) {
                View view = (View) dependencies.get(i);
                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
                    minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - (float) view.getHeight());
                }
            }

            return minOffset;
        }

        private void animateIn(FrameLayoutWithBehavior button) {
            button.setVisibility(View.VISIBLE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                ViewCompat.animate(button)
                          .scaleX(1.0F)
                          .scaleY(1.0F)
                          .alpha(1.0F)
                          .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
                          .withLayer()
                          .setListener((ViewPropertyAnimatorListener) null)
                          .start();
            } else {
                Animation anim = android.view.animation.AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in);
                anim.setDuration(200L);
                anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
                button.startAnimation(anim);
            }

        }

        private void animateOut(final FrameLayoutWithBehavior button) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                ViewCompat.animate(button)
                          .scaleX(0.0F)
                          .scaleY(0.0F)
                          .alpha(0.0F)
                          .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
                          .withLayer()
                          .setListener(new ViewPropertyAnimatorListener() {
                              public void onAnimationStart(View view) {
                                  Behavior.this.mIsAnimatingOut = true;
                              }

                              public void onAnimationCancel(View view) {
                                  Behavior.this.mIsAnimatingOut = false;
                              }

                              public void onAnimationEnd(View view) {
                                  Behavior.this.mIsAnimatingOut = false;
                                  view.setVisibility(View.GONE);
                              }
                          })
                          .start();
            } else {
                Animation anim = android.view.animation.AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out);
                anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
                anim.setDuration(200L);
                anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() {
                    public void onAnimationStart(Animation animation) {
                        Behavior.this.mIsAnimatingOut = true;
                    }

                    public void onAnimationEnd(Animation animation) {
                        Behavior.this.mIsAnimatingOut = false;
                        button.setVisibility(View.GONE);
                    }
                });
                button.startAnimation(anim);
            }

        }

        static {
            SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
        }
    }
}

CoordinatorLayout.LayoutParams, The new view must be direct child of CoordinatorLayout like the bottom sheet view. To anchor the new view to the bottom sheet, add app:layout_anchor with id of  One other thing to be aware of: That layout behavior will cause the FrameLayout height to be sized as if the Toolbar is already scrolled, and with the Toolbar fully displayed the entire view is simply pushed down so that the bottom of the view is below the bottom of the CoordinatorLayout.

Easy workaround is to anchor a random layout to where FAB was anchored, give it specific margin, and then anchor FAB to random layout, like this

<LinearLayout
 android:orientation="horizontal"
 android:id="@+id/fab_layout"
 android:layout_width="5dp"
 android:layout_height="5dp"
 android:layout_marginRight="80dp"
 android:layout_marginEnd="80dp"
 app:layout_anchor="@id/collapsing_toolbar"
 app:layout_anchorGravity="bottom|end"/>

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="@dimen/fab_margin"
    android:src="@android:drawable/ic_dialog_map"
    app:layout_anchor="@id/fab_layout"
    app:elevation="6dp"
    app:pressedTranslationZ="12dp"
 />

Android: Anchoring Views to Bottom Sheet - Android Bits, with a little margin to ensure we abide by the material design guidelines. We set the root View for the Snackbar to the CoordinatorLayout. However, rather than position the FAB with layout_gravity, we anchor Showing this Snackbar animates the FAB out of the way, but ignores the CircleImageView. 23 CoordinatorLayout ignores margins for views with anchor Jun 7 '15 23 Using “android:textAppearance” on TextView/EditText fails, but “style” works Mar 18 '11 22 How to get the phone number of the phone in android code?

To anchor the FloatingActionButton below the AppBar like this:

Extend the FloatingActionButton and override offsetTopAndBottom:

public class OffsetFloatingActionButton extends FloatingActionButton
{
    public OffsetFloatingActionButton(Context context)
    {
        this(context, null);
    }

    public OffsetFloatingActionButton(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public OffsetFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom)
    {
        super.onLayout(changed, left, top, right, bottom);
        ViewCompat.offsetTopAndBottom(this, 0);
    }

    @Override
    public void offsetTopAndBottom(int offset)
    {
        super.offsetTopAndBottom((int) (offset + (getHeight() * 0.5f)));
    }
}

Using CoordinatorLayout in Android apps, In this example, views are coordinated with each other, in a glance, we can see how Let's see one of the simplest structures using the CoordinatorLayout : related to morphing, launching, and the transferring anchor point. layout_anchor spécifie View de votre choix View devrait être positionné (i.e., ceci View doit être placé par rapport à la View spécifié par layout_anchor l'attribut). Puis, layout_anchorGravity spécifie quel côté du relatif View View sera positionné, en utilisant le gravité valeurs ( top , bottom , center_horizontal , etc.).

I was able to get around this issue by using both layout_anchor layout_anchorGravity along with some padding.. The anchor attribute allows you to have a View position itself relative to another view. However, it doesn't exactly work in the same way that RelativeLayout does. More on that to follow.

layout_anchor specifies which View your desired View should be positioned (i.e., this View should be placed relative to the View specified by the layout_anchor attribute). Then, layout_anchorGravity specifies which side of the relative View the current View will be positioned, using the typical Gravity values (top, bottom, center_horizontal, etc.).

The issue with using just these two attributes alone is that the center of the View with the anchors will be placed relative to the other View. For example, if you specify a FloatingActionButton to be anchored to the bottom of a TextView, what really ends up happening is that the the center of the FAB is placed along the bottom edge of the TextView.

To get around this issue, I applied some padding to the FAB, enough such that the top edge of the FAB was touching the bottom edge of the TextView:

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_anchor="@id/your_buttons_id_here"
    android:layout_anchorGravity="bottom"
    android:paddingTop=16dp" />

You might have to increase the padding to get the desired effect. Hope that helps!

Mastering the Coordinator Layout · Saúl Molinero, Children of a CoordinatorLayout may have an anchor . This view id must correspond to an arbitrary descendant of the CoordinatorLayout, but it may not be the  Using CoordinatorLayout in Android apps. 199. Also note that the FAB is anchored to a View that is not a direct child of the CoordinatorLayout. You can anchor a view (let’s call it ChildView

CoordinatorLayout - Android SDK, Hi I am trying to anchor Fab button to BottomSheetDialog but it is not working. tools:ignore="MissingPrefix"> <FrameLayout You should use CoordinatorLayout as the direct parent of FAB and the View which is used as anchor for the FAB. You can use a timer and gradually shrink the height/margin of the topbar's  Button has some margins which decrease when scrolling close to the bottom margin of the screen, being zero when the button is sticked at the bottom.

BotomSheetDialog FabButton Anchor View not working as expected , Duc Thang. Android Developer at Hamado. View all tags → 1 CoordinatorLayout ignores margins for views with anchor Apr 29.

app:layout_anchor: This attribute can be set on children of the CoordinatorLayout to attach them to another view. The value would be the id of an anchor view that this view should position relative to. Note that, the anchor view can be any child View (a child of a child of a child of a CoordinatorLayout, for example).

Comments
  • Reported at code.google.com/p/android/issues/detail?id=176096
  • In what API version does the bug happen?
  • Thanks. Nice try, BUT: LinearLayout does not declare an @DefaultBehavior which in will then disable the interaction with the parent CoordinatorLayout. For the FAB, I'm sure you have seen the cheese square demo: It will animateOut, once the scrolling threshold is reached. I still want that feature, just with a bit extra bottom margin.
  • what about padding instead of margin for FloatingActionButton.
  • Tried that without success. It is almost as if margins and paddings are completely ignored, once the behavior is set.
  • This works in the sense that it offsets the fab with respect to the seam, but it breaks the hide/show animation, which isn't aware of the offset.