CoordinatorLayout ignora márgenes para vistas con ancla

Dado que estoy usando un diseño como este:

<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> 

Que es más o menos la muestra estándar Cheesesquare – excepto el FloatingActionButton, que me gustaría subir por cerca de 20dp.

Sin embargo, esto no funcionará. No importa si utilizo margen, relleno, etc – el botón siempre se centrará en el borde del ancla, como este:

FAB ignorará el margen

¿Cómo puedo mover el FAB por 20dp según lo previsto?

Trate de ponerlo en un diseño lineal que tiene relleno:

 <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> 

Como puede haber bugs en el diseño-support-lib en relación con el CoordinatorLayout & FrameLayout , escribí un FrameLayout que implementa / copia el mismo "Comportamiento" como el FAB y permite establecer un padding para simular el efecto:

Asegúrese de incluirlo en el paquete android.support.design.widget , ya que necesita acceder a algunas clases de ámbito de paquete.

 /* * 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; } } } 

Para anclar el FloatingActionButton debajo de AppBar como esto: Fab como ancla a la AppBar

Extender el FloatingActionButton y reemplazar 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))); } } 

Solución fácil es anclar un diseño aleatorio a donde FAB se ancló, darle margen específico y, a continuación, anclar FAB a disposición aleatoria, como este

 <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" /> 

Puede intentar usar valor negativo para el margen superior:

 android:layout_marginTop="-20dp" 
  • No se puede usar srcCompat para ImageViews en android
  • SearchView es invisible con el parámetro collapseActionView desde la biblioteca de diseño 24.1
  • Establecimiento de la escucha de compensación en CollapsingToolbarLayout
  • Agregar el comportamiento de vista de desplazamiento de la barra de aplicaciones a varias vistas en CoordinatorLayout
  • Pase el evento de desplazamiento a otra vista
  • TabLayout pestaña de pestaña seleccionada
  • Tablayout de la biblioteca de soporte de diseño de Android mediante el diseño de pestañas personalizadas, pero el diseño que envuelve las pestañas
  • TabLayout establece el espaciado o el margen de cada ficha
  • CollapsingToolbarLayout no debe colapsar cuando RecyclerView está vacío
  • Establecer el ítem / id de elementos inicialmente seleccionado en BottomNavigationView
  • DrawableLeft se está superponiendo con una sugerencia con TextInputLayout android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.