【问题标题】:Android fitsSystemWindows not working when replacing fragments替换片段时Android适合SystemWindows不起作用
【发布时间】:2016-09-03 13:52:55
【问题描述】:

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

我正在替换 FrameLayout 中的 Fragments。当我在 Fragment 布局上将 fitsSystemWindows 设置为 true 时,它​​没有响应。实际上它只有在创建Activity 时才有效,但是一旦我替换了FrameLayout 中的Fragment,fitsSystemWindows 参数将被忽略,并且布局位于状态栏和导航栏下方。

我发现一些 solution 带有自定义 FrameLayout,它使用了已弃用的方法,但由于某种原因,它对我不起作用(与普通 FrameLayout 的结果相同),我也不喜欢使用已弃用方法的想法。

【问题讨论】:

  • 你知道 fitSystemWindow 的作用吗?
  • 我希望我做对了,基本上当它设置为true时,视图及其所有子视图不应该作为状态栏或导航栏显示在系统窗口下。设置为 false 时,应显示在系统窗口下。所以基本上我可以设置我想在状态栏和导航栏(fitsSystemWindow = true)和状态栏和导航栏下的背景图像(fitsSystemWindow = false)之间渲染整个Fragment布局。在不替换碎片之前,我可以完全按照自己的意愿制作。
  • 我遇到了同样的问题。我最终制作了一个 FrameLayout 来保存窗口插图并将它们重新分配给新的孩子。我将代码放在Github gist 中。让我知道这是否适合你!

标签: android android-layout android-fragments


【解决方案1】:

您的 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;
}

您可以查看my this 的答案,其中将详细解释其工作原理,以及如何使用ViewCompat.setOnApplyWindowInsetsListener API。

【讨论】:

  • 在支持低于 20 的 API 时始终使用ViewCompat.setOnApplyWindowInsetsListener,因为stackoverflow.com/questions/35028395/…。
  • @azizbekian 你的解释很有用,但它仍然不能帮助我解决更改片段的问题。 fitSystemWindows 在第一次打开片段时在片段布局中工作,但如果片段替换另一个片段则被忽略。我说的是非根视图第二级上的 fitSystemWindows。在第一级它有效。活动和片段中层次结构中的所有视图都是协调器布局或实现您答案中的方法。
  • @lobzik,检查您的视图层次结构并找出父视图,该视图不会将插图分派给其子视图。您可以使用 ViewCompat.setOnApplyWindowInsetsListener 并检查哪个 ViewGroup 恰好返回 0 作为插入大小。
  • @azizbekian 如果在替换片段之前它工作正常,它怎么可能是一个不调度插图的视图?正如我从调试中看到的那样,当片段替换另一个片段时,片段中的根布局没有收到 onApplyWindowInsets 。但它在第一次打开时确实会收到此调用。不知道怎么解决
  • 这似乎不适用于新的Navigation Component。使用 BottomNavigation 和导航控制器,如果我重新选择 BottomNavigation 中的任何项目,fitsSystemWindows 将被忽略。有什么解决办法吗?供参考here 是一个演示应用程序
【解决方案2】:

您还可以构建自定义 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

【讨论】:

  • 对我来说,我也必须覆盖“onApplyWindowInsets”
【解决方案3】:

我认为问题围绕着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 中的整个功能似乎一团糟。

【讨论】:

  • 我应该补充一点,CoordinatorLayout 缓存了它收到的最后一个WindowInsets,如果收到后续的WindowInsets,例如通过View#requestApplyInsets(),则“新”插入将是@987654333 @ 到缓存的那个,所以它不会将它分派给它的子 Behavior 实例,所以层次结构中它下面的任何东西都不会被通知。在这种情况下,您需要手动发送。
【解决方案4】:

a) 您可以使用 CoordinatorLayout 作为片段内的根视图

或

b) 您可以创建自定义线性布局,调用 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()
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-06
    • 1970-01-01
    • 2018-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-18
    相关资源
    最近更新 更多