CoordinatorLayout ignores margins for views with 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: <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 parentCoordinatorLayout
. 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.