Étant donné que j'utilise une mise en page comme ceci:
<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>
Ce qui est à peu près le standard Cheesesquare sample - à l’exception du FloatingActionButton, que je souhaiterais augmenter de 20 dp environ.
Cependant, cela ne fonctionnera pas. Peu importe si j'utilise margin, padding, etc. - le bouton sera toujours centré sur le bord de l'ancre, comme ceci:
Comment puis-je déplacer le FAB de 20dp comme prévu?
Comme il pourrait y avoir bugs dans le design-support-lib concernant CoordinatorLayout
& marges, j’ai écrit une FrameLayout
qui implémente/copie le même "Behavior" comme le FAB et permet de définir une padding
pour simuler l’effet:
Assurez-vous de le mettre dans le paquetage Android.support.design.widget
car il doit accéder à certaines classes couvertes par les paquets.
/*
* 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;
}
}
}
Essayez de le mettre dans une disposition linéaire avec un rembourrage:
<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>
Pour ancrer la FloatingActionButton
ci-dessous la AppBar
comme ceci:
Étendre la FloatingActionButton
et remplacer 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)));
}
}
J'ai pu contourner ce problème en utilisant à la fois layout_anchor
layout_anchorGravity
et certains bourrages .. L'attribut anchor vous permet d'avoir une position View
par rapport à une autre vue. Cependant, cela ne fonctionne pas exactement de la même manière que RelativeLayout
. Plus à ce sujet à suivre.
layout_anchor
spécifie quelle View
votre View
désirée doit être positionnée (c'est-à-dire, cette View
doit être placée par rapport à la View
spécifiée par l'attribut layout_anchor
). Ensuite, layout_anchorGravity
spécifie quel côté de la valeur relative View
la View
courante sera positionnée, en utilisant les valeurs Gravity typiques (top
, bottom
, center_horizontal
, etc.).
Le seul problème avec l’utilisation de ces deux attributs est que le centre de la View
avec les ancres sera placé par rapport à l’autre View
. Par exemple, si vous spécifiez une FloatingActionButton
pour être ancrée au bas d'une TextView
, ce qui finit par se produire est que le centre de la FAB est placé le long du bord inférieur de la TextView
.
Pour résoudre ce problème, j'ai appliqué un peu de rembourrage sur le FAB, suffisamment pour que le bord supérieur du FAB touche le bord inférieur de la 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" />
Vous devrez peut-être augmenter le rembourrage pour obtenir l'effet souhaité. J'espère que cela pourra aider!