ActionBarOverlayLayout.java revision 46e38fd9abe1af3ccb903a80ff89bc3faef4d3e3
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.graphics.Canvas;
20import android.graphics.drawable.Drawable;
21import android.os.Build;
22import android.view.ViewGroup;
23import android.view.WindowInsets;
24import com.android.internal.app.ActionBarImpl;
25
26import android.content.Context;
27import android.content.res.TypedArray;
28import android.graphics.Rect;
29import android.util.AttributeSet;
30import android.view.View;
31
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 */
37public class ActionBarOverlayLayout extends ViewGroup {
38    private static final String TAG = "ActionBarOverlayLayout";
39
40    private int mActionBarHeight;
41    private ActionBarImpl mActionBar;
42    private int mWindowVisibility = View.VISIBLE;
43
44    // The main UI elements that we handle the layout of.
45    private View mContent;
46    private View mActionBarBottom;
47    private ActionBarContainer mActionBarTop;
48
49    // Some interior UI elements.
50    private ActionBarView mActionBarView;
51
52    // Content overlay drawable - generally the action bar's shadow
53    private Drawable mWindowContentOverlay;
54    private boolean mIgnoreWindowContentOverlay;
55
56    private boolean mOverlayMode;
57    private int mLastSystemUiVisibility;
58    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();
64
65    static final int[] ATTRS = new int [] {
66            com.android.internal.R.attr.actionBarSize,
67            com.android.internal.R.attr.windowContentOverlay
68    };
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) {
81        TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS);
82        mActionBarHeight = ta.getDimensionPixelSize(0, 0);
83        mWindowContentOverlay = ta.getDrawable(1);
84        setWillNotDraw(mWindowContentOverlay == null);
85        ta.recycle();
86
87        mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
88                Build.VERSION_CODES.KITKAT;
89    }
90
91    public void setActionBar(ActionBarImpl impl) {
92        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);
100                requestApplyInsets();
101            }
102        }
103    }
104
105    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 <
114                        Build.VERSION_CODES.KITKAT;
115    }
116
117    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
137    @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;
144        final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
145        final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
146        if (mActionBar != null) {
147            // 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();
152            else mActionBar.hideForSystem();
153        }
154        if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
155            if (mActionBar != null) {
156                requestApplyInsets();
157            }
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;
173        LayoutParams lp = (LayoutParams)view.getLayoutParams();
174        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
194    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
195        pullChildren();
196
197        final int vis = getWindowSystemUiVisibility();
198        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
199        final Rect systemInsets = insets.getSystemWindowInsets();
200
201        // The top and bottom action bars are always within the content area.
202        boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true);
203        if (mActionBarBottom != null) {
204            changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
205        }
206
207        mBaseInnerInsets.set(systemInsets);
208        computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
209        if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
210            changed = true;
211            mLastBaseContentInsets.set(mBaseContentInsets);
212        }
213
214        if (changed) {
215            requestLayout();
216        }
217
218        // 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
220        // bar elements.  onApplyWindowInsets() happens before the measure pass, so we can't
221        // do that here.  Instead we will take this up in onMeasure().
222        return WindowInsets.EMPTY;
223    }
224
225    @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
236    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
246    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
247        pullChildren();
248
249        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
264        // 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        }
274
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()) {
283                View tabs = mActionBarTop.getTabContainer();
284                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
295        if (mActionBarView.isSplitActionBar()) {
296            // If action bar is split, adjust bottom insets for it.
297            if (mActionBarBottom != null) {
298                if (stable) {
299                    bottomInset = mActionBarHeight;
300                } else {
301                    bottomInset = mActionBarBottom.getMeasuredHeight();
302                }
303            }
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);
326            mContent.dispatchApplyWindowInsets(new WindowInsets(mInnerInsets));
327        }
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
381    @Override
382    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
394    public boolean shouldDelayChildPressedState() {
395        return false;
396    }
397
398    void pullChildren() {
399        if (mContent == null) {
400            mContent = findViewById(com.android.internal.R.id.content);
401            mActionBarTop = (ActionBarContainer)findViewById(
402                    com.android.internal.R.id.action_bar_container);
403            mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
404            mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
405        }
406    }
407
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    }
426}
427