ActionBarOverlayLayout.java revision 474690caf8b3bece133b40914979ac2520036989
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 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
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
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
136    protected void onWindowVisibilityChanged(int visibility) {
137        super.onWindowVisibilityChanged(visibility);
138        mWindowVisibility = visibility;
139        if (mActionBar != null) {
140            mActionBar.setWindowVisibility(visibility);
141        }
142    }
143
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
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
199    protected LayoutParams generateDefaultLayoutParams() {
200        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
201    }
202
203    @Override
204    public LayoutParams generateLayoutParams(AttributeSet attrs) {
205        return new LayoutParams(getContext(), attrs);
206    }
207
208    @Override
209    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
210        return new LayoutParams(p);
211    }
212
213    @Override
214    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
215        return p instanceof LayoutParams;
216    }
217
218    @Override
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
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
355    public boolean shouldDelayChildPressedState() {
356        return false;
357    }
358
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 {
372        public LayoutParams(Context c, AttributeSet attrs) {
373            super(c, attrs);
374        }
375
376        public LayoutParams(int width, int height) {
377            super(width, height);
378        }
379
380        public LayoutParams(ViewGroup.LayoutParams source) {
381            super(source);
382        }
383
384        public LayoutParams(ViewGroup.MarginLayoutParams source) {
385            super(source);
386        }
387    }
388}
389