Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.widget;
     18 
     19 import android.view.ViewGroup;
     20 import com.android.internal.app.ActionBarImpl;
     21 
     22 import android.content.Context;
     23 import android.content.res.TypedArray;
     24 import android.graphics.Rect;
     25 import android.util.AttributeSet;
     26 import android.view.View;
     27 
     28 /**
     29  * Special layout for the containing of an overlay action bar (and its
     30  * content) to correctly handle fitting system windows when the content
     31  * has request that its layout ignore them.
     32  */
     33 public class ActionBarOverlayLayout extends ViewGroup {
     34     private int mActionBarHeight;
     35     private ActionBarImpl mActionBar;
     36     private int mWindowVisibility = View.VISIBLE;
     37 
     38     // The main UI elements that we handle the layout of.
     39     private View mContent;
     40     private View mActionBarTop;
     41     private View mActionBarBottom;
     42 
     43     // Some interior UI elements.
     44     private ActionBarContainer mContainerView;
     45     private ActionBarView mActionView;
     46 
     47     private boolean mOverlayMode;
     48     private int mLastSystemUiVisibility;
     49     private final Rect mBaseContentInsets = new Rect();
     50     private final Rect mLastBaseContentInsets = new Rect();
     51     private final Rect mContentInsets = new Rect();
     52     private final Rect mBaseInnerInsets = new Rect();
     53     private final Rect mInnerInsets = new Rect();
     54     private final Rect mLastInnerInsets = new Rect();
     55 
     56     static final int[] mActionBarSizeAttr = new int [] {
     57             com.android.internal.R.attr.actionBarSize
     58     };
     59 
     60     public ActionBarOverlayLayout(Context context) {
     61         super(context);
     62         init(context);
     63     }
     64 
     65     public ActionBarOverlayLayout(Context context, AttributeSet attrs) {
     66         super(context, attrs);
     67         init(context);
     68     }
     69 
     70     private void init(Context context) {
     71         TypedArray ta = getContext().getTheme().obtainStyledAttributes(mActionBarSizeAttr);
     72         mActionBarHeight = ta.getDimensionPixelSize(0, 0);
     73         ta.recycle();
     74     }
     75 
     76     public void setActionBar(ActionBarImpl impl, boolean overlayMode) {
     77         mActionBar = impl;
     78         mOverlayMode = overlayMode;
     79         if (getWindowToken() != null) {
     80             // This is being initialized after being added to a window;
     81             // make sure to update all state now.
     82             mActionBar.setWindowVisibility(mWindowVisibility);
     83             if (mLastSystemUiVisibility != 0) {
     84                 int newVis = mLastSystemUiVisibility;
     85                 onWindowSystemUiVisibilityChanged(newVis);
     86                 requestFitSystemWindows();
     87             }
     88         }
     89     }
     90 
     91     public void setShowingForActionMode(boolean showing) {
     92         if (showing) {
     93             // Here's a fun hack: if the status bar is currently being hidden,
     94             // and the application has asked for stable content insets, then
     95             // we will end up with the action mode action bar being shown
     96             // without the status bar, but moved below where the status bar
     97             // would be.  Not nice.  Trying to have this be positioned
     98             // correctly is not easy (basically we need yet *another* content
     99             // inset from the window manager to know where to put it), so
    100             // instead we will just temporarily force the status bar to be shown.
    101             if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
    102                     | SYSTEM_UI_FLAG_LAYOUT_STABLE))
    103                     == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) {
    104                 setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
    105             }
    106         } else {
    107             setDisabledSystemUiVisibility(0);
    108         }
    109     }
    110 
    111     @Override
    112     public void onWindowSystemUiVisibilityChanged(int visible) {
    113         super.onWindowSystemUiVisibilityChanged(visible);
    114         pullChildren();
    115         final int diff = mLastSystemUiVisibility ^ visible;
    116         mLastSystemUiVisibility = visible;
    117         final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0;
    118         final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
    119         final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
    120         if (mActionBar != null) {
    121             // We want the bar to be visible if it is not being hidden,
    122             // or the app has not turned on a stable UI mode (meaning they
    123             // are performing explicit layout around the action bar).
    124             mActionBar.enableContentAnimations(!stable);
    125             if (barVisible || !stable) mActionBar.showForSystem();
    126             else mActionBar.hideForSystem();
    127         }
    128         if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
    129             if (mActionBar != null) {
    130                 requestFitSystemWindows();
    131             }
    132         }
    133     }
    134 
    135     @Override
    136     protected void onWindowVisibilityChanged(int visibility) {
    137         super.onWindowVisibilityChanged(visibility);
    138         mWindowVisibility = visibility;
    139         if (mActionBar != null) {
    140             mActionBar.setWindowVisibility(visibility);
    141         }
    142     }
    143 
    144     private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
    145             boolean bottom, boolean right) {
    146         boolean changed = false;
    147         LayoutParams lp = (LayoutParams)view.getLayoutParams();
    148         if (left && lp.leftMargin != insets.left) {
    149             changed = true;
    150             lp.leftMargin = insets.left;
    151         }
    152         if (top && lp.topMargin != insets.top) {
    153             changed = true;
    154             lp.topMargin = insets.top;
    155         }
    156         if (right && lp.rightMargin != insets.right) {
    157             changed = true;
    158             lp.rightMargin = insets.right;
    159         }
    160         if (bottom && lp.bottomMargin != insets.bottom) {
    161             changed = true;
    162             lp.bottomMargin = insets.bottom;
    163         }
    164         return changed;
    165     }
    166 
    167     @Override
    168     protected boolean fitSystemWindows(Rect insets) {
    169         pullChildren();
    170 
    171         final int vis = getWindowSystemUiVisibility();
    172         final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
    173 
    174         // The top and bottom action bars are always within the content area.
    175         boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true);
    176         if (mActionBarBottom != null) {
    177             changed |= applyInsets(mActionBarBottom, insets, true, false, true, true);
    178         }
    179 
    180         mBaseInnerInsets.set(insets);
    181         computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
    182         if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
    183             changed = true;
    184             mLastBaseContentInsets.set(mBaseContentInsets);
    185         }
    186 
    187         if (changed) {
    188             requestLayout();
    189         }
    190 
    191         // We don't do any more at this point.  To correctly compute the content/inner
    192         // insets in all cases, we need to know the measured size of the various action
    193         // bar elements.  fitSystemWindows() happens before the measure pass, so we can't
    194         // do that here.  Instead we will take this up in onMeasure().
    195         return true;
    196     }
    197 
    198     @Override
    199     protected LayoutParams generateDefaultLayoutParams() {
    200         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    201     }
    202 
    203     @Override
    204     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    205         return new LayoutParams(getContext(), attrs);
    206     }
    207 
    208     @Override
    209     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    210         return new LayoutParams(p);
    211     }
    212 
    213     @Override
    214     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    215         return p instanceof LayoutParams;
    216     }
    217 
    218     @Override
    219     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    220         pullChildren();
    221 
    222         int maxHeight = 0;
    223         int maxWidth = 0;
    224         int childState = 0;
    225 
    226         int topInset = 0;
    227         int bottomInset = 0;
    228 
    229         measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0);
    230         LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams();
    231         maxWidth = Math.max(maxWidth,
    232                 mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
    233         maxHeight = Math.max(maxHeight,
    234                 mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
    235         childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState());
    236 
    237         // xlarge screen layout doesn't have bottom action bar.
    238         if (mActionBarBottom != null) {
    239             measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
    240             lp = (LayoutParams) mActionBarBottom.getLayoutParams();
    241             maxWidth = Math.max(maxWidth,
    242                     mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
    243             maxHeight = Math.max(maxHeight,
    244                     mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
    245             childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState());
    246         }
    247 
    248         final int vis = getWindowSystemUiVisibility();
    249         final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
    250 
    251         if (stable) {
    252             // This is the standard space needed for the action bar.  For stable measurement,
    253             // we can't depend on the size currently reported by it -- this must remain constant.
    254             topInset = mActionBarHeight;
    255             if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
    256                 View tabs = mContainerView.getTabContainer();
    257                 if (tabs != null) {
    258                     // If tabs are not embedded, increase space on top to account for them.
    259                     topInset += mActionBarHeight;
    260                 }
    261             }
    262         } else if (mActionBarTop.getVisibility() == VISIBLE) {
    263             // This is the space needed on top of the window for all of the action bar
    264             // and tabs.
    265             topInset = mActionBarTop.getMeasuredHeight();
    266         }
    267 
    268         if (mActionView.isSplitActionBar()) {
    269             // If action bar is split, adjust bottom insets for it.
    270             if (mActionBarBottom != null) {
    271                 if (stable) {
    272                     bottomInset = mActionBarHeight;
    273                 } else {
    274                     bottomInset = mActionBarBottom.getMeasuredHeight();
    275                 }
    276             }
    277         }
    278 
    279         // If the window has not requested system UI layout flags, we need to
    280         // make sure its content is not being covered by system UI...  though it
    281         // will still be covered by the action bar if they have requested it to
    282         // overlay.
    283         mContentInsets.set(mBaseContentInsets);
    284         mInnerInsets.set(mBaseInnerInsets);
    285         if (!mOverlayMode && !stable) {
    286             mContentInsets.top += topInset;
    287             mContentInsets.bottom += bottomInset;
    288         } else {
    289             mInnerInsets.top += topInset;
    290             mInnerInsets.bottom += bottomInset;
    291         }
    292         applyInsets(mContent, mContentInsets, true, true, true, true);
    293 
    294         if (!mLastInnerInsets.equals(mInnerInsets)) {
    295             // If the inner insets have changed, we need to dispatch this down to
    296             // the app's fitSystemWindows().  We do this before measuring the content
    297             // view to keep the same semantics as the normal fitSystemWindows() call.
    298             mLastInnerInsets.set(mInnerInsets);
    299             super.fitSystemWindows(mInnerInsets);
    300         }
    301 
    302         measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
    303         lp = (LayoutParams) mContent.getLayoutParams();
    304         maxWidth = Math.max(maxWidth,
    305                 mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
    306         maxHeight = Math.max(maxHeight,
    307                 mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
    308         childState = combineMeasuredStates(childState, mContent.getMeasuredState());
    309 
    310         // Account for padding too
    311         maxWidth += getPaddingLeft() + getPaddingRight();
    312         maxHeight += getPaddingTop() + getPaddingBottom();
    313 
    314         // Check against our minimum height and width
    315         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    316         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    317 
    318         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
    319                 resolveSizeAndState(maxHeight, heightMeasureSpec,
    320                         childState << MEASURED_HEIGHT_STATE_SHIFT));
    321     }
    322 
    323     @Override
    324     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    325         final int count = getChildCount();
    326 
    327         final int parentLeft = getPaddingLeft();
    328         final int parentRight = right - left - getPaddingRight();
    329 
    330         final int parentTop = getPaddingTop();
    331         final int parentBottom = bottom - top - getPaddingBottom();
    332 
    333         for (int i = 0; i < count; i++) {
    334             final View child = getChildAt(i);
    335             if (child.getVisibility() != GONE) {
    336                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    337 
    338                 final int width = child.getMeasuredWidth();
    339                 final int height = child.getMeasuredHeight();
    340 
    341                 int childLeft = parentLeft + lp.leftMargin;
    342                 int childTop;
    343                 if (child == mActionBarBottom) {
    344                     childTop = parentBottom - height - lp.bottomMargin;
    345                 } else {
    346                     childTop = parentTop + lp.topMargin;
    347                 }
    348 
    349                 child.layout(childLeft, childTop, childLeft + width, childTop + height);
    350             }
    351         }
    352     }
    353 
    354     @Override
    355     public boolean shouldDelayChildPressedState() {
    356         return false;
    357     }
    358 
    359     void pullChildren() {
    360         if (mContent == null) {
    361             mContent = findViewById(com.android.internal.R.id.content);
    362             mActionBarTop = findViewById(com.android.internal.R.id.top_action_bar);
    363             mContainerView = (ActionBarContainer)findViewById(
    364                     com.android.internal.R.id.action_bar_container);
    365             mActionView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
    366             mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
    367         }
    368     }
    369 
    370 
    371     public static class LayoutParams extends MarginLayoutParams {
    372         public LayoutParams(Context c, AttributeSet attrs) {
    373             super(c, attrs);
    374         }
    375 
    376         public LayoutParams(int width, int height) {
    377             super(width, height);
    378         }
    379 
    380         public LayoutParams(ViewGroup.LayoutParams source) {
    381             super(source);
    382         }
    383 
    384         public LayoutParams(ViewGroup.MarginLayoutParams source) {
    385             super(source);
    386         }
    387     }
    388 }
    389