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