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