Biblioteka projektów systemu Android - problemy z wypełnieniem / marginesem pływającego przycisku akcji


109

Używam nowego FloatingActionButton z Google Design Library i pojawiają się dziwne problemy z dopełnieniem / marginesami. Ten obraz (z włączonymi opcjami układu programisty) pochodzi z interfejsu API 22.

wprowadź opis obrazu tutaj

I z API 17.

wprowadź opis obrazu tutaj

To jest plik 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"/>

Dlaczego FAB w API 17 jest o wiele większy pod względem dopełnienia / marginesów?


1
Polecam użyć CoordinatorLayoutdo wyrównania go w pionie i gałki ocznej dodatkowej wyściółki pre-lollipop. Możesz to rozgryźć na podstawie zdekompilowanych źródeł FAB, ale wolałbym poczekać, aż Google naprawi to tak, jak zrobili CardView.
DariusL

Odpowiedzi:


129

Aktualizacja (październik 2016 r.):

Prawidłowym rozwiązaniem jest teraz umieszczenie app:useCompatPadding="true"w FloatingActionButton. Dzięki temu wypełnienie będzie spójne między różnymi wersjami interfejsu API. Jednak nadal wydaje się, że powoduje to nieznaczne zmniejszenie domyślnych marginesów, więc może być konieczne ich dostosowanie. Ale przynajmniej nie ma już potrzeby stosowania stylów specyficznych dla API.

Poprzednia odpowiedź:

Możesz to łatwo osiągnąć za pomocą stylów specyficznych dla API. W swoim normalnym miejscu values/styles.xmlumieść coś takiego:

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

a następnie w values-v21 / styles.xml, użyj tego:

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

i zastosuj styl do FloatingActionButton:

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

Jak zauważyli inni, w API <20 przycisk renderuje swój własny cień, który dodaje do ogólnej logicznej szerokości widoku, podczas gdy w API> = 20 używa nowych parametrów Elevation, które nie mają wpływu na szerokość widoku.


19
buggy Android .. :(
Abdullah Shoaib

3
wygląda śmiesznie brzydko, ale wydaje się, że nie ma innego rozwiązania
11

3
Dobra odpowiedź, dziękuję. Czy mogę zasugerować edycję i dodanie stylu również dla tabletu? Dla API> = 20, margines wynosi 24 dp; dla API <20 zauważyłem, że potrzebujesz <item name = "android: layout_marginRight"> 12dp </item> <item name = "android: layout_marginBottom"> 6dp </item> . Musiałem je sam obliczyć, ponieważ potrzebowałem tego do mojej aplikacji. Możesz także użyć zasobu dimens zamiast stylu.
JJ86

Numery marginesów będą się różnić w zależności od elevationustawień ustawionych na FAB.
Jarett Millard,

użycieapp:useCompatPadding="true"
Kishan Vaghela

51

Koniec z majstrowaniemstyles.xml przy .javaplikach. Pozwól mi to uprościć.

Możesz używać app:useCompatPadding="true"i usuwać marginesy niestandardowe, aby zachować te same marginesy w różnych wersjach Androida

Dodatkowy margines / wypełnienie, które widziałeś na FAB na swoim drugim obrazie, jest spowodowany tym kompatybilnym wypełnieniem na urządzeniach przed Lollipopem . Jeśli ta właściwość nie jest ustawiona, jest stosowana na urządzeniach sprzed lollopop , a NIE na urządzeniach Lollipop +.

kod studia android

Dowód koncepcji

widok projektu


jeśli układ nadrzędny to widok karty, musimy użyć „card_view: useCompatPadding =" true "dla fab.
Amit Tumkur

U mnie to działa po uaktualnieniu biblioteki obsługi do wersji 23.2. Dzięki!
Pablo

Widzę marginesy w Twoim PoC.
Pedro Oliveira

@PedroOliveira Tak, we wszystkich wersjach Androida zobaczysz ten sam margines, co w rzeczywistości jest celem.
Saravanabalagi Ramachandran

1
Mówisz, że może użyć „xxx”, aby usunąć niestandardowe marginesy, a mimo to Twój dowód słuszności pokazuje wszystkie marginesy, tak jak OP zapytał dlaczego.
Pedro Oliveira

31

po kilku chwilach szukania i testowania rozwiązania naprawiam swój problem, dodając tę ​​linię tylko do mojego układu xml:

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

a to jest mój cały układ przycisków pływających

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

2
Świetnie, pracował dla mnie. Ani useCompatPaddingnic innego nie daje takiego samego wyniku.
bgplaya


22

Wystąpił problem w bibliotece Design Support Library. Użyj poniższej metody, aby rozwiązać ten problem, dopóki biblioteka nie zostanie zaktualizowana. Spróbuj dodać ten kod do swojego działania lub fragmentu, aby rozwiązać problem. Zachowaj ten sam plik XML. Na Lollipop i powyżej nie ma marginesu, ale poniżej jest margines 16 dp.

Zaktualizuj przykład roboczy

XML - FAB znajduje się w 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"/>

Jawa

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

Konwertuj dp na 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);
}

Lizak

wprowadź opis obrazu tutaj

Pre Lollipop

wprowadź opis obrazu tutaj


To powinna być prawidłowa odpowiedź. Jednak nie rozwiązuje to problemu w 100%, ponieważ wypełnienie jest również nierówne.
JohnnyLambada

@JohnnyLambada Nie sądzę. RelativeLayout.LayoutParamsnie można rzutować na LayoutParamsz FloatingActionButtoni nie widzę żadnych marginesów 16 dp w wersji wcześniejszej niż Lollipop. Szerokość FloatingActionButtonwcześniejszego Lollipopa to 84 dp zamiast 56 dp, a jego wysokość to 98 dp.
Markus Rubey

@MarkusRubey Zaktualizuję moją odpowiedź działającym przykładem i obrazami przedstawiającymi wersje pre Lollipop i Lollipop.
Eugene H

1
@MarkusRubey - View.getLayoutParamszwraca a LayoutParamstypu Viewjest w - w przypadku Eugene'a jest to RelativeLayout.LayoutParams.
JohnnyLambada

1
@EugeneH Zmieniłem RelativeLayout.LayoutParams na ViewGroup.MarginLayoutParams. Dzięki temu kod będzie działał w wielu innych przypadkach i rozwiąże problem, który pojawia się w MarkusRubey.
JohnnyLambada

8

W wersji pre Lollipop FloatingActionButtonjest odpowiedzialny za rysowanie własnego cienia. Dlatego widok musi być nieco większy, aby zrobić miejsce na cień. Aby uzyskać spójne zachowanie, możesz ustawić marginesy uwzględniające różnicę w wysokości i szerokości. Obecnie używam następującej klasy :

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

Aktualizacja: Zmieniono nazwę bibliotek obsługi Androida v23 fab_size na:

import static android.support.design.R.dimen.design_fab_size_normal;
import static android.support.design.R.dimen.design_fab_size_mini;

Właśnie refaktoryzowałem mój projekt na AndroidX i nie mogę utworzyć trzeciego konstruktora na podstawie twojego przykładu. Jak mogę to zrobić za pomocą bibliotek AndroidX?
Alon Shlider

5

Odpowiedź Markusa zadziałała dobrze po aktualizacji do wersji 23.1.0 i wprowadzeniu pewnych poprawek do importu (z najnowszą wtyczką gradle używamy naszej aplikacji R zamiast R z biblioteki projektów). Oto kod wersji 23.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);
        }
    }
}

Wydaje się, że nie mogę użyć tego przykładu po refaktoryzacji do AndroidX - trzeci konstruktor już się nie kompiluje. Czy wiesz, co się stało?
Alon Shlider

3

W pliku układu ustaw rzędną atrybutu na 0., ponieważ przyjmuje ona domyślną rzędną.

app:elevation="0dp"

Teraz w aktywności sprawdź poziom api większy niż 21 i ustaw wysokość, jeśli to konieczne.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    fabBtn.setElevation(10.0f);
}

Jeśli używasz kompatybilnego biblioteki use setCompatElevation . I lepiej będzie bezpośrednio używać spadków zamiast pikseli.
ingkevin
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.