it-swarm.com.ru

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

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

Затем мне пришлось заменить <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>

фрагмент.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>
40
Actine

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

Поэтому в случае <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, если вы не сильно к этому относитесь.

67
ianhanniballake

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

            }
        });
    }

}
11
Marius

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

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

3
Actine

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

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

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

<FrameLayout
    Android:id="@+id/fragment_Host"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fitsSystemWindows="true" />

Решение:

Расширьте класс FrameLayout и override функцию 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" />

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

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

1
Ryan Amaral

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

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

1
BladeCoder