Biblioteca de diseño de Android: problemas de margen / relleno del botón de acción flotante

109

Estoy usando el nuevo FloatingActionButton de la biblioteca de diseño de Google y tengo algunos problemas extraños de relleno / margen. Esta imagen (con las opciones de diseño del desarrollador activadas) es de API 22.

ingrese la descripción de la imagen aquí

Y de API 17.

ingrese la descripción de la imagen aquí

Este es el 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 qué el FAB en API 17 es mucho más grande en cuanto a relleno / margen?

Hitch.united
fuente
1
Recomendaría usarlo CoordinatorLayoutpara alinearlo verticalmente y observar el relleno adicional pre-piruleta. Puede averiguarlo a partir de las fuentes FAB descompiladas, pero prefiero esperar a que Google lo arregle como lo hizo CardView.
DariusL

Respuestas:

129

Actualización (octubre de 2016):

La solución correcta ahora es poner app:useCompatPadding="true"en su FloatingActionButton. Esto hará que el relleno sea coherente entre las diferentes versiones de API. Sin embargo, esto todavía parece hacer que los márgenes predeterminados se desvíen un poco, por lo que es posible que deba ajustarlos. Pero al menos no hay más necesidad de estilos específicos de API.

Respuesta anterior:

Puede lograr esto fácilmente utilizando estilos específicos de API. En tu normal values/styles.xml, pon algo como esto:

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

y luego en values-v21 / styles.xml, use esto:

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

y aplique el estilo a su FloatingActionButton:

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

Como han señalado otros, en API <20, el botón genera su propia sombra, que se suma al ancho lógico general de la vista, mientras que en API> = 20 usa los nuevos parámetros de Elevación que no contribuyen al ancho de la vista.

Dmitry Brant
fuente
19
buggy Android .. :(
Abdullah Shoaib
3
se ve ridículamente feo, pero parece que no hay otra solución
once
3
Buena respuesta, gracias. ¿Puedo sugerir editarlo y agregar un estilo también para tableta? Para API> = 20, el margen es 24 dp; para API <20 me di cuenta de que necesita <item name = "android: layout_marginRight"> 12dp </item> <item name = "android: layout_marginBottom"> 6dp </item> . Tuve que calcularlos yo mismo, porque lo necesitaba para mi aplicación. También puede usar el recurso dimens en lugar de style.
JJ86
Los números de los márgenes serán diferentes según el elevationque establezca en la FAB.
Jarett Millard
usoapp:useCompatPadding="true"
Kishan Vaghela
51

Se acabaron con styles.xmlo con .javaarchivos. Déjame hacerlo simple.

Puede usar app:useCompatPadding="true"y eliminar márgenes personalizados para mantener los mismos márgenes en diferentes versiones de Android

El margen / relleno adicional que vio en el FAB en su segunda imagen se debe a este compatPadding en los dispositivos anteriores a la piruleta . Si esta propiedad no se establece, se aplica en dispositivos anteriores a lollopop y NO en dispositivos lollipop +.

código de estudio de Android

Prueba de concepto

vista de diseño

Saravanabalagi Ramachandran
fuente
si el diseño principal es la vista de tarjeta, entonces debemos usar "card_view: useCompatPadding =" true "para fab.
Amit Tumkur
Esto me funciona después de actualizar la biblioteca de soporte a la versión 23.2. ¡Gracias!
Pablo
Veo márgenes en su PoC.
Pedro Oliveira
@PedroOliveira Sí, verá el mismo margen en todas las versiones de Android, que de hecho es el objetivo.
Saravanabalagi Ramachandran
1
Está diciendo que puede usar "xxx" para eliminar los márgenes personalizados y, sin embargo, su prueba de concepto muestra todos los márgenes, como el OP preguntó por qué.
Pedro Oliveira
31

después de un tiempo buscando y probando la solución, soluciono mi problema agregando esta línea solo a mi diseño xml:

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

y este es mi diseño de botón flotante completo

<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
fuente
2
Genial, funcionó para mí. Ni useCompatPaddingnada más obtiene el mismo resultado.
bgplaya
1
Combiné
22

Hay un problema dentro de la biblioteca de soporte de diseño. Utilice el método siguiente para solucionar este problema hasta que se actualice la biblioteca. Intente agregar este código a su actividad o fragmento para resolver el problema. Mantenga su xml igual. En piruletas y arriba no hay margen, pero abajo hay un margen de 16dp.

Actualizar ejemplo de trabajo

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

Chupete

ingrese la descripción de la imagen aquí

Pre Lollipop

ingrese la descripción de la imagen aquí

Eugene H
fuente
Esta debería ser la respuesta correcta. Sin embargo, no soluciona el problema al 100% ya que el relleno tampoco es cuadrado.
JohnnyLambada
@JohnnyLambada No lo creo. RelativeLayout.LayoutParamsno se puede convertir al LayoutParamsde FloatingActionButtony no veo márgenes de 16dp en pre Lollipop. El ancho de FloatingActionButtonpre Lollipop es 84dp en lugar de 56dp y su altura es 98dp.
Markus Rubey
@MarkusRubey Actualizaré mi respuesta con un ejemplo funcional e imágenes que muestran la versión previa de la paleta y la paleta.
Eugene H
1
@MarkusRubey - View.getLayoutParamsdevuelve un LayoutParamsdel tipo de Viewis - en el caso de Eugene es un RelativeLayout.LayoutParams.
JohnnyLambada
1
@EugeneH Cambié RelativeLayout.LayoutParams a ViewGroup.MarginLayoutParams. Esto hará que el código funcione en muchos más casos y solucionará el problema que plantea MarkusRubey.
JohnnyLambada
8

En pre Lollipop FloatingActionButtonse encarga de dibujar su propia sombra. Por lo tanto, la vista tiene que ser un poco más grande para dejar espacio a la sombra. Para obtener un comportamiento coherente, puede establecer márgenes para tener en cuenta la diferencia de altura y anchura. Actualmente estoy usando la siguiente clase :

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

Actualización: Bibliotecas de soporte de Android v23 renombradas fab_size dimens a:

import static android.support.design.R.dimen.design_fab_size_normal;
import static android.support.design.R.dimen.design_fab_size_mini;
Markus Rubey
fuente
Acabo de refactorizar mi proyecto a AndroidX y no puedo crear el tercer constructor a partir de su ejemplo. ¿Cómo puedo hacerlo usando las bibliotecas de AndroidX?
Alon Shlider
5

La respuesta de Markus funcionó bien para mí después de actualizar a v23.1.0 y hacer algunos ajustes en las importaciones (con el complemento gradle reciente usamos nuestra aplicación R en lugar de la R de la biblioteca de diseño). Aquí está el código para 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
fuente
Parece que no puedo usar este ejemplo después de refactorizar a AndroidX: el tercer constructor ya no se compila. ¿Sabes lo que está mal?
Alon Shlider
3

En el archivo de diseño, establezca la elevación del atributo en 0. porque toma la elevación predeterminada.

app:elevation="0dp"

Ahora, en actividad, verifique el nivel de API mayor que 21 y configure la elevación si es necesario.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    fabBtn.setElevation(10.0f);
}
Vikram
fuente
Si está usando la biblioteca compat , use setCompatElevation . Y será mejor usar fondos en lugar de píxeles directamente.
ingkevin