Библиотека дизайна Android - Проблемы с заполнением / полем плавающей кнопки действия

109

Я использую новый элемент FloatingActionButton из библиотеки дизайна Google, и у меня возникают странные проблемы с заполнением / полем. Это изображение (с включенными параметрами макета разработчика) взято из API 22.

введите описание изображения здесь

И из API 17.

введите описание изображения здесь

Это 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"/>

Почему FAB в API 17 намного больше заполнения / поля?

hitch.united
источник
1
Я бы рекомендовал использовать его, CoordinatorLayoutчтобы выровнять его по вертикали и обратить внимание на дополнительную набивку перед леденцом. Вы можете понять это из декомпилированных источников FAB, но я бы предпочел дождаться, пока Google исправит это, как они это сделали для CardView.
DariusL

Ответы:

129

Обновление (октябрь 2016 г.):

Теперь правильное решение - поместить app:useCompatPadding="true"в ваш FloatingActionButton. Это сделает заполнение согласованным между разными версиями API. Тем не менее, это все еще немного снижает поля по умолчанию, поэтому вам может потребоваться их настроить. Но, по крайней мере, больше нет необходимости в стилях, специфичных для API.

Предыдущий ответ:

Вы можете легко добиться этого, используя стили, специфичные для API. В обычном режиме values/styles.xmlпоставьте что-то вроде этого:

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

а затем в values-v21 / styles.xml используйте это:

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

и примените стиль к вашему FloatingActionButton:

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

Как отмечали другие, в API <20 кнопка отображает свою собственную тень, которая увеличивает общую логическую ширину представления, тогда как в API> = 20 она использует новые параметры высоты, которые не влияют на ширину представления.

Дмитрий Брант
источник
19
глючный Android .. :(
Абдулла Шоаиб
3
выглядит до смешного уродливо, но, похоже, другого решения нет
11
3
Хороший ответ, спасибо. Могу я предложить отредактировать его и добавить стиль также для планшета? Для API> = 20 маржа составляет 24 dp; для API <20 я заметил, что вам нужно <item name = "android: layout_marginRight"> 12dp </item> <item name = "android: layout_marginBottom"> 6dp </item> . Мне пришлось рассчитывать их самостоятельно, потому что это было нужно для моего приложения. Вы также можете использовать размерный ресурс вместо стиля.
JJ86
Номера полей будут отличаться в зависимости от того, что elevationвы установили на FAB.
Джаретт Миллард
useapp:useCompatPadding="true"
Кишан Вагела
51

Нет больше возиться с styles.xmlили с .javaфайлами. Позвольте мне сделать это проще.

Вы можете использовать app:useCompatPadding="true"и удалять пользовательские поля, чтобы поддерживать одинаковые поля в разных версиях Android.

Дополнительные поля / отступы, которые вы видели на FAB на втором изображении, связаны с этим compatPadding на устройствах pre-lollipop . Если это свойство не установлено, оно применяется к устройствам pre-lollopop, а НЕ к устройствам lollipop +.

код студии Android

Доказательство концепции

вид дизайна

Сараванабалаги Рамачандран
источник
если родительский макет представляет собой карточный вид, тогда нам нужно использовать "card_view: useCompatPadding =" true "для fab.
Амит Тумкур,
У меня это работает после обновления библиотеки поддержки до версии 23.2. Спасибо!
Пабло
Я вижу поля в вашем PoC.
Педро Оливейра
@PedroOliveira Да, вы увидите одинаковую маржу для всех версий Android, что на самом деле является целью.
Сараванабалаги Рамачандран
1
Вы говорите, что он может использовать «xxx» для удаления настраиваемых полей, и все же ваше доказательство концепции показывает все поля, как OP спросил, почему.
Педро Оливейра
31

После нескольких поисков и тестирования решения я исправил свою проблему, добавив эту строку только в мой макет xml:

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

и это весь мой макет плавающих кнопок

<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
Отлично, сработало для меня. Ни то, useCompatPaddingни другое не дает такого результата.
bgplaya
1
Я совместил этот ответ с ответом Викрама
ingkevin
22

Возникла проблема в библиотеке поддержки дизайна. Используйте метод ниже, чтобы исправить эту проблему, пока библиотека не будет обновлена. Попробуйте добавить этот код в свою операцию или фрагмент, чтобы решить проблему. Сохраните свой xml таким же. На леденцах и выше нет поля, но ниже есть запас 16dp.

Обновить рабочий пример

XML - FAB находится в 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"/>

Ява

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

Преобразовать dp в 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);
}

леденец

введите описание изображения здесь

Pre Lollipop

введите описание изображения здесь

Евгений Н
источник
Это должен быть правильный ответ. Однако это не решает проблему на 100%, поскольку отступы также неквадратные.
JohnnyLambada
@JohnnyLambada Я так не думаю. RelativeLayout.LayoutParamsне может быть преобразован в LayoutParamsof, FloatingActionButtonи я не вижу полей в 16dp на pre Lollipop. Ширина FloatingActionButtonpre Lollipop составляет 84dp вместо 56dp, а высота - 98dp.
Маркус Руби
@MarkusRubey Я дополню свой ответ рабочим примером и изображениями, отображающими предварительную версию леденца на палочке и леденец на палочке.
Eugene H
1
@MarkusRubey - View.getLayoutParamsвозвращает LayoutParamsтип, в котором Viewнаходится - в случае Евгения это a RelativeLayout.LayoutParams.
JohnnyLambada
1
@EugeneH Я изменил RelativeLayout.LayoutParams на ViewGroup.MarginLayoutParams. Это заставит код работать во многих других случаях и решит проблему, которую поднимает MarkusRubey.
JohnnyLambada
8

Предварительно Lollipop FloatingActionButtonотвечает за рисование собственной тени. Следовательно, вид должен быть немного больше, чтобы оставалось место для тени. Чтобы добиться единообразного поведения, вы можете установить поля с учетом разницы в высоте и ширине. В настоящее время я использую следующий класс :

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

Обновление: библиотеки поддержки Android v23 переименовали fab_size sizes в:

import static android.support.design.R.dimen.design_fab_size_normal;
import static android.support.design.R.dimen.design_fab_size_mini;
Маркус Рубей
источник
Я только что реорганизовал свой проект на AndroidX и не могу создать третий конструктор из вашего примера. Как это сделать с помощью библиотек AndroidX?
Алон Шлидер
5

Ответ Маркуса сработал для меня после обновления до версии 23.1.0 и внесения некоторых корректировок в импорт (с недавним плагином gradle мы используем наше приложение R вместо R библиотеки дизайна). Вот код для 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);
        }
    }
}
Изучите OpenGL ES
источник
Я не могу использовать этот пример после рефакторинга до AndroidX - третий конструктор больше не компилируется. Вы знаете, что не так?
Алон Шлидер
3

В файле макета задайте для атрибута отметку 0 значение, так как для него требуется высота по умолчанию.

app:elevation="0dp"

Теперь в процессе работы проверьте уровень API выше 21 и при необходимости установите высоту.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    fabBtn.setElevation(10.0f);
}
Викрам
источник
Если вы используете Compat библиотеки использовать setCompatElevation . И лучше будет использовать дипы вместо пикселей напрямую.
ingkevin