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

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

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

Я полагаю, что может быть какая-то разница между тем, как <fragment> получает завышенное значение в макете действия по сравнению с самим собой. Я googled и нашел некоторые решения для 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">
...

Он работал, когда 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">

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

Ответ 1

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

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

DrawerLayout
  CoordinatorLayout
    AppBarLayout
    ...
  NavigationView

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

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

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

DrawerLayout
  FrameLayout
    CoordinatorLayout
      AppBarLayout
      ...
  NavigationView

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

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

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

Ответ 2

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

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

Ответ 3

Моя проблема была похожа на вашу: у меня есть Нижняя панель навигации, которая заменяет фрагменты контента. Теперь некоторые фрагменты хотят нарисовать строку состояния (с 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) {

            }
        });
    }

}

Ответ 4

Я создал этот последний год для решения этой проблемы: https://gist.github.com/cbeyls/ab6903e103475bd4d51b

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