blob: c957b673ff597efcb19e0a6170b4cfa4c1f89245 [file] [log] [blame]
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -07001/*
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
Adam Powell9b0dc282013-07-31 13:58:43 -070019import android.graphics.Canvas;
20import android.graphics.drawable.Drawable;
21import android.os.Build;
Dianne Hackborn1dc5f922013-03-04 18:17:50 -080022import android.view.ViewGroup;
Adam Powell46e38fd2014-02-03 10:16:49 -080023import android.view.WindowInsets;
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070024import com.android.internal.app.ActionBarImpl;
25
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070026import android.content.Context;
27import android.content.res.TypedArray;
28import android.graphics.Rect;
29import android.util.AttributeSet;
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070030import android.view.View;
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070031
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 */
Dianne Hackborn1dc5f922013-03-04 18:17:50 -080037public class ActionBarOverlayLayout extends ViewGroup {
Adam Powell9b0dc282013-07-31 13:58:43 -070038 private static final String TAG = "ActionBarOverlayLayout";
39
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070040 private int mActionBarHeight;
41 private ActionBarImpl mActionBar;
42 private int mWindowVisibility = View.VISIBLE;
Dianne Hackborn1dc5f922013-03-04 18:17:50 -080043
44 // The main UI elements that we handle the layout of.
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070045 private View mContent;
Dianne Hackborn1dc5f922013-03-04 18:17:50 -080046 private View mActionBarBottom;
Adam Powell9b0dc282013-07-31 13:58:43 -070047 private ActionBarContainer mActionBarTop;
Dianne Hackborn1dc5f922013-03-04 18:17:50 -080048
49 // Some interior UI elements.
Adam Powell9b0dc282013-07-31 13:58:43 -070050 private ActionBarView mActionBarView;
51
52 // Content overlay drawable - generally the action bar's shadow
53 private Drawable mWindowContentOverlay;
54 private boolean mIgnoreWindowContentOverlay;
Dianne Hackborn1dc5f922013-03-04 18:17:50 -080055
Dianne Hackborndf7221c2013-02-26 14:53:55 -080056 private boolean mOverlayMode;
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070057 private int mLastSystemUiVisibility;
Dianne Hackborn1dc5f922013-03-04 18:17:50 -080058 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();
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070064
Adam Powell9b0dc282013-07-31 13:58:43 -070065 static final int[] ATTRS = new int [] {
66 com.android.internal.R.attr.actionBarSize,
67 com.android.internal.R.attr.windowContentOverlay
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070068 };
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) {
Adam Powell9b0dc282013-07-31 13:58:43 -070081 TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS);
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070082 mActionBarHeight = ta.getDimensionPixelSize(0, 0);
Adam Powell9b0dc282013-07-31 13:58:43 -070083 mWindowContentOverlay = ta.getDrawable(1);
84 setWillNotDraw(mWindowContentOverlay == null);
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070085 ta.recycle();
Adam Powell9b0dc282013-07-31 13:58:43 -070086
87 mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
Chet Haasee8222dd2013-09-05 07:44:18 -070088 Build.VERSION_CODES.KITKAT;
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070089 }
90
Adam Powell9b0dc282013-07-31 13:58:43 -070091 public void setActionBar(ActionBarImpl impl) {
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -070092 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);
Adam Powell46e38fd2014-02-03 10:16:49 -0800100 requestApplyInsets();
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700101 }
102 }
103 }
104
Adam Powell9b0dc282013-07-31 13:58:43 -0700105 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 <
Chet Haasee8222dd2013-09-05 07:44:18 -0700114 Build.VERSION_CODES.KITKAT;
Adam Powell9b0dc282013-07-31 13:58:43 -0700115 }
116
Dianne Hackborn139e5aa2012-05-05 20:36:38 -0700117 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
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700137 @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;
Dianne Hackborn139e5aa2012-05-05 20:36:38 -0700144 final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
Dianne Hackborndf7221c2013-02-26 14:53:55 -0800145 final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
Dianne Hackborn139e5aa2012-05-05 20:36:38 -0700146 if (mActionBar != null) {
Dianne Hackborndf7221c2013-02-26 14:53:55 -0800147 // 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();
Dianne Hackborn139e5aa2012-05-05 20:36:38 -0700152 else mActionBar.hideForSystem();
153 }
154 if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700155 if (mActionBar != null) {
Adam Powell46e38fd2014-02-03 10:16:49 -0800156 requestApplyInsets();
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700157 }
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;
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800173 LayoutParams lp = (LayoutParams)view.getLayoutParams();
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700174 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
Adam Powell46e38fd2014-02-03 10:16:49 -0800194 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700195 pullChildren();
196
197 final int vis = getWindowSystemUiVisibility();
198 final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
Adam Powell46e38fd2014-02-03 10:16:49 -0800199 final Rect systemInsets = insets.getSystemWindowInsets();
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700200
201 // The top and bottom action bars are always within the content area.
Adam Powell46e38fd2014-02-03 10:16:49 -0800202 boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true);
Dianne Hackborn474690c2013-03-06 11:33:26 -0800203 if (mActionBarBottom != null) {
Adam Powell46e38fd2014-02-03 10:16:49 -0800204 changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
Dianne Hackborn474690c2013-03-06 11:33:26 -0800205 }
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700206
Adam Powell46e38fd2014-02-03 10:16:49 -0800207 mBaseInnerInsets.set(systemInsets);
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800208 computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
209 if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
210 changed = true;
211 mLastBaseContentInsets.set(mBaseContentInsets);
Dianne Hackborndf7221c2013-02-26 14:53:55 -0800212 }
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700213
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700214 if (changed) {
215 requestLayout();
216 }
217
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800218 // 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
Adam Powell46e38fd2014-02-03 10:16:49 -0800220 // bar elements. onApplyWindowInsets() happens before the measure pass, so we can't
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800221 // do that here. Instead we will take this up in onMeasure().
Adam Powell46e38fd2014-02-03 10:16:49 -0800222 return WindowInsets.EMPTY;
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700223 }
224
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800225 @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
Dianne Hackborn7cf71fb2013-03-05 18:00:12 -0800236 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
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800246 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Dianne Hackborn474690c2013-03-06 11:33:26 -0800247 pullChildren();
248
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800249 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
Dianne Hackborn474690c2013-03-06 11:33:26 -0800264 // 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 }
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800274
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()) {
Adam Powell9b0dc282013-07-31 13:58:43 -0700283 View tabs = mActionBarTop.getTabContainer();
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800284 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
Adam Powell9b0dc282013-07-31 13:58:43 -0700295 if (mActionBarView.isSplitActionBar()) {
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800296 // If action bar is split, adjust bottom insets for it.
Dianne Hackborn474690c2013-03-06 11:33:26 -0800297 if (mActionBarBottom != null) {
298 if (stable) {
299 bottomInset = mActionBarHeight;
300 } else {
301 bottomInset = mActionBarBottom.getMeasuredHeight();
302 }
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800303 }
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);
Adam Powell46e38fd2014-02-03 10:16:49 -0800326 mContent.dispatchApplyWindowInsets(new WindowInsets(mInnerInsets));
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800327 }
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
Dianne Hackborn7cf71fb2013-03-05 18:00:12 -0800381 @Override
Adam Powell9b0dc282013-07-31 13:58:43 -0700382 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
Dianne Hackborn7cf71fb2013-03-05 18:00:12 -0800394 public boolean shouldDelayChildPressedState() {
395 return false;
396 }
397
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700398 void pullChildren() {
399 if (mContent == null) {
400 mContent = findViewById(com.android.internal.R.id.content);
Adam Powell9b0dc282013-07-31 13:58:43 -0700401 mActionBarTop = (ActionBarContainer)findViewById(
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700402 com.android.internal.R.id.action_bar_container);
Adam Powell9b0dc282013-07-31 13:58:43 -0700403 mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700404 mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
405 }
406 }
Dianne Hackborn1dc5f922013-03-04 18:17:50 -0800407
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 }
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700426}