Android Design Library - Floating Action Button Padding/Margin Issues
Solution 1
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.
Solution 2
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.
Proof of concept
Solution 3
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" />
Solution 4
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 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);
}
Lollipop
Pre Lollipop
Solution 5
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;
Related videos on Youtube
Comments
-
Nick H over 4 years
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.
And from API 17.
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?
-
DariusL almost 9 yearsI'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 forCardView
.
-
-
JohnnyLambada almost 9 yearsThis should be the correct answer. However, it doesn't fix the problem 100% as the padding is also unsquare.
-
Markus Rubey almost 9 years@JohnnyLambada I don't think so.
RelativeLayout.LayoutParams
cannot be cast to theLayoutParams
ofFloatingActionButton
and i don't see any margins of 16dp on pre Lollipop. Width ofFloatingActionButton
pre Lollipop is 84dp instead of 56dp and it's height is 98dp. -
Eugene H almost 9 years@MarkusRubey I will update my answer with a working example and images displaying the pre lollipop and lollipop version.
-
Markus Rubey almost 9 years@EugeneH I see. I assumed he is using a CoordinatorLayout. But still, the margin part is not correct.
-
Eugene H almost 9 years@MarkusRubey I am actually not using CoordinatorLayout. I will update my answer with a working example with the margin corrected.
-
Eugene H almost 9 years@MarkusRubey Just updated the answer with a working example of the correct margins.
-
JohnnyLambada almost 9 years@MarkusRubey -
View.getLayoutParams
returns aLayoutParams
of the type of theView
is in -- in Eugene's case it's aRelativeLayout.LayoutParams
. -
JohnnyLambada almost 9 years@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. -
JohnnyLambada almost 9 years@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.
-
eleven over 8 yearslooks ridiculously ugly, but it seems there is no other solution
-
JJ86 over 8 yearsGood 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.
-
Jarett Millard about 8 yearsThe margin numbers will be different depending on the
elevation
you set on the FAB. -
Amit Tumkur about 8 yearsif Parent layout is card view then we need to use "card_view:useCompatPadding="true" for fab.
-
Pablo almost 8 yearsThis works for me after upgrading support library to version 23.2. Thanks!
-
Pedro Oliveira almost 8 yearsI see margins in your PoC.
-
Saravanabalagi Ramachandran almost 8 years@PedroOliveira Yes, you shall see the same margin across all versions of Android, which in fact is the objective.
-
Pedro Oliveira almost 8 yearsYou'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.
-
Saravanabalagi Ramachandran almost 8 yearsLet 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 android -
Saravanabalagi Ramachandran almost 8 yearsbtw the objective is NOT to add or remove margins, but to make the fab have SAME margin across different versions
-
Kishan Vaghela over 7 yearsuse
app:useCompatPadding="true"
-
Kishan Vaghela over 7 yearsJust use -
app:useCompatPadding="true"
-
whoKnows over 7 yearsThis is
fab:useCompatPadding
now. -
k2col over 7 yearsIf 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">
-
Björn Kechel almost 7 yearsapp:useCompatPadding does not work well with Api 21: on long press it creates a rectangular shadow, see stackoverflow.com/questions/44199012/…
-
bgplaya almost 7 yearsGreat, worked for me. Neither
useCompatPadding
nor anything else gets the same result. -
ingkevin almost 6 yearsIf you're using the compat library use setCompatElevation. And it will be better to use dips instead of pixels directly.
-
ingkevin almost 6 yearsI've combine this answer with vikram's
-
saltandpepper over 4 years@whoKnows I can't find any documentation that says that we should now use
fab:useCompatPadding
. -
Alon Shlider over 4 yearsI 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?
-
Alon Shlider over 4 yearsI can't seem to use this example after refactoring to AndroidX - the 3rd constructor does not compile anymore. Do you know what is wrong?