Эффект 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 в первую очередь. Когда вы устанавливаете его в режиме просмотра, это в основном означает: "Поместите этот вид и все его дочерние элементы ниже строки состояния и над навигационной панелью". Нет смысла устанавливать этот атрибут в верхнем контейнере.