Android Design Library - Problemas de preenchimento / margem do botão de ação flutuante

109

Estou usando o novo FloatingActionButton da Biblioteca de projetos do Google e estou tendo problemas estranhos de preenchimento / margem. Esta imagem (com opções de layout do desenvolvedor ativadas) é da API 22.

insira a descrição da imagem aqui

E da API 17.

insira a descrição da imagem aqui

Este é o 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"/>

Por que o FAB na API 17 é muito maior em termos de preenchimento / margem?

hitch.united
fonte
1
Eu recomendo usar CoordinatorLayoutpara alinhá-lo verticalmente e observar o preenchimento extra pré-pirulito. Você poderia descobrir a partir das fontes FAB descompiladas, mas prefiro esperar que o Google conserte como fez para CardView.
DariusL

Respostas:

129

Atualização (outubro de 2016):

A solução correta agora é colocar app:useCompatPadding="true"em seu FloatingActionButton. Isso tornará o preenchimento consistente entre as diferentes versões da API. No entanto, isso ainda parece diminuir um pouco as margens padrão, então você pode precisar ajustá-las. Mas pelo menos não há necessidade de estilos específicos de API.

Resposta anterior:

Você pode fazer isso facilmente usando estilos específicos de API. Em seu normal values/styles.xml, coloque algo assim:

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

e em values-v21 / styles.xml, use isto:

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

e aplique o estilo ao seu FloatingActionButton:

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

Como outros notaram, na API <20, o botão renderiza sua própria sombra, o que adiciona à largura lógica geral da visualização, enquanto na API> = 20 ele usa os novos parâmetros de elevação que não contribuem para a largura da visualização.

Dmitry Brant
fonte
19
Android com erros .. :(
Abdullah Shoaib
3
parece ridiculamente feio, mas parece que não há outra solução
onze de
3
Boa resposta, obrigado. Posso sugerir editá-lo e adicionar um estilo também para tablet? Para API> = 20, a margem é de 24 dp; para API <20, percebi que você precisa de <item name = "android: layout_marginRight"> 12dp </item> <item name = "android: layout_marginBottom"> 6dp </item> . Tive de calculá-los sozinho, porque precisava disso para o meu aplicativo. Você também pode usar recursos dimens em vez de estilo.
JJ86 de
Os números das margens serão diferentes dependendo do que elevationvocê definir no FAB.
Jarett Millard de
usoapp:useCompatPadding="true"
Kishan Vaghela
51

Chega de mexer com styles.xmlou com .javaarquivos. Deixe-me simplificar.

Você pode usar app:useCompatPadding="true"e remover margens personalizadas para manter as mesmas margens em diferentes versões do Android

A margem extra / preenchimento que você viu no FAB em sua segunda foto é devido a este compatPadding em dispositivos pré-pirulito . Se esta propriedade não for definida, ela será aplicada em dispositivos pré-lollopop e NÃO em dispositivos lollipop +.

código do android studio

Prova de conceito

vista de design

Saravanabalagi Ramachandran
fonte
se o layout pai for de visualização de cartão, então precisamos usar "card_view: useCompatPadding =" true "para fab.
Amit Tumkur
Isso funciona para mim depois de atualizar a biblioteca de suporte para a versão 23.2. Obrigado!
Pablo
Eu vejo margens em seu PoC.
Pedro Oliveira
@PedroOliveira Sim, você verá a mesma margem em todas as versões do Android, o que na verdade é o objetivo.
Saravanabalagi Ramachandran
1
Você está dizendo que ele pode usar "xxx" para remover as margens personalizadas, mas sua prova de conceito mostra todas as margens, como o OP perguntou por quê.
Pedro Oliveira
31

depois de algum tempo pesquisando e testando a solução, resolvo meu problema ao adicionar esta linha somente ao meu layout xml:

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

e este é todo o meu layout de botão flutuante

<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
fonte
2
Ótimo, funcionou para mim. Nem useCompatPaddingnem qualquer outra coisa obtém o mesmo resultado.
bgplaya
1
Combinei
22

Há um problema na Biblioteca de Suporte de Design. Use o método abaixo para corrigir esse problema até que a biblioteca seja atualizada. Tente adicionar este código à sua atividade ou fragmento para resolver o problema. Mantenha o seu xml igual. No pirulito e acima não há margem, mas abaixo há uma margem de 16 dp.

Atualizar Exemplo de Trabalho

XML - FAB está dentro de um 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);
}

Converter dp em 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);
}

Pirulito

insira a descrição da imagem aqui

Pré Lollipop

insira a descrição da imagem aqui

Eugene H
fonte
Esta deve ser a resposta correta. No entanto, isso não corrige o problema 100%, pois o preenchimento também não é quadrado.
JohnnyLambada de
@JohnnyLambada Acho que não. RelativeLayout.LayoutParamsnão pode ser convertido para o LayoutParamsde FloatingActionButtone não vejo nenhuma margem de 16 dp no pré Lollipop. A largura do FloatingActionButtonpré Lollipop é 84 dp em vez de 56 dp e sua altura é 98 dp.
Markus Rubey
@MarkusRubey Vou atualizar minha resposta com um exemplo de trabalho e imagens exibindo a versão pré e pirulito.
Eugene H
1
@MarkusRubey - View.getLayoutParamsretorna um LayoutParamsdo tipo de Viewestá in - no caso de Eugene é a RelativeLayout.LayoutParams.
JohnnyLambada
1
@EugeneH Eu alterei RelativeLayout.LayoutParams para ViewGroup.MarginLayoutParams. Isso fará com que o código funcione em muitos outros casos e corrigirá o problema que MarkusRubey traz à tona.
JohnnyLambada de
8

No pré Lollipop FloatingActionButtoné responsável por desenhar sua própria sombra. Portanto, a visualização deve ser ligeiramente maior para dar espaço para a sombra. Para obter um comportamento consistente, você pode definir as margens para compensar a diferença de altura e largura. Atualmente, estou usando a seguinte classe :

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);
        }
    }
}

Atualização: Bibliotecas de suporte Android v23 renomeado fab_size dimens para:

import static android.support.design.R.dimen.design_fab_size_normal;
import static android.support.design.R.dimen.design_fab_size_mini;
Markus Rubey
fonte
Acabei de refatorar meu projeto para AndroidX e não consigo criar o terceiro construtor de seu exemplo. Como posso fazer isso usando bibliotecas AndroidX?
Alon Shlider
5

A resposta de Markus funcionou bem para mim depois de atualizar para a v23.1.0 e fazer alguns ajustes nas importações (com o plugin gradle recente, usamos nosso aplicativo R em vez do R da biblioteca de design). Aqui está o código da 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);
        }
    }
}
Aprenda OpenGL ES
fonte
Não consigo usar este exemplo após a refatoração para AndroidX - o terceiro construtor não compila mais. Você sabe o que está errado?
Alon Shlider
3

No arquivo de layout, defina a elevação do atributo como 0. porque leva a elevação padrão.

app:elevation="0dp"

Agora, em atividade, verifique o nível de API maior que 21 e defina a elevação, se necessário.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    fabBtn.setElevation(10.0f);
}
Vikram
fonte
Se você estiver usando a biblioteca compat , use setCompatElevation . E será melhor usar quedas em vez de pixels diretamente.
ingkevin