ActionBarOverlayLayout.java revision 7cf71fbdc3225778d0397b22cdaf40c812c15afb
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 setOverlayMode(boolean mode) {
77        mOverlayMode = mode;
78    }
79
80    public void setActionBar(ActionBarImpl impl, boolean overlayMode) {
81        mActionBar = impl;
82        mOverlayMode = overlayMode;
83        if (getWindowToken() != null) {
84            // This is being initialized after being added to a window;
85            // make sure to update all state now.
86            mActionBar.setWindowVisibility(mWindowVisibility);
87            if (mLastSystemUiVisibility != 0) {
88                int newVis = mLastSystemUiVisibility;
89                onWindowSystemUiVisibilityChanged(newVis);
90                requestFitSystemWindows();
91            }
92        }
93    }
94
95    public void setShowingForActionMode(boolean showing) {
96        if (showing) {
97            // Here's a fun hack: if the status bar is currently being hidden,
98            // and the application has asked for stable content insets, then
99            // we will end up with the action mode action bar being shown
100            // without the status bar, but moved below where the status bar
101            // would be.  Not nice.  Trying to have this be positioned
102            // correctly is not easy (basically we need yet *another* content
103            // inset from the window manager to know where to put it), so
104            // instead we will just temporarily force the status bar to be shown.
105            if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
106                    | SYSTEM_UI_FLAG_LAYOUT_STABLE))
107                    == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) {
108                setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
109            }
110        } else {
111            setDisabledSystemUiVisibility(0);
112        }
113    }
114
115    @Override
116    public void onWindowSystemUiVisibilityChanged(int visible) {
117        super.onWindowSystemUiVisibilityChanged(visible);
118        pullChildren();
119        final int diff = mLastSystemUiVisibility ^ visible;
120        mLastSystemUiVisibility = visible;
121        final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0;
122        final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
123        final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
124        if (mActionBar != null) {
125            // We want the bar to be visible if it is not being hidden,
126            // or the app has not turned on a stable UI mode (meaning they
127            // are performing explicit layout around the action bar).
128            mActionBar.enableContentAnimations(!stable);
129            if (barVisible || !stable) mActionBar.showForSystem();
130            else mActionBar.hideForSystem();
131        }
132        if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
133            if (mActionBar != null) {
134                requestFitSystemWindows();
135            }
136        }
137    }
138
139    @Override
140    protected void onWindowVisibilityChanged(int visibility) {
141        super.onWindowVisibilityChanged(visibility);
142        mWindowVisibility = visibility;
143        if (mActionBar != null) {
144            mActionBar.setWindowVisibility(visibility);
145        }
146    }
147
148    private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
149            boolean bottom, boolean right) {
150        boolean changed = false;
151        LayoutParams lp = (LayoutParams)view.getLayoutParams();
152        if (left && lp.leftMargin != insets.left) {
153            changed = true;
154            lp.leftMargin = insets.left;
155        }
156        if (top && lp.topMargin != insets.top) {
157            changed = true;
158            lp.topMargin = insets.top;
159        }
160        if (right && lp.rightMargin != insets.right) {
161            changed = true;
162            lp.rightMargin = insets.right;
163        }
164        if (bottom && lp.bottomMargin != insets.bottom) {
165            changed = true;
166            lp.bottomMargin = insets.bottom;
167        }
168        return changed;
169    }
170
171    @Override
172    protected boolean fitSystemWindows(Rect insets) {
173        pullChildren();
174
175        final int vis = getWindowSystemUiVisibility();
176        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
177
178        // The top and bottom action bars are always within the content area.
179        boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true);
180        changed |= applyInsets(mActionBarBottom, insets, true, false, true, true);
181
182        mBaseInnerInsets.set(insets);
183        computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
184        if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
185            changed = true;
186            mLastBaseContentInsets.set(mBaseContentInsets);
187        }
188
189        if (changed) {
190            requestLayout();
191        }
192
193        // We don't do any more at this point.  To correctly compute the content/inner
194        // insets in all cases, we need to know the measured size of the various action
195        // bar elements.  fitSystemWindows() happens before the measure pass, so we can't
196        // do that here.  Instead we will take this up in onMeasure().
197        return true;
198    }
199
200    @Override
201    protected LayoutParams generateDefaultLayoutParams() {
202        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
203    }
204
205    @Override
206    public LayoutParams generateLayoutParams(AttributeSet attrs) {
207        return new LayoutParams(getContext(), attrs);
208    }
209
210    @Override
211    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
212        return new LayoutParams(p);
213    }
214
215    @Override
216    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
217        return p instanceof LayoutParams;
218    }
219
220    @Override
221    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
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        measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
238        lp = (LayoutParams) mActionBarBottom.getLayoutParams();
239        maxWidth = Math.max(maxWidth,
240                mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
241        maxHeight = Math.max(maxHeight,
242                mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
243        childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState());
244
245        final int vis = getWindowSystemUiVisibility();
246        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
247
248        if (stable) {
249            // This is the standard space needed for the action bar.  For stable measurement,
250            // we can't depend on the size currently reported by it -- this must remain constant.
251            topInset = mActionBarHeight;
252            if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
253                View tabs = mContainerView.getTabContainer();
254                if (tabs != null) {
255                    // If tabs are not embedded, increase space on top to account for them.
256                    topInset += mActionBarHeight;
257                }
258            }
259        } else if (mActionBarTop.getVisibility() == VISIBLE) {
260            // This is the space needed on top of the window for all of the action bar
261            // and tabs.
262            topInset = mActionBarTop.getMeasuredHeight();
263        }
264
265        if (mActionView.isSplitActionBar()) {
266            // If action bar is split, adjust bottom insets for it.
267            if (stable) {
268                bottomInset = mActionBarHeight;
269            } else {
270                bottomInset = mActionBarBottom.getMeasuredHeight();
271            }
272        }
273
274        // If the window has not requested system UI layout flags, we need to
275        // make sure its content is not being covered by system UI...  though it
276        // will still be covered by the action bar if they have requested it to
277        // overlay.
278        mContentInsets.set(mBaseContentInsets);
279        mInnerInsets.set(mBaseInnerInsets);
280        if (!mOverlayMode && !stable) {
281            mContentInsets.top += topInset;
282            mContentInsets.bottom += bottomInset;
283        } else {
284            mInnerInsets.top += topInset;
285            mInnerInsets.bottom += bottomInset;
286        }
287        applyInsets(mContent, mContentInsets, true, true, true, true);
288
289        if (!mLastInnerInsets.equals(mInnerInsets)) {
290            // If the inner insets have changed, we need to dispatch this down to
291            // the app's fitSystemWindows().  We do this before measuring the content
292            // view to keep the same semantics as the normal fitSystemWindows() call.
293            mLastInnerInsets.set(mInnerInsets);
294            super.fitSystemWindows(mInnerInsets);
295        }
296
297        measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
298        lp = (LayoutParams) mContent.getLayoutParams();
299        maxWidth = Math.max(maxWidth,
300                mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
301        maxHeight = Math.max(maxHeight,
302                mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
303        childState = combineMeasuredStates(childState, mContent.getMeasuredState());
304
305        // Account for padding too
306        maxWidth += getPaddingLeft() + getPaddingRight();
307        maxHeight += getPaddingTop() + getPaddingBottom();
308
309        // Check against our minimum height and width
310        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
311        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
312
313        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
314                resolveSizeAndState(maxHeight, heightMeasureSpec,
315                        childState << MEASURED_HEIGHT_STATE_SHIFT));
316    }
317
318    @Override
319    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
320        final int count = getChildCount();
321
322        final int parentLeft = getPaddingLeft();
323        final int parentRight = right - left - getPaddingRight();
324
325        final int parentTop = getPaddingTop();
326        final int parentBottom = bottom - top - getPaddingBottom();
327
328        for (int i = 0; i < count; i++) {
329            final View child = getChildAt(i);
330            if (child.getVisibility() != GONE) {
331                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
332
333                final int width = child.getMeasuredWidth();
334                final int height = child.getMeasuredHeight();
335
336                int childLeft = parentLeft + lp.leftMargin;
337                int childTop;
338                if (child == mActionBarBottom) {
339                    childTop = parentBottom - height - lp.bottomMargin;
340                } else {
341                    childTop = parentTop + lp.topMargin;
342                }
343
344                child.layout(childLeft, childTop, childLeft + width, childTop + height);
345            }
346        }
347    }
348
349    @Override
350    public boolean shouldDelayChildPressedState() {
351        return false;
352    }
353
354    void pullChildren() {
355        if (mContent == null) {
356            mContent = findViewById(com.android.internal.R.id.content);
357            mActionBarTop = findViewById(com.android.internal.R.id.top_action_bar);
358            mContainerView = (ActionBarContainer)findViewById(
359                    com.android.internal.R.id.action_bar_container);
360            mActionView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
361            mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
362        }
363    }
364
365
366    public static class LayoutParams extends MarginLayoutParams {
367        public LayoutParams(Context c, AttributeSet attrs) {
368            super(c, attrs);
369        }
370
371        public LayoutParams(int width, int height) {
372            super(width, height);
373        }
374
375        public LayoutParams(ViewGroup.LayoutParams source) {
376            super(source);
377        }
378
379        public LayoutParams(ViewGroup.MarginLayoutParams source) {
380            super(source);
381        }
382    }
383}
384