Android fitsSystemWindows not working when replacing fragments

前端 未结 4 393
说谎
说谎 2021-01-01 13:30

I have SingleFramgnetActivity whose purpose is only to hold and replace fragments inside it.

layout looks like this:



        
相关标签:
4条回答
  • 2021-01-01 13:58

    a) you can use CoordinatorLayout as your root view inside fragment

    or

    b) you can create custom linear layout what will call requestApplyInsets and use it as your root view inside fragment

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

    and then inside fragment you can catch applying insets

    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()
        }
    }
    
    0 讨论(0)
  • 2021-01-01 14:02

    Your FrameLayout is not aware of window inset sizes, because it's parent - LinearLayout hasn't dispatched him any. As a workaround, you can subclass LinearLayout and pass insets to children on your own:

    @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;
    }
    

    You can have a look to my this answer, which will explain detailed how this works, and also how to use ViewCompat.setOnApplyWindowInsetsListener API.

    0 讨论(0)
  • 2021-01-01 14:03

    I think the problem revolves around onApplyWindowInsets getting called before the fragment view hierarchy gets attached. An effective solution is to get the following override on a view somewhere in the view hierarchy of the fragment.

      @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();
            }
        }
    

    A complete solution (if you don't have to use CoordinatorLayout) follows. Make sure fitSystemWindows=true does not appear ANYWHERE in views higher in the heirarchy. Maybe not anywhere else. I suspect (but am not sure) that consumeSystemWindowInsets eats the insets for views that are further on in the layout order of the view tree.

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

    Suspicion, not fact: that only one view in the entire hierarchy gets a chance to eat the window insets, UNLESS you have CoordinatorLayout in the hierarchy, which allows more than one direct child to have FitSystemWindow=true. If you do have a CoordinatorLayout, your mileage may vary.

    This entire feature in Android seems to be an unholy mess.

    0 讨论(0)
  • 2021-01-01 14:08

    You could also build a custom WindowInsetsFrameLayout and use a OnHierarchyChangedListener to request applying the insets again:

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

    Check out this detailed answer: https://stackoverflow.com/a/47349880/3979479

    0 讨论(0)
提交回复
热议问题