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