Bibliothèque de conception Android - Problèmes de remplissage / marge du bouton d'action flottant

109

J'utilise le nouveau FloatingActionButton de la bibliothèque de conception Google et j'obtiens d'étranges problèmes de remplissage / marge. Cette image (avec les options de mise en page du développeur activées) provient de l'API 22.

entrez la description de l'image ici

Et de l'API 17.

entrez la description de l'image ici

C'est le XML

<android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_gravity="bottom|right"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="-32dp"
        android:src="@drawable/ic_action_add"
        app:fabSize="normal"
        app:elevation="4dp"
        app:borderWidth="0dp"
        android:layout_below="@+id/header"/>

Pourquoi le FAB de l'API 17 est-il tellement plus grand en termes de remplissage / marge?

attelage.
la source
1
Je recommanderais de l'utiliser CoordinatorLayoutpour l'aligner verticalement et le globe oculaire du rembourrage supplémentaire pré-sucette. Vous pouvez le comprendre à partir des sources FAB décompilées, mais je préfère attendre que Google le répare comme ils l'ont fait pour CardView.
DariusL

Réponses:

129

Mise à jour (octobre 2016):

La bonne solution est maintenant de mettre app:useCompatPadding="true"dans votre FloatingActionButton. Cela rendra le remplissage cohérent entre les différentes versions d'API. Cependant, cela semble encore réduire légèrement les marges par défaut, vous devrez peut-être les ajuster. Mais au moins, il n'y a plus besoin de styles spécifiques à l'API.

Réponse précédente:

Vous pouvez accomplir cela facilement en utilisant des styles spécifiques à l'API. Dans votre normal values/styles.xml, mettez quelque chose comme ceci:

<style name="floating_action_button">
    <item name="android:layout_marginLeft">0dp</item>
    <item name="android:layout_marginTop">0dp</item>
    <item name="android:layout_marginRight">8dp</item>
    <item name="android:layout_marginBottom">0dp</item>
</style>

puis sous values-v21 / styles.xml, utilisez ceci:

<style name="floating_action_button">
    <item name="android:layout_margin">16dp</item>
</style>

et appliquez le style à votre FloatingActionButton:

<android.support.design.widget.FloatingActionButton
...
style="@style/floating_action_button"
...
/>

Comme d'autres l'ont noté, dans l'API <20, le bouton rend sa propre ombre, ce qui ajoute à la largeur logique globale de la vue, tandis que dans l'API> = 20, il utilise les nouveaux paramètres d'élévation qui ne contribuent pas à la largeur de la vue.

Dmitry Brant
la source
19
buggy Android .. :(
Abdullah Shoaib
3
semble ridiculement moche, mais il semble qu'il n'y ait pas d'autre solution
onze
3
Bonne réponse, merci. Puis-je suggérer de le modifier et d'ajouter un style également pour tablette? Pour API> = 20, la marge est de 24dp; pour l'API <20, j'ai remarqué que vous avez besoin de <item name = "android: layout_marginRight"> 12dp </item> <item name = "android: layout_marginBottom"> 6dp </item> . J'ai dû les calculer moi-même, car j'en avais besoin pour mon application. Vous pouvez également utiliser la ressource dimens au lieu du style.
JJ86
Les numéros de marge seront différents selon le que elevationvous avez défini sur le FAB.
Jarett Millard
utilisationapp:useCompatPadding="true"
Kishan Vaghela
51

Plus besoin de jouer avec styles.xmlou avec des .javafichiers. Laissez-moi faire simple.

Vous pouvez utiliser app:useCompatPadding="true"et supprimer des marges personnalisées pour conserver les mêmes marges sur différentes versions d'Android

La marge / rembourrage supplémentaire que vous avez vu sur le FAB dans votre deuxième image est dû à ce compatPadding sur les appareils pré-sucette . Si cette propriété n'est pas définie, elle est appliquée sur les appareils pré-lollopop et NON sur les appareils lollipop +.

code de studio android

Preuve de concept

vue de conception

Saravanabalagi Ramachandran
la source
si la mise en page parent est une vue carte, nous devons utiliser "card_view: useCompatPadding =" true "pour fab.
Amit Tumkur
Cela fonctionne pour moi après la mise à niveau de la bibliothèque de support vers la version 23.2. Merci!
Pablo
Je vois des marges dans votre PoC.
Pedro Oliveira
@PedroOliveira Oui, vous verrez la même marge sur toutes les versions d'Android, ce qui est en fait l'objectif.
Saravanabalagi Ramachandran
1
Vous dites qu'il peut utiliser "xxx" pour supprimer les marges personnalisées, et pourtant votre preuve de concept montre toutes les marges, comme l'OP a demandé pourquoi.
Pedro Oliveira
31

après quelques recherches et tests de solution, je résout mon problème en ajoutant cette ligne uniquement à ma mise en page XML:

app:elevation="0dp"
app:pressedTranslationZ="0dp"

et c'est toute ma disposition de bouton flottant

<android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:src="@drawable/ic_add"
        app:elevation="0dp"
        app:pressedTranslationZ="0dp"
        app:fabSize="normal" />
Mostafa Rostami
la source
2
Super, a travaillé pour moi. Ni useCompatPaddingni rien d'autre n'obtient le même résultat.
bgplaya
1
J'ai combiné cette réponse avec celle de
Vikram
22

Il y a un problème dans la bibliothèque de support de conception. Utilisez la méthode ci-dessous pour résoudre ce problème jusqu'à ce que la bibliothèque soit mise à jour. Essayez d'ajouter ce code à votre activité ou fragment pour résoudre le problème. Gardez votre xml le même. Sur la sucette et au-dessus il n'y a pas de marge mais en dessous il y a une marge de 16dp.

Mettre à jour l'exemple de travail

XML - FAB est dans un RelativeLayout

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    android:layout_marginBottom="16dp"
    android:layout_marginRight="16dp"
    android:src="@mipmap/ic_add"
    app:backgroundTint="@color/accent"
    app:borderWidth="0dp"
    app:elevation="4sp"/>

Java

FloatingActionButton mFab = (FloatingActionButton) v.findViewById(R.id.fab);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
    ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) mFab.getLayoutParams();
    p.setMargins(0, 0, dpToPx(getActivity(), 8), 0); // get rid of margins since shadow area is now the margin
    mFab.setLayoutParams(p);
}

Convertir dp en px

public static int dpToPx(Context context, float dp) {
    // Reference http://stackoverflow.com/questions/8309354/formula-px-to-dp-dp-to-px-android
    float scale = context.getResources().getDisplayMetrics().density;
    return (int) ((dp * scale) + 0.5f);
}

Sucette

entrez la description de l'image ici

Pré sucette

entrez la description de l'image ici

Eugène H
la source
Cela devrait être la bonne réponse. Cependant, cela ne résout pas le problème à 100% car le rembourrage est également non carré.
JohnnyLambada
@JohnnyLambada Je ne pense pas. RelativeLayout.LayoutParamsne peut pas être converti en LayoutParamsde FloatingActionButtonet je ne vois aucune marge de 16dp sur pré Lollipop. La largeur du FloatingActionButtonpré Lollipop est de 84dp au lieu de 56dp et sa hauteur est de 98dp.
Markus Rubey
@MarkusRubey Je mettrai à jour ma réponse avec un exemple de travail et des images affichant la version pré sucette et sucette.
Eugene H
1
@MarkusRubey - View.getLayoutParamsrenvoie a LayoutParamsdu type de Viewest dans - dans le cas d'Eugene, c'est un RelativeLayout.LayoutParams.
JohnnyLambada
1
@EugeneH J'ai changé RelativeLayout.LayoutParams en ViewGroup.MarginLayoutParams. Cela fera fonctionner le code dans de nombreux autres cas et résoudra le problème soulevé par MarkusRubey.
JohnnyLambada
8

Sur pré Lollipop FloatingActionButtonest responsable de dessiner sa propre ombre. Par conséquent, la vue doit être légèrement plus grande pour faire de la place pour l'ombre. Pour obtenir un comportement cohérent, vous pouvez définir des marges pour tenir compte de la différence de hauteur et de largeur. J'utilise actuellement la classe suivante :

import android.content.Context;
import android.content.res.TypedArray;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
import android.view.ViewGroup.MarginLayoutParams;

import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;

import static android.support.design.R.styleable.FloatingActionButton;
import static android.support.design.R.styleable.FloatingActionButton_fabSize;
import static android.support.design.R.style.Widget_Design_FloatingActionButton;
import static android.support.design.R.dimen.fab_size_normal;
import static android.support.design.R.dimen.fab_size_mini;

public class CustomFloatingActionButton extends FloatingActionButton {

    private int mSize;

    public CustomFloatingActionButton(Context context) {
        this(context, null);
    }

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

    public CustomFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, FloatingActionButton, defStyleAttr,
                Widget_Design_FloatingActionButton);
        this.mSize = a.getInt(FloatingActionButton_fabSize, 0);
        a.recycle();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (SDK_INT < LOLLIPOP) {
            int size = this.getSizeDimension();
            int offsetVertical = (h - size) / 2;
            int offsetHorizontal = (w - size) / 2;
            MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
            params.leftMargin = params.leftMargin - offsetHorizontal;
            params.rightMargin = params.rightMargin - offsetHorizontal;
            params.topMargin = params.topMargin - offsetVertical;
            params.bottomMargin = params.bottomMargin - offsetVertical;
            setLayoutParams(params);
        }
    }

    private final int getSizeDimension() {
        switch (this.mSize) {
            case 0: default: return this.getResources().getDimensionPixelSize(fab_size_normal);
            case 1: return this.getResources().getDimensionPixelSize(fab_size_mini);
        }
    }
}

Mise à jour: les bibliothèques de support Android v23 ont renommé fab_size dimens en:

import static android.support.design.R.dimen.design_fab_size_normal;
import static android.support.design.R.dimen.design_fab_size_mini;
Markus Rubey
la source
Je viens de refactoriser mon projet vers AndroidX et je suis incapable de créer le 3ème constructeur à partir de votre exemple. Comment puis-je le faire en utilisant les bibliothèques AndroidX?
Alon Shlider
5

La réponse de Markus a bien fonctionné pour moi après la mise à jour vers la v23.1.0 et après avoir fait quelques ajustements aux importations (avec le récent plugin gradle, nous utilisons notre application R au lieu du R de la bibliothèque de conception). Voici le code pour la v23.1.0:

// Based on this answer: https://stackoverflow.com/a/30845164/1317564
public class CustomFloatingActionButton extends FloatingActionButton {
    private int mSize;

    public CustomFloatingActionButton(Context context) {
        this(context, null);
    }

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

    @SuppressLint("PrivateResource")
    public CustomFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.FloatingActionButton, defStyleAttr,
                R.style.Widget_Design_FloatingActionButton);
        mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, 0);
        a.recycle();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            int size = this.getSizeDimension();
            int offsetVertical = (h - size) / 2;
            int offsetHorizontal = (w - size) / 2;
            ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
            params.leftMargin = params.leftMargin - offsetHorizontal;
            params.rightMargin = params.rightMargin - offsetHorizontal;
            params.topMargin = params.topMargin - offsetVertical;
            params.bottomMargin = params.bottomMargin - offsetVertical;
            setLayoutParams(params);
        }
    }

    @SuppressLint("PrivateResource")
    private int getSizeDimension() {
        switch (mSize) {
            case 1:
                return getResources().getDimensionPixelSize(R.dimen.design_fab_size_mini);
            case 0:
            default:
                return getResources().getDimensionPixelSize(R.dimen.design_fab_size_normal);
        }
    }
}
Apprenez OpenGL ES
la source
Je n'arrive pas à utiliser cet exemple après la refactorisation vers AndroidX - le 3ème constructeur ne compile plus. Savez-vous ce qui ne va pas?
Alon Shlider
3

Dans le fichier de mise en page, définissez l'élévation de l'attribut sur 0. car il prend l'élévation par défaut.

app:elevation="0dp"

Maintenant, dans l'activité, vérifiez le niveau de l'API supérieur à 21 et définissez l'élévation si nécessaire.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    fabBtn.setElevation(10.0f);
}
Vikram
la source
Si vous utilisez le compat bibliothèque , utilisez setCompatElevation . Et il sera préférable d'utiliser directement des creux plutôt que des pixels.
ingkevin le