Эффект fitsSystemWindows для фрагментов, добавленных через FragmentTransaction

У меня есть блок действий с навигационной панелью и фрагментом с полным кровотечением (с изображением вверху, которое должно появиться за полупрозрачной системной панелью на Lollipop). В то время как у меня было временное решение, где фрагмент был раздут просто <fragment> тег в XML деятельности, это выглядело хорошо.

Тогда мне пришлось заменить <fragment> с <FrameLayout> и выполнять транзакции фрагмента, и теперь фрагмент больше не появляется за системной панелью, несмотря на fitsSystemWindows установлен в true по всей необходимой иерархии.

Я полагаю, что между тем, как <fragment> раздувается в макете Activity против. Я гуглил и нашел некоторые решения для KitKat, но ни один из них не работал для меня (Lollipop).

activity.xml

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                        xmlns:app="http://schemas.android.com/apk/res-auto"
                                        android:id="@+id/drawer_layout"
                                        android:layout_height="match_parent"
                                        android:layout_width="match_parent"
                                        android:fitsSystemWindows="true">

    <FrameLayout
            android:id="@+id/fragment_host"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true">

    </FrameLayout>

    <android.support.design.widget.NavigationView
            android:id="@+id/nav_view"
            android:layout_height="match_parent"
            android:layout_width="wrap_content"
            android:layout_gravity="start"
            android:fitsSystemWindows="true"/>

</android.support.v4.widget.DrawerLayout>

fragment.xml

<android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="224dp"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
...

Это сработало, когда активность.xml была такой:

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                        xmlns:app="http://schemas.android.com/apk/res-auto"
                                        android:id="@+id/drawer_layout"
                                        android:layout_height="match_parent"
                                        android:layout_width="match_parent"
                                        android:fitsSystemWindows="true">

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:id="@+id/fragment"
              android:name="com.actinarium.random.ui.home.HomeCardsFragment"
              tools:layout="@layout/fragment_home"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>

    <android.support.design.widget.NavigationView
            android:id="@+id/nav_view"
            android:layout_height="match_parent"
            android:layout_width="wrap_content"
            android:layout_gravity="start"
            android:fitsSystemWindows="true"/>

</android.support.v4.widget.DrawerLayout>

5 ответов

Решение

Когда вы используете <fragment> макет вернулся в ваш фрагмент onCreateView непосредственно прикреплен вместо <fragment> тег (вы никогда не увидите <fragment> тег, если вы посмотрите на иерархию просмотра.

Поэтому в <fragment> случай, у вас есть

DrawerLayout
  CoordinatorLayout
    AppBarLayout
    ...
  NavigationView

Аналогично тому, как работает cheesesquare. Это работает, потому что, как объясняется в этом сообщении в блоге, DrawerLayout а также CoordinatorLayout оба имеют разные правила о том, как fitsSystemWindows применяется к ним - они оба используют его для вставки своих дочерних представлений, но также вызывают dispatchApplyWindowInsets() для каждого дочернего элемента, предоставляя им доступ к fitsSystemWindows="true" имущество.

Это отличие от поведения по умолчанию для таких макетов, как FrameLayout где, когда вы используете fitsSystemWindows="true" is потребляет все вставки, слепо применяя отступы, не информируя дочерние представления (это часть глубины первой статьи в блоге).

Поэтому, когда вы заменяете <fragment> пометить с FrameLayout и FragmentTransactions, ваша иерархия представления становится:

DrawerLayout
  FrameLayout
    CoordinatorLayout
      AppBarLayout
      ...
  NavigationView

как вид фрагмента вставляется в FrameLayout, Эта точка зрения ничего не знает о прохождении fitsSystemWindows для детских взглядов, так что ваш CoordinatorLayout никогда не увидит этот флаг или не выполнит свое собственное поведение.

Устранить проблему на самом деле довольно просто: замените FrameLayout с другим CoordinatorLayout, Это обеспечивает fitsSystemWindows="true" попадает на недавно раздутый CoordinatorLayout из фрагмента.

Альтернативными и в равной степени действительными решениями было бы создание собственного подкласса FrameLayout и переопределите onApplyWindowInsets() для отправки каждому дочернему элементу ( в вашем случае только одному) или используйте метод ViewCompat.setOnApplyWindowInsetsListener() для перехвата вызова в коде и отправки оттуда (подкласс не требуется). Меньше кода, как правило, проще всего поддерживать, поэтому я не обязательно рекомендую проходить эти маршруты через CoordinatorLayout решение, если вы не чувствуете сильно об этом.

Моя проблема была похожа на вашу: у меня есть навигация по нижней панели, которая заменяет фрагменты контента. Теперь некоторые фрагменты хотят нарисовать поверх строки состояния (с CoordinatorLayout, AppBarLayout другие нет (с ConstraintLayout, Toolbar).

ConstraintLayout
  FrameLayout
    [the ViewGroup of your choice]
  BottomNavigationView

Предложение ianhanniballake добавить еще один CoordinatorLayout слой не то, что я хочу, поэтому я создал кастом FrameLayout который обрабатывает вставки (как он предложил), и через некоторое время я наткнулся на это решение, которое на самом деле не так много кода:

activity_main.xml

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.app.WindowInsetsFrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</android.support.constraint.ConstraintLayout>

WindowInsetsFrameLayout.java

/**
 * FrameLayout which takes care of applying the window insets to child views.
 */
public class WindowInsetsFrameLayout extends FrameLayout {

    public WindowInsetsFrameLayout(Context context) {
        this(context, null);
    }

    public WindowInsetsFrameLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WindowInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // Look for replaced fragments and apply the insets again.
        setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
            @Override
            public void onChildViewAdded(View parent, View child) {
                requestApplyInsets();
            }

            @Override
            public void onChildViewRemoved(View parent, View child) {

            }
        });
    }

}

Хорошо, после того, как несколько человек указали, что fitsSystemWindows работает по-разному, и его не следует использовать в каждом представлении вниз по иерархии, я продолжал экспериментировать и удалять свойство из разных представлений.

Я получил ожидаемое состояние после удаления fitsSystemWindows от каждого узла в activity.xml знак равно

Другой подход, написанный на Kotlin,

Эта проблема:

FrameLayout вы используете не размножается fitsSystemWindows="true" своим детям:

<FrameLayout
    android:id="@+id/fragment_host"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true" />

Решение:

простираться FrameLayout Класс и переопределить функцию onApplyWindowInsets() Размножить оконные вставки на прикрепленные фрагменты:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class BetterFrameLayout : FrameLayout {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)

    override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets {
        childCount.let {
            // propagates window insets to children's
            for (index in 0 until it) {
                getChildAt(index).dispatchApplyWindowInsets(windowInsets)
            }
        }
        return windowInsets
    }
}

Используйте этот макет в качестве контейнера фрагмента вместо стандартного FrameLayout:

<com.foo.bar.BetterFrameLayout
    android:id="@+id/fragment_host"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true" />

Дополнительно:

Если вы хотите узнать больше об этой проверке, пост блога Крис Бейнс Стать мастером по сборке окон.

Другая ужасная проблема с отправкой оконных вставок заключается в том, что первый View, использующий оконные вставки при поиске в глубину, не позволяет всем другим представлениям в иерархии видеть оконные вставки.

Следующий фрагмент кода позволяет нескольким дочерним элементам обрабатывать вставки окон. Чрезвычайно полезно, если вы пытаетесь применить вставки окон к украшениям вне NavigationView (или CoordinatorLayout). Переопределите в ViewGroup по вашему выбору.

@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
    if (!insets.isConsumed()) {

        // each child gets a fresh set of window insets
        // to consume.
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            WindowInsets freshInsets = new WindowInsets(insets);
            getChildAt(i).dispatchApplyWindowInsets(freshInsets);
        }
    }
    return insets; // and we don't.
}

Также полезно:

@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
      return insets.consume(); // consume without adding padding!
}

который позволяет простым обычным представлениям, которые являются потомками этого представления, быть размеченными без оконных вставок.

Я создал это в прошлом году, чтобы решить эту проблему: https://gist.github.com/cbeyls/ab6903e103475bd4d51b

Изменить: убедитесь, что вы понимаете, что делает fitsSystemWindows в первую очередь. Когда вы устанавливаете его в представлении, это в основном означает: "поместите это представление и все его дочерние элементы ниже строки состояния и выше панели навигации". Нет смысла устанавливать этот атрибут в верхнем контейнере.

Другие вопросы по тегам