• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ActionBarOverlayLayout(Context context)60     public ActionBarOverlayLayout(Context context) {
61         super(context);
62         init(context);
63     }
64 
ActionBarOverlayLayout(Context context, AttributeSet attrs)65     public ActionBarOverlayLayout(Context context, AttributeSet attrs) {
66         super(context, attrs);
67         init(context);
68     }
69 
init(Context context)70     private void init(Context context) {
71         TypedArray ta = getContext().getTheme().obtainStyledAttributes(mActionBarSizeAttr);
72         mActionBarHeight = ta.getDimensionPixelSize(0, 0);
73         ta.recycle();
74     }
75 
setActionBar(ActionBarImpl impl, boolean overlayMode)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 
setShowingForActionMode(boolean showing)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
onWindowSystemUiVisibilityChanged(int visible)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
onWindowVisibilityChanged(int visibility)136     protected void onWindowVisibilityChanged(int visibility) {
137         super.onWindowVisibilityChanged(visibility);
138         mWindowVisibility = visibility;
139         if (mActionBar != null) {
140             mActionBar.setWindowVisibility(visibility);
141         }
142     }
143 
applyInsets(View view, Rect insets, boolean left, boolean top, boolean bottom, boolean right)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
fitSystemWindows(Rect insets)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
generateDefaultLayoutParams()199     protected LayoutParams generateDefaultLayoutParams() {
200         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
201     }
202 
203     @Override
generateLayoutParams(AttributeSet attrs)204     public LayoutParams generateLayoutParams(AttributeSet attrs) {
205         return new LayoutParams(getContext(), attrs);
206     }
207 
208     @Override
generateLayoutParams(ViewGroup.LayoutParams p)209     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
210         return new LayoutParams(p);
211     }
212 
213     @Override
checkLayoutParams(ViewGroup.LayoutParams p)214     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
215         return p instanceof LayoutParams;
216     }
217 
218     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
onLayout(boolean changed, int left, int top, int right, int bottom)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
shouldDelayChildPressedState()355     public boolean shouldDelayChildPressedState() {
356         return false;
357     }
358 
pullChildren()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 {
LayoutParams(Context c, AttributeSet attrs)372         public LayoutParams(Context c, AttributeSet attrs) {
373             super(c, attrs);
374         }
375 
LayoutParams(int width, int height)376         public LayoutParams(int width, int height) {
377             super(width, height);
378         }
379 
LayoutParams(ViewGroup.LayoutParams source)380         public LayoutParams(ViewGroup.LayoutParams source) {
381             super(source);
382         }
383 
LayoutParams(ViewGroup.MarginLayoutParams source)384         public LayoutParams(ViewGroup.MarginLayoutParams source) {
385             super(source);
386         }
387     }
388 }
389