Android Design Library - Floating Action Button Padding/Margin Issues
Asked Answered
S

7

111

I'm using the new FloatingActionButton from the Google Design Library and I am getting some strange padding/margin problems. This image (with developer layout options on) is from API 22.

enter image description here

And from API 17.

enter image description here

This is the 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"/>

Why is the FAB in API 17 so much larger padding/margin wise?

Sequestrate answered 6/6, 2015 at 21:45 Comment(1)
I'd recommend using CoordinatorLayout to align it vertically and eyeball the extra padding pre-lollipop. You could figure it out from the decompiled FAB sources, but I'd rather wait for Google to fix it like they did for CardView.Gonta
P
131

Update (Oct 2016):

The correct solution now is to put app:useCompatPadding="true" into your FloatingActionButton. This will make the padding consistent between different API versions. However, this still seems to make the default margins off by a little bit, so you may need to adjust those. But at least there's no further need for API-specific styles.

Previous answer:

You can accomplish this easily using API-specific styles. In your normal values/styles.xml, put something like this:

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

and then under values-v21/styles.xml, use this:

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

and apply the style to your FloatingActionButton:

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

As others have noted, in API <20, the button renders its own shadow, which adds to the overall logical width of the view, whereas in API >=20 it uses the new Elevation parameters which don't contribute to the view width.

Pareto answered 16/6, 2015 at 22:29 Comment(6)
looks ridiculously ugly, but it seems there is no other solutionOmega
Good answer, thank you. May i suggest to edit it and add a style also for tablet? For API >=20, the margin is 24dp; for API<20 i noticed that the you need <item name="android:layout_marginRight">12dp</item> <item name="android:layout_marginBottom">6dp</item> . I had to calculate them by myself, because i needed it for my app. You can also use dimens resource instead of style.Tetryl
The margin numbers will be different depending on the elevation you set on the FAB.Kero
use app:useCompatPadding="true"Imprudent
If you're going to create a style for a FAB then you should also set the parent of that style accordingly, ie <style name="floating_action_button" parent="Widget.Design.FloatingActionButton">Alexine
app:useCompatPadding does not work well with Api 21: on long press it creates a rectangular shadow, see #44199512Fruiter
L
51

No more fiddling with styles.xml or with .java files. Let me make it simple.

You can use app:useCompatPadding="true" and remove custom margins to maintain same margins across different versions of android

The extra margin/padding you saw on the FAB in your second picture is due to this compatPadding on pre-lollipop devices. If this property is not set, it gets applied on pre-lollopop devices and NOT in lollipop+ devices.

android studio code

Proof of concept

design view

Ligure answered 24/4, 2016 at 12:16 Comment(9)
if Parent layout is card view then we need to use "card_view:useCompatPadding="true" for fab.Dreary
This works for me after upgrading support library to version 23.2. Thanks!Deformation
I see margins in your PoC.Crush
@PedroOliveira Yes, you shall see the same margin across all versions of Android, which in fact is the objective.Ligure
You're saying that he can use "xxx" to remove custom margins, and yet your proof of concept shows all margins, like the OP asked why.Crush
Let me make it clear, userCompatPadding=true does NOT remove custom margins. You need to REMOVE custom margins and add the property and set it to true so that the result would look like above across different versions of androidLigure
btw the objective is NOT to add or remove margins, but to make the fab have SAME margin across different versionsLigure
This is fab:useCompatPadding now.Nonego
@Nonego I can't find any documentation that says that we should now use fab:useCompatPadding.Rosefish
P
33

after a few time searching and test solution i fix my problem with add this line to my xml layout only:

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

and this is my whole float button layout

<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" />
Pomcroy answered 2/6, 2016 at 13:49 Comment(2)
Great, worked for me. Neither useCompatPadding nor anything else gets the same result.Opportuna
I've combine this answer with vikram'sWeizmann
N
22

There is an issue within the Design Support Library. Use the method below to fix this issue until the library is updated. Try adding this code to your activity or fragment to solve the issue. Keep your xml the same. On lollipop and above there is no margin but below there is a margin of 16dp.

Update Working Example

XML - FAB is within a 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);
}

Convert dp to to px

public static int dpToPx(Context context, float dp) {
    // Reference https://mcmap.net/q/36299/-formula-px-to-dp-dp-to-px-android
    float scale = context.getResources().getDisplayMetrics().density;
    return (int) ((dp * scale) + 0.5f);
}

Lollipop

enter image description here

Pre Lollipop

enter image description here

Nonperishable answered 6/6, 2015 at 22:22 Comment(10)
This should be the correct answer. However, it doesn't fix the problem 100% as the padding is also unsquare.Carnahan
@Carnahan I don't think so. RelativeLayout.LayoutParams cannot be cast to the LayoutParams of FloatingActionButton and i don't see any margins of 16dp on pre Lollipop. Width of FloatingActionButton pre Lollipop is 84dp instead of 56dp and it's height is 98dp.Zrike
@MarkusRubey I will update my answer with a working example and images displaying the pre lollipop and lollipop version.Nonperishable
@EugeneH I see. I assumed he is using a CoordinatorLayout. But still, the margin part is not correct.Zrike
@MarkusRubey I am actually not using CoordinatorLayout. I will update my answer with a working example with the margin corrected.Nonperishable
@MarkusRubey Just updated the answer with a working example of the correct margins.Nonperishable
@MarkusRubey - View.getLayoutParams returns a LayoutParams of the type of the View is in -- in Eugene's case it's a RelativeLayout.LayoutParams.Carnahan
@EugeneH FYI, to get it perfectly aligned with another view, I had to move it further south by 4dips. I used mFab.setTranslationY(dpToPx(getActivity(), 4)); to accomplish this.Carnahan
@EugeneH I changed RelativeLayout.LayoutParams to ViewGroup.MarginLayoutParams. This will make the code work in many more cases and fixes the problem that MarkusRubey brings up.Carnahan
Just use - app:useCompatPadding="true"Imprudent
Z
8

On pre Lollipop FloatingActionButton is responsible for drawing its own shadow. Therefore the view has to be slightly bigger to make space for the shadow. To get a consistent behavior you can set margins to account for the difference in height and width. I am currently using the following class:

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

Update: Android Support Libraries v23 renamed fab_size dimens to:

import static android.support.design.R.dimen.design_fab_size_normal;
import static android.support.design.R.dimen.design_fab_size_mini;
Zrike answered 15/6, 2015 at 12:27 Comment(1)
I just refactored my project to AndroidX and I am unable to create the 3rd constructor from your example. How can I do it using AndroidX libraries?Lungan
I
5

Markus's answer worked well for me after updating to v23.1.0 and making some adjustments to the imports (with the recent gradle plugin we use our app R instead of the design library's R). Here is the code for v23.1.0:

// Based on this answer: https://mcmap.net/q/194438/-android-design-library-floating-action-button-padding-margin-issues
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);
        }
    }
}
Integrand answered 16/10, 2015 at 22:59 Comment(1)
I can't seem to use this example after refactoring to AndroidX - the 3rd constructor does not compile anymore. Do you know what is wrong?Lungan
H
3

In layout file set attribute elevation to 0. because it takes default elevation.

app:elevation="0dp"

Now in activity check api level greater than 21 and set elevation if needed.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    fabBtn.setElevation(10.0f);
}
Hemophilia answered 10/11, 2016 at 6:0 Comment(1)
If you're using the compat library use setCompatElevation. And it will be better to use dips instead of pixels directly.Weizmann

© 2022 - 2024 — McMap. All rights reserved.