ActionBarOverlayLayout.java revision 7cf71fbdc3225778d0397b22cdaf40c812c15afb
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 17package com.android.internal.widget; 18 19import android.view.ViewGroup; 20import com.android.internal.app.ActionBarImpl; 21 22import android.content.Context; 23import android.content.res.TypedArray; 24import android.graphics.Rect; 25import android.util.AttributeSet; 26import 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 */ 33public 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 setOverlayMode(boolean mode) { 77 mOverlayMode = mode; 78 } 79 80 public void setActionBar(ActionBarImpl impl, boolean overlayMode) { 81 mActionBar = impl; 82 mOverlayMode = overlayMode; 83 if (getWindowToken() != null) { 84 // This is being initialized after being added to a window; 85 // make sure to update all state now. 86 mActionBar.setWindowVisibility(mWindowVisibility); 87 if (mLastSystemUiVisibility != 0) { 88 int newVis = mLastSystemUiVisibility; 89 onWindowSystemUiVisibilityChanged(newVis); 90 requestFitSystemWindows(); 91 } 92 } 93 } 94 95 public void setShowingForActionMode(boolean showing) { 96 if (showing) { 97 // Here's a fun hack: if the status bar is currently being hidden, 98 // and the application has asked for stable content insets, then 99 // we will end up with the action mode action bar being shown 100 // without the status bar, but moved below where the status bar 101 // would be. Not nice. Trying to have this be positioned 102 // correctly is not easy (basically we need yet *another* content 103 // inset from the window manager to know where to put it), so 104 // instead we will just temporarily force the status bar to be shown. 105 if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 106 | SYSTEM_UI_FLAG_LAYOUT_STABLE)) 107 == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) { 108 setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN); 109 } 110 } else { 111 setDisabledSystemUiVisibility(0); 112 } 113 } 114 115 @Override 116 public void onWindowSystemUiVisibilityChanged(int visible) { 117 super.onWindowSystemUiVisibilityChanged(visible); 118 pullChildren(); 119 final int diff = mLastSystemUiVisibility ^ visible; 120 mLastSystemUiVisibility = visible; 121 final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0; 122 final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true; 123 final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 124 if (mActionBar != null) { 125 // We want the bar to be visible if it is not being hidden, 126 // or the app has not turned on a stable UI mode (meaning they 127 // are performing explicit layout around the action bar). 128 mActionBar.enableContentAnimations(!stable); 129 if (barVisible || !stable) mActionBar.showForSystem(); 130 else mActionBar.hideForSystem(); 131 } 132 if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { 133 if (mActionBar != null) { 134 requestFitSystemWindows(); 135 } 136 } 137 } 138 139 @Override 140 protected void onWindowVisibilityChanged(int visibility) { 141 super.onWindowVisibilityChanged(visibility); 142 mWindowVisibility = visibility; 143 if (mActionBar != null) { 144 mActionBar.setWindowVisibility(visibility); 145 } 146 } 147 148 private boolean applyInsets(View view, Rect insets, boolean left, boolean top, 149 boolean bottom, boolean right) { 150 boolean changed = false; 151 LayoutParams lp = (LayoutParams)view.getLayoutParams(); 152 if (left && lp.leftMargin != insets.left) { 153 changed = true; 154 lp.leftMargin = insets.left; 155 } 156 if (top && lp.topMargin != insets.top) { 157 changed = true; 158 lp.topMargin = insets.top; 159 } 160 if (right && lp.rightMargin != insets.right) { 161 changed = true; 162 lp.rightMargin = insets.right; 163 } 164 if (bottom && lp.bottomMargin != insets.bottom) { 165 changed = true; 166 lp.bottomMargin = insets.bottom; 167 } 168 return changed; 169 } 170 171 @Override 172 protected boolean fitSystemWindows(Rect insets) { 173 pullChildren(); 174 175 final int vis = getWindowSystemUiVisibility(); 176 final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 177 178 // The top and bottom action bars are always within the content area. 179 boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true); 180 changed |= applyInsets(mActionBarBottom, insets, true, false, true, true); 181 182 mBaseInnerInsets.set(insets); 183 computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets); 184 if (!mLastBaseContentInsets.equals(mBaseContentInsets)) { 185 changed = true; 186 mLastBaseContentInsets.set(mBaseContentInsets); 187 } 188 189 if (changed) { 190 requestLayout(); 191 } 192 193 // We don't do any more at this point. To correctly compute the content/inner 194 // insets in all cases, we need to know the measured size of the various action 195 // bar elements. fitSystemWindows() happens before the measure pass, so we can't 196 // do that here. Instead we will take this up in onMeasure(). 197 return true; 198 } 199 200 @Override 201 protected LayoutParams generateDefaultLayoutParams() { 202 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 203 } 204 205 @Override 206 public LayoutParams generateLayoutParams(AttributeSet attrs) { 207 return new LayoutParams(getContext(), attrs); 208 } 209 210 @Override 211 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 212 return new LayoutParams(p); 213 } 214 215 @Override 216 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 217 return p instanceof LayoutParams; 218 } 219 220 @Override 221 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 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 measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0); 238 lp = (LayoutParams) mActionBarBottom.getLayoutParams(); 239 maxWidth = Math.max(maxWidth, 240 mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 241 maxHeight = Math.max(maxHeight, 242 mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 243 childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState()); 244 245 final int vis = getWindowSystemUiVisibility(); 246 final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 247 248 if (stable) { 249 // This is the standard space needed for the action bar. For stable measurement, 250 // we can't depend on the size currently reported by it -- this must remain constant. 251 topInset = mActionBarHeight; 252 if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) { 253 View tabs = mContainerView.getTabContainer(); 254 if (tabs != null) { 255 // If tabs are not embedded, increase space on top to account for them. 256 topInset += mActionBarHeight; 257 } 258 } 259 } else if (mActionBarTop.getVisibility() == VISIBLE) { 260 // This is the space needed on top of the window for all of the action bar 261 // and tabs. 262 topInset = mActionBarTop.getMeasuredHeight(); 263 } 264 265 if (mActionView.isSplitActionBar()) { 266 // If action bar is split, adjust bottom insets for it. 267 if (stable) { 268 bottomInset = mActionBarHeight; 269 } else { 270 bottomInset = mActionBarBottom.getMeasuredHeight(); 271 } 272 } 273 274 // If the window has not requested system UI layout flags, we need to 275 // make sure its content is not being covered by system UI... though it 276 // will still be covered by the action bar if they have requested it to 277 // overlay. 278 mContentInsets.set(mBaseContentInsets); 279 mInnerInsets.set(mBaseInnerInsets); 280 if (!mOverlayMode && !stable) { 281 mContentInsets.top += topInset; 282 mContentInsets.bottom += bottomInset; 283 } else { 284 mInnerInsets.top += topInset; 285 mInnerInsets.bottom += bottomInset; 286 } 287 applyInsets(mContent, mContentInsets, true, true, true, true); 288 289 if (!mLastInnerInsets.equals(mInnerInsets)) { 290 // If the inner insets have changed, we need to dispatch this down to 291 // the app's fitSystemWindows(). We do this before measuring the content 292 // view to keep the same semantics as the normal fitSystemWindows() call. 293 mLastInnerInsets.set(mInnerInsets); 294 super.fitSystemWindows(mInnerInsets); 295 } 296 297 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0); 298 lp = (LayoutParams) mContent.getLayoutParams(); 299 maxWidth = Math.max(maxWidth, 300 mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 301 maxHeight = Math.max(maxHeight, 302 mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 303 childState = combineMeasuredStates(childState, mContent.getMeasuredState()); 304 305 // Account for padding too 306 maxWidth += getPaddingLeft() + getPaddingRight(); 307 maxHeight += getPaddingTop() + getPaddingBottom(); 308 309 // Check against our minimum height and width 310 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); 311 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); 312 313 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), 314 resolveSizeAndState(maxHeight, heightMeasureSpec, 315 childState << MEASURED_HEIGHT_STATE_SHIFT)); 316 } 317 318 @Override 319 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 320 final int count = getChildCount(); 321 322 final int parentLeft = getPaddingLeft(); 323 final int parentRight = right - left - getPaddingRight(); 324 325 final int parentTop = getPaddingTop(); 326 final int parentBottom = bottom - top - getPaddingBottom(); 327 328 for (int i = 0; i < count; i++) { 329 final View child = getChildAt(i); 330 if (child.getVisibility() != GONE) { 331 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 332 333 final int width = child.getMeasuredWidth(); 334 final int height = child.getMeasuredHeight(); 335 336 int childLeft = parentLeft + lp.leftMargin; 337 int childTop; 338 if (child == mActionBarBottom) { 339 childTop = parentBottom - height - lp.bottomMargin; 340 } else { 341 childTop = parentTop + lp.topMargin; 342 } 343 344 child.layout(childLeft, childTop, childLeft + width, childTop + height); 345 } 346 } 347 } 348 349 @Override 350 public boolean shouldDelayChildPressedState() { 351 return false; 352 } 353 354 void pullChildren() { 355 if (mContent == null) { 356 mContent = findViewById(com.android.internal.R.id.content); 357 mActionBarTop = findViewById(com.android.internal.R.id.top_action_bar); 358 mContainerView = (ActionBarContainer)findViewById( 359 com.android.internal.R.id.action_bar_container); 360 mActionView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); 361 mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar); 362 } 363 } 364 365 366 public static class LayoutParams extends MarginLayoutParams { 367 public LayoutParams(Context c, AttributeSet attrs) { 368 super(c, attrs); 369 } 370 371 public LayoutParams(int width, int height) { 372 super(width, height); 373 } 374 375 public LayoutParams(ViewGroup.LayoutParams source) { 376 super(source); 377 } 378 379 public LayoutParams(ViewGroup.MarginLayoutParams source) { 380 super(source); 381 } 382 } 383} 384