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