У меня есть SingleFramgnetActivity, цель которого - только удерживать и заменять фрагменты внутри него.

Макет выглядит так:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".SingleFragmentActivity"
    >

    <include layout="@layout/toolbar"/>

    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>

Я заменяю Fragments внутри FrameLayout. Когда я устанавливаю для fitsSystemWindows значение true в макете Fragment, он не отвечает. На самом деле он работает только при создании Activity, но как только я заменяю Fragment внутри FrameLayout, параметр fitsSystemWindows игнорируется, и макет находится под строкой состояния и Панель навигации.

Я нашел решение с настраиваемым FrameLayout, в котором используются устаревшие методы. , но по какой-то причине у меня это не работает (тот же результат, что и с обычным FrameLayout), и мне также не нравится идея использовать устаревшие методы.

21
Sandak 3 Сен 2016 в 16:52

4 ответа

Лучший ответ

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

@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
    int childCount = getChildCount();
    for (int index = 0; index < childCount; index++)
        getChildAt(index).dispatchApplyWindowInsets(insets); // let children know about WindowInsets

    return insets;
}

Вы можете посмотреть мой ответ, в котором подробно объясняется, как это работает, а также как использовать ViewCompat.setOnApplyWindowInsetsListener API.

10
Community 23 Май 2017 в 12:02

Вы также можете создать собственный WindowInsetsFrameLayout и использовать OnHierarchyChangedListener, чтобы снова запросить применение вставок:

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

        }
    });
}

Ознакомьтесь с подробным ответом: https://stackoverflow.com/a/47349880/3979479

3
Marius 17 Ноя 2017 в 11:44

А) вы можете использовать CoordinatorLayout в качестве корневого представления внутри фрагмента

Или

Б) вы можете создать собственный линейный макет, который будет вызывать requestApplyInsets и использовать его в качестве корневого представления внутри фрагмента

class WindowInsetsLinearLayout : LinearLayout {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        ViewCompat.requestApplyInsets(this)
    }
}

А затем внутри фрагмента ловить вставки

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    ViewCompat.setOnApplyWindowInsetsListener(root_layout) { _, insets ->
        //appbar.setPadding(insets.systemWindowInsetLeft, insets.systemWindowInsetTop, 0, 0)
        insets.consumeSystemWindowInsets()
    }
}
1
John 4 Июл 2019 в 08:42

Я думаю, проблема связана с вызовом onApplyWindowInsets до того, как будет прикреплена иерархия представления фрагментов. Эффективное решение - получить следующее переопределение для представления где-нибудь в иерархии представлений фрагмента.

  @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        // force window insets to get re-applied if we're being attached by a fragment.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
            requestApplyInsets();
        } else {
            //noinspection deprecation
            requestFitSystemWindows();
        }
    }

Далее следует полное решение (если вам не нужно использовать CoordinatorLayout). Убедитесь, что fitSystemWindows=true НИКОГДА не появляется в представлениях выше в иерархии. Может больше нигде. Я подозреваю (но не уверен), что consumeSystemWindowInsets съедает вставки для представлений, которые находятся дальше в порядке компоновки дерева представлений.

package com.twoplay.xcontrols;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;

public class FitSystemWindowsLayout extends FrameLayout {
    private boolean mFit = true;

    public FitSystemWindowsLayout(final Context context) {
        super(context);
        init();
    }

    public FitSystemWindowsLayout(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public FitSystemWindowsLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setFitsSystemWindows(true);
    }

    public boolean isFit() {
        return mFit;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
            requestApplyInsets();
        } else {
            //noinspection deprecation
            requestFitSystemWindows();
        }

    }

    public void setFit(final boolean fit) {
        if (mFit == fit) {
            return;
        }

        mFit = fit;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
            requestApplyInsets();
        } else {
            //noinspection deprecation
            requestFitSystemWindows();
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    protected boolean fitSystemWindows(final Rect insets) {
        if (mFit) {
            setPadding(
                    insets.left,
                    insets.top,
                    insets.right,
                    insets.bottom
            );
            return true;
        } else {
            setPadding(0, 0, 0, 0);
            return false;
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
    @Override
    public WindowInsets onApplyWindowInsets(final WindowInsets insets) {
        if (mFit) {
            setPadding(
                    insets.getSystemWindowInsetLeft(),
                    insets.getSystemWindowInsetTop(),
                    insets.getSystemWindowInsetRight(),
                    insets.getSystemWindowInsetBottom()
            );
            return insets.consumeSystemWindowInsets();
        } else {
            setPadding(0, 0, 0, 0);
            return insets;
        }
    }
}

Подозрение, а не факт: только одно представление во всей иерархии получает шанс съесть вставки окна, ЕСЛИ у вас нет CoordinatorLayout в иерархии, что позволяет нескольким прямым дочерним элементам иметь FitSystemWindow=true. Если у вас есть CoordinatorLayout, ваш опыт может отличаться.

Вся эта функция в Android кажется ужасным беспорядком.

1
Robin Davies 10 Июн 2018 в 14:57