Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1dc5f927 authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Fix issue #8311263: Corrupted UI across all tabs in People app

The problem is that there are other configurations that the
action bar can be in that ActionBarOverlayLayout didn't account
for -- such as here, the nav part visible but the rest hidden.

Fixing this was non-trivial because it means that to correctly
implement fitSystemWindows() we need to in these cases take the
actual measured height of the action bar for positioning the
content view...  but that is not yet available, since
fitSystemWindows() must run before layout.

To solve this, ActionBarOverlayLayout now inherits directly from
ViewGroup and implements its own custom layout.  In its measure
pass it does all of the fitSystemWindows() work that is dependent
on the measured sizes of the action bar child views after those
are measured and applies them to the content view before it is
measured.

Change-Id: Ie327075d502e9c348aa80b0968c6b0403478301e
parent 7be6d4c4
Loading
Loading
Loading
Loading
+172 −33
Original line number Diff line number Diff line
@@ -16,33 +16,42 @@

package com.android.internal.widget;

import android.view.ViewGroup;
import com.android.internal.app.ActionBarImpl;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;

/**
 * Special layout for the containing of an overlay action bar (and its
 * content) to correctly handle fitting system windows when the content
 * has request that its layout ignore them.
 */
public class ActionBarOverlayLayout extends FrameLayout {
public class ActionBarOverlayLayout extends ViewGroup {
    private int mActionBarHeight;
    private ActionBarImpl mActionBar;
    private int mWindowVisibility = View.VISIBLE;

    // The main UI elements that we handle the layout of.
    private View mContent;
    private View mActionBarTop;
    private View mActionBarBottom;

    // Some interior UI elements.
    private ActionBarContainer mContainerView;
    private ActionBarView mActionView;
    private View mActionBarBottom;

    private boolean mOverlayMode;
    private int mLastSystemUiVisibility;
    private final Rect mLocalInsets = new Rect();
    private final Rect mBaseContentInsets = new Rect();
    private final Rect mLastBaseContentInsets = new Rect();
    private final Rect mContentInsets = new Rect();
    private final Rect mBaseInnerInsets = new Rect();
    private final Rect mInnerInsets = new Rect();
    private final Rect mLastInnerInsets = new Rect();

    static final int[] mActionBarSizeAttr = new int [] {
            com.android.internal.R.attr.actionBarSize
@@ -139,7 +148,7 @@ public class ActionBarOverlayLayout extends FrameLayout {
    private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
            boolean bottom, boolean right) {
        boolean changed = false;
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)view.getLayoutParams();
        LayoutParams lp = (LayoutParams)view.getLayoutParams();
        if (left && lp.leftMargin != insets.left) {
            changed = true;
            lp.leftMargin = insets.left;
@@ -168,52 +177,163 @@ public class ActionBarOverlayLayout extends FrameLayout {

        // The top and bottom action bars are always within the content area.
        boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true);
        if (mActionBarBottom != null) {
        changed |= applyInsets(mActionBarBottom, insets, true, false, true, true);

        mBaseInnerInsets.set(insets);
        computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
        if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
            changed = true;
            mLastBaseContentInsets.set(mBaseContentInsets);
        }

        int topSpace = 0;
        if (stable || mActionBarTop.getVisibility() == VISIBLE) {
            // This is the space needed on top of the window for the action bar.
            topSpace = mActionBarHeight;
        if (changed) {
            requestLayout();
        }

        // We don't do any more at this point.  To correctly compute the content/inner
        // insets in all cases, we need to know the measured size of the various action
        // bar elements.  fitSystemWindows() happens before the measure pass, so we can't
        // do that here.  Instead we will take this up in onMeasure().
        return true;
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        int topInset = 0;
        int bottomInset = 0;

        measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0);
        LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams();
        maxWidth = Math.max(maxWidth,
                mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
        maxHeight = Math.max(maxHeight,
                mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
        childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState());

        measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
        lp = (LayoutParams) mActionBarBottom.getLayoutParams();
        maxWidth = Math.max(maxWidth,
                mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
        maxHeight = Math.max(maxHeight,
                mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
        childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState());

        final int vis = getWindowSystemUiVisibility();
        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;

        if (stable) {
            // This is the standard space needed for the action bar.  For stable measurement,
            // we can't depend on the size currently reported by it -- this must remain constant.
            topInset = mActionBarHeight;
            if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
                View tabs = mContainerView.getTabContainer();
            if (tabs != null && (stable || tabs.getVisibility() == VISIBLE)) {
                if (tabs != null) {
                    // If tabs are not embedded, increase space on top to account for them.
                topSpace += mActionBarHeight;
                    topInset += mActionBarHeight;
                }
            }
        } else if (mActionBarTop.getVisibility() == VISIBLE) {
            // This is the space needed on top of the window for all of the action bar
            // and tabs.
            topInset = mActionBarTop.getMeasuredHeight();
        }

        int bottomSpace = 0;
        if (mActionView.isSplitActionBar()) {
            if ((mActionBarBottom != null
                    && (stable || mActionBarBottom.getVisibility() == VISIBLE))) {
            // If action bar is split, adjust bottom insets for it.
                bottomSpace = mActionBarHeight;
            if (stable) {
                bottomInset = mActionBarHeight;
            } else {
                bottomInset = mActionBarBottom.getMeasuredHeight();
            }
        }

        // If the window has not requested system UI layout flags, we need to
        // make sure its content is not being covered by system UI...  though it
        // will still be covered by the action bar since they have requested it to
        // will still be covered by the action bar if they have requested it to
        // overlay.
        boolean res = computeFitSystemWindows(insets, mLocalInsets);
        mContentInsets.set(mBaseContentInsets);
        mInnerInsets.set(mBaseInnerInsets);
        if (!mOverlayMode && !stable) {
            mLocalInsets.top += topSpace;
            mLocalInsets.bottom += bottomSpace;
            mContentInsets.top += topInset;
            mContentInsets.bottom += bottomInset;
        } else {
            insets.top += topSpace;
            insets.bottom += bottomSpace;
            mInnerInsets.top += topInset;
            mInnerInsets.bottom += bottomInset;
        }
        changed |= applyInsets(mContent, mLocalInsets, true, true, true, true);
        applyInsets(mContent, mContentInsets, true, true, true, true);

        if (changed) {
            requestLayout();
        if (!mLastInnerInsets.equals(mInnerInsets)) {
            // If the inner insets have changed, we need to dispatch this down to
            // the app's fitSystemWindows().  We do this before measuring the content
            // view to keep the same semantics as the normal fitSystemWindows() call.
            mLastInnerInsets.set(mInnerInsets);
            super.fitSystemWindows(mInnerInsets);
        }

        super.fitSystemWindows(insets);
        return true;
        measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
        lp = (LayoutParams) mContent.getLayoutParams();
        maxWidth = Math.max(maxWidth,
                mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
        maxHeight = Math.max(maxHeight,
                mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
        childState = combineMeasuredStates(childState, mContent.getMeasuredState());

        // Account for padding too
        maxWidth += getPaddingLeft() + getPaddingRight();
        maxHeight += getPaddingTop() + getPaddingBottom();

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeft();
        final int parentRight = right - left - getPaddingRight();

        final int parentTop = getPaddingTop();
        final int parentBottom = bottom - top - getPaddingBottom();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft = parentLeft + lp.leftMargin;
                int childTop;
                if (child == mActionBarBottom) {
                    childTop = parentBottom - height - lp.bottomMargin;
                } else {
                    childTop = parentTop + lp.topMargin;
                }

                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

    void pullChildren() {
@@ -226,4 +346,23 @@ public class ActionBarOverlayLayout extends FrameLayout {
            mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
        }
    }


    public static class LayoutParams extends MarginLayoutParams {
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }
    }
}
+1 −3
Original line number Diff line number Diff line
@@ -29,8 +29,7 @@ This is an optimized layout for a screen with the Action Bar enabled.
        android:layout_height="match_parent" />
    <LinearLayout android:id="@+id/top_action_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:layout_gravity="top">
                  android:layout_height="wrap_content">
        <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
@@ -57,7 +56,6 @@ This is an optimized layout for a screen with the Action Bar enabled.
    <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:layout_gravity="bottom"
                  style="?android:attr/actionBarSplitStyle"
                  android:visibility="gone"
                  android:gravity="center"/>