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