Android 4.4 — 半透明状态/导航栏 — fitSystemWindows/clipToPadding 不能通过片段事务工作

Posted

技术标签:

【中文标题】Android 4.4 — 半透明状态/导航栏 — fitSystemWindows/clipToPadding 不能通过片段事务工作【英文标题】:Android 4.4 — Translucent status/navigation bars — fitsSystemWindows/clipToPadding don't work through fragment transactions 【发布时间】:2014-01-16 07:52:20 【问题描述】:

当使用来自新的 android 4.4 KitKat API 的半透明状态栏和导航栏时,将 fitsSystemWindows="true"clipToPadding="false" 设置为 ListView 最初可以工作。 fitsSystemWindows="true" 将列表保留在操作栏下方和导航栏上方,clipToPadding="false" 允许列表在透明导航栏下方滚动,并使列表中的最后一项向上滚动到足以通过导航栏。

但是,当您通过FragmentTransaction 将内容替换为另一个Fragment 时,fitsSystemWindows 的效果会消失,并且片段会位于操作栏和导航栏下方。

我在这里有一个演示源代码的代码库以及一个可下载的 APK 作为示例:https://github.com/afollestad/kitkat-transparency-demo。要查看我在说什么,请从运行 KitKat 的设备打开演示应用程序,点击列表中的一个项目(这将打开另一个活动),然后在打开的新活动中点击一个项目。替换内容的片段位于操作栏下方,并且 clipToPadding 无法正常工作(当您一直向下滚动时,导航栏会覆盖列表中的最后一项)。

有什么想法吗?需要任何澄清吗?我发布了为我的雇主开发的个人应用程序的前后截图。

【问题讨论】:

【参考方案1】:

我昨天也遇到了同样的问题。经过深思熟虑,我找到了一个优雅的解决这个问题的方法。

首先,我在ViewParent 上看到了requestFitSystemWindows() 方法,我尝试在片段的onActivityCreated() 中调用它(在片段附加到视图层次结构之后),但遗憾的是它没有效果。我想看看如何使用该方法的具体示例。

然后我找到了一个巧妙的解决方法:我创建了一个自定义 FitsSystemWindowsFrameLayout,在我的布局中用作 片段容器,作为经典 FrameLayout 的直接替代品。它的作用是在系统调用fitSystemWindows() 时记住窗口插入,然后在添加/附加片段后立即将调用再次传播到其子布局(片段布局)。

这是完整的代码:

public class FitsSystemWindowsFrameLayout extends FrameLayout 

    private Rect windowInsets = new Rect();
    private Rect tempInsets = new Rect();

    public FitsSystemWindowsFrameLayout(Context context) 
        super(context);
    

    public FitsSystemWindowsFrameLayout(Context context, AttributeSet attrs) 
        super(context, attrs);
    

    public FitsSystemWindowsFrameLayout(Context context, AttributeSet attrs, int defStyle) 
        super(context, attrs, defStyle);
    

    @Override
    protected boolean fitSystemWindows(Rect insets) 
        windowInsets.set(insets);
        super.fitSystemWindows(insets);
        return false;
    

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) 
        super.addView(child, index, params);
        tempInsets.set(windowInsets);
        super.fitSystemWindows(tempInsets);
    

我认为这比试图通过访问可能随时间变化的隐藏系统属性来确定 UI 元素大小然后手动对元素应用填充来确定 UI 元素大小的 hack 更简单、更健壮。

【讨论】:

这是否需要您仍然在布局中设置 fitSystemWindows 属性? 是的,您需要将 fitSystemWindows 属性放在您选择的视图的片段布局中。关键是允许在同一个片段中混合占据全屏空间的视图和适合窗口的视图,否则如果您希望整个片段适合窗口,则只需在片段容器上设置 fitSystemWindows。 太棒了,这正是我需要的。尽管 fitSystemWindows 现在已被弃用,但您遇到过问题吗? 一个目标 API 19 的应用程序在 Lollipop 下使用这种技术运行良好;我没有使用目标 API 21 进行测试。当我有时间研究新的 Lollipop 行为时,我会更新上面的代码。 我将创建此布局的应用程序更新为 Lollipop,据我所知,即使该方法已被弃用,它仍然可以正常工作。【参考方案2】:

我通过使用设置半透明状态栏颜色的库解决了这个问题。

SystemBarTint 的 SystemBarConfig 类(如此处所示 https://github.com/jgilfelt/SystemBarTint#systembarconfig)可让您获得我设置为每个片段中列表的填充的插图,以及在列表中使用 clipToPadding="false"

我有我在这篇文章中所做的详细信息:http://mindofaandroiddev.wordpress.com/2013/12/28/making-the-status-bar-and-navigation-bar-transparent-with-a-listview-on-android-4-4-kitkat/

【讨论】:

你不知道你帮了我多少。在过去的 3 天里,我一直在尝试解决这个问题,但从未搜索过它,因为我认为这可能是我的实现在某处不可靠。最后决定搜索一下,找到了你。你给了我新的生命。谢谢 小心。 SustemBarConfig 类不适用于某些设备(例如 OnePlus One) @PasqualeAnatriello 是的,我注意到在一些生产应用程序(例如橱柜)中 检查我发布的答案。 100% 的时间为我工作【参考方案3】:

好的,这太奇怪了。我最近遇到了同样的问题,除了我的涉及软键盘。它最初有效,但如果我添加片段事务,android:fitsSystemWindows="true" 将不再有效。我在这里尝试了所有解决方案,但没有一个对我有用。

这是我的问题:

它不是重新调整我的视图大小,而是推高我的视图,这就是问题所在。

但是,我很幸运,偶然发现了一个对我有用的答案!

原来是这样:

首先,我的应用主题是:Theme.AppCompat.Light.NoActionBar(如果相关,也许是,android 很奇怪)。

Maurycy 在这里指出了一些非常有趣的东西,所以我想测试一下他所说的是不是真的。在我的情况下,他所说的也是正确的......除非您将此属性添加到应用程序的 android 清单中的活动中:

添加后:

android:windowSoftInputMode="adjustResize" 

对于您的活动,片段事务后不再忽略android:fitsSystemWindows="true"

但是,我更喜欢你在 Fragment 的根布局上调用 android:fitsSystemWindows="true" 而不是。发生此问题的最大地方之一是如果您有 EditText 或 ListView。如果您像我一样陷入这种困境,请在根布局的子级中设置android:fitsSystemWindows="true",如下所示:

是的,此解决方案适用于所有 Lollipop 和 pre-lollipop 设备。

这是证据:

它会重新调整大小而不是向上推动布局。 所以希望我帮助了和我在同一条船上的人。

非常感谢大家!

【讨论】:

你的解释有些误导。默认情况下,键盘会在所有 Android 版本中向上推布局。不同的是,在你的左边截图中它是 android 4.1,而在 Android 4.1 中,系统栏总是黑色的,不能设置为透明,fitsSystemWindows 没有效果。您应该将 Lollipop 与 KitKat 进行比较。【参考方案4】:

提醒一些遇到此问题的人。

使用 fitSystemWindows 方法完成大量工作的关键信息:

此函数沿层次结构向下遍历是深度优先的。相同 内容插入对象沿层次结构向下传播,因此任何更改 以下所有视图(包括可能 因为这是深度优先遍历)。 第一个返回 true 的视图将中止整个遍历。

因此,如果您有任何其他片段的内容视图的 fitSystemWindows 设置为 true,则该标志可能会被忽略。如果可能,我会考虑让您的片段容器包含 fitSystemWindows 标志。否则手动添加填充。

【讨论】:

【参考方案5】:

我也一直在为此苦苦挣扎。 我在这里看到了所有的回复。不幸的是,他们都没有 100% 地解决我的问题。 SystemBarConfig 并不总是工作,因为它无法检测到某些设备上的栏。 我查看了源代码,找到了 insets 存储在窗口内的位置。

        Rect insets = new Rect();
        Window window = getActivity().getWindow();
        try 
            Class clazz = Class.forName("com.android.internal.policy.impl.PhoneWindow");
            Field field = clazz.getDeclaredField("mDecor");
            field.setAccessible(true);
            Object decorView = field.get(window);
            Field insetsField = decorView.getClass().getDeclaredField("mFrameOffsets");
            insetsField.setAccessible(true);
            insets = (Rect) insetsField.get(decorView);
         catch (ClassNotFoundException e) 
            e.printStackTrace();
         catch (NoSuchFieldException e) 
            e.printStackTrace();
         catch (IllegalAccessException e) 
            e.printStackTrace();
        

这是获取它们的方法。 显然在 Android L 中会有一个很好的方法来获取这些插图,但同时这可能是一个很好的解决方案。

【讨论】:

如果您查看我的回答,我将展示一种方法,您可以在不使用反射的情况下捕获插图。【参考方案6】:

我遇到了同样的问题。当我更换片段时。 “fitsSystemWindows”不起作用。

我通过添加到您的片段的代码修复了

@Override
public void onViewCreated(final View view, Bundle savedInstanceState) 
    super.onViewCreated(view, savedInstanceState);
    AndroidUtil.runOnUIThread(new Runnable() 
        @Override
        public void run() 
            ((ViewGroup) getView().getParent()).setFitsSystemWindows(true);
        
    );

【讨论】:

【参考方案7】:

结合@BladeCoder 的回答,我创建了 FittedFrameLayout 类,它做了两件事:

它不会为自己添加填充

它扫描其容器内的所有视图并为它们添加填充,但在最低层停止(如果发现 fitsystemwindows 标志,它不会更深入地扫描子视图,但仍处于相同深度或更低)。

public class FittedFrameLayout extends FrameLayout 
    private Rect insets = new Rect();

    public FittedFrameLayout(Context context) 
        super(context);
    

    public FittedFrameLayout(Context context, AttributeSet attrs) 
        super(context, attrs);
    

    public FittedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
    

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public FittedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) 
        super(context, attrs, defStyleAttr, defStyleRes);
    

    protected void setChildPadding(View view, Rect insets)
        if(!(view instanceof ViewGroup))
            return;

        ViewGroup parent = (ViewGroup) view;
        if (parent instanceof FittedFrameLayout)
            ((FittedFrameLayout)parent).fitSystemWindows(insets);
        else
            if( ViewCompat.getFitsSystemWindows(parent))
                parent.setPadding(insets.left,insets.top,insets.right,insets.bottom);
            else
                for (int i = 0, z = parent.getChildCount(); i < z; i++)
                    setChildPadding(parent.getChildAt(i), insets);
            
        
    

    @Override
    protected boolean fitSystemWindows(Rect insets) 
        this.insets = insets;
        for (int i = 0, z = getChildCount(); i < z; i++)
            setChildPadding(getChildAt(i), insets);

        return true;
    

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) 
        super.addView(child, index, params);
        setChildPadding(child, insets);
    

【讨论】:

【参考方案8】:

我已经在 4.4 中解决了这个问题

if(test)
    Log.d(TAG, "fit true ");
    relativeLayout.setFitsSystemWindows(true);
    relativeLayout.requestFitSystemWindows();
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
else 
    Log.d(TAG, "fit false");
    relativeLayout.setFitsSystemWindows(false);
    relativeLayout.requestFitSystemWindows();
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

【讨论】:

以上是关于Android 4.4 — 半透明状态/导航栏 — fitSystemWindows/clipToPadding 不能通过片段事务工作的主要内容,如果未能解决你的问题,请参考以下文章

Android 沉浸式/透明式状态栏、导航栏

利用 Android 4.4 KitKat 中的半透明状态栏

利用 Android 4.4 KitKat 中的半透明状态栏

Flutter沉浸式状态栏/AppBar导航栏/仿咸鱼底部凸起导航

Android 4.4 沉浸式透明状态栏与导航栏

Flutter沉浸式透明状态栏|flutter自定义凸起BottomAppBar导航