ActionBarOverlayLayout.java revision 11335f14b9a3fd0a672a0fa0206e1f58b688dd36
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.content.res.Configuration;
20import android.graphics.Canvas;
21import android.graphics.drawable.Drawable;
22import android.os.Build;
23import android.view.ViewGroup;
24import android.view.WindowInsets;
25import com.android.internal.app.ActionBarImpl;
26
27import android.content.Context;
28import android.content.res.TypedArray;
29import android.graphics.Rect;
30import android.util.AttributeSet;
31import android.view.View;
32
33/**
34 * Special layout for the containing of an overlay action bar (and its
35 * content) to correctly handle fitting system windows when the content
36 * has request that its layout ignore them.
37 */
38public class ActionBarOverlayLayout extends ViewGroup {
39    private static final String TAG = "ActionBarOverlayLayout";
40
41    private int mActionBarHeight;
42    private ActionBarImpl mActionBar;
43    private int mWindowVisibility = View.VISIBLE;
44
45    // The main UI elements that we handle the layout of.
46    private View mContent;
47    private View mActionBarBottom;
48    private ActionBarContainer mActionBarTop;
49
50    // Some interior UI elements.
51    private ActionBarView mActionBarView;
52
53    // Content overlay drawable - generally the action bar's shadow
54    private Drawable mWindowContentOverlay;
55    private boolean mIgnoreWindowContentOverlay;
56
57    private boolean mOverlayMode;
58    private int mLastSystemUiVisibility;
59    private final Rect mBaseContentInsets = new Rect();
60    private final Rect mLastBaseContentInsets = new Rect();
61    private final Rect mContentInsets = new Rect();
62    private final Rect mBaseInnerInsets = new Rect();
63    private final Rect mInnerInsets = new Rect();
64    private final Rect mLastInnerInsets = new Rect();
65
66    static final int[] ATTRS = new int [] {
67            com.android.internal.R.attr.actionBarSize,
68            com.android.internal.R.attr.windowContentOverlay
69    };
70
71    public ActionBarOverlayLayout(Context context) {
72        super(context);
73        init(context);
74    }
75
76    public ActionBarOverlayLayout(Context context, AttributeSet attrs) {
77        super(context, attrs);
78        init(context);
79    }
80
81    private void init(Context context) {
82        TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS);
83        mActionBarHeight = ta.getDimensionPixelSize(0, 0);
84        mWindowContentOverlay = ta.getDrawable(1);
85        setWillNotDraw(mWindowContentOverlay == null);
86        ta.recycle();
87
88        mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
89                Build.VERSION_CODES.KITKAT;
90    }
91
92    public void setActionBar(ActionBarImpl impl) {
93        mActionBar = impl;
94        if (getWindowToken() != null) {
95            // This is being initialized after being added to a window;
96            // make sure to update all state now.
97            mActionBar.setWindowVisibility(mWindowVisibility);
98            if (mLastSystemUiVisibility != 0) {
99                int newVis = mLastSystemUiVisibility;
100                onWindowSystemUiVisibilityChanged(newVis);
101                requestApplyInsets();
102            }
103        }
104    }
105
106    public void setOverlayMode(boolean overlayMode) {
107        mOverlayMode = overlayMode;
108
109        /*
110         * Drawing the window content overlay was broken before K so starting to draw it
111         * again unexpectedly will cause artifacts in some apps. They should fix it.
112         */
113        mIgnoreWindowContentOverlay = overlayMode &&
114                getContext().getApplicationInfo().targetSdkVersion <
115                        Build.VERSION_CODES.KITKAT;
116    }
117
118    public void setShowingForActionMode(boolean showing) {
119        if (showing) {
120            // Here's a fun hack: if the status bar is currently being hidden,
121            // and the application has asked for stable content insets, then
122            // we will end up with the action mode action bar being shown
123            // without the status bar, but moved below where the status bar
124            // would be.  Not nice.  Trying to have this be positioned
125            // correctly is not easy (basically we need yet *another* content
126            // inset from the window manager to know where to put it), so
127            // instead we will just temporarily force the status bar to be shown.
128            if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
129                    | SYSTEM_UI_FLAG_LAYOUT_STABLE))
130                    == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) {
131                setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
132            }
133        } else {
134            setDisabledSystemUiVisibility(0);
135        }
136    }
137
138    @Override
139    protected void onConfigurationChanged(Configuration newConfig) {
140        super.onConfigurationChanged(newConfig);
141        init(getContext());
142        requestApplyInsets();
143    }
144
145    @Override
146    public void onWindowSystemUiVisibilityChanged(int visible) {
147        super.onWindowSystemUiVisibilityChanged(visible);
148        pullChildren();
149        final int diff = mLastSystemUiVisibility ^ visible;
150        mLastSystemUiVisibility = visible;
151        final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0;
152        final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
153        final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
154        if (mActionBar != null) {
155            // We want the bar to be visible if it is not being hidden,
156            // or the app has not turned on a stable UI mode (meaning they
157            // are performing explicit layout around the action bar).
158            mActionBar.enableContentAnimations(!stable);
159            if (barVisible || !stable) mActionBar.showForSystem();
160            else mActionBar.hideForSystem();
161        }
162        if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
163            if (mActionBar != null) {
164                requestApplyInsets();
165            }
166        }
167    }
168
169    @Override
170    protected void onWindowVisibilityChanged(int visibility) {
171        super.onWindowVisibilityChanged(visibility);
172        mWindowVisibility = visibility;
173        if (mActionBar != null) {
174            mActionBar.setWindowVisibility(visibility);
175        }
176    }
177
178    private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
179            boolean bottom, boolean right) {
180        boolean changed = false;
181        LayoutParams lp = (LayoutParams)view.getLayoutParams();
182        if (left && lp.leftMargin != insets.left) {
183            changed = true;
184            lp.leftMargin = insets.left;
185        }
186        if (top && lp.topMargin != insets.top) {
187            changed = true;
188            lp.topMargin = insets.top;
189        }
190        if (right && lp.rightMargin != insets.right) {
191            changed = true;
192            lp.rightMargin = insets.right;
193        }
194        if (bottom && lp.bottomMargin != insets.bottom) {
195            changed = true;
196            lp.bottomMargin = insets.bottom;
197        }
198        return changed;
199    }
200
201    @Override
202    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
203        pullChildren();
204
205        final int vis = getWindowSystemUiVisibility();
206        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
207        final Rect systemInsets = insets.getSystemWindowInsets();
208
209        // The top and bottom action bars are always within the content area.
210        boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true);
211        if (mActionBarBottom != null) {
212            changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
213        }
214
215        mBaseInnerInsets.set(systemInsets);
216        computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
217        if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
218            changed = true;
219            mLastBaseContentInsets.set(mBaseContentInsets);
220        }
221
222        if (changed) {
223            requestLayout();
224        }
225
226        // We don't do any more at this point.  To correctly compute the content/inner
227        // insets in all cases, we need to know the measured size of the various action
228        // bar elements.  onApplyWindowInsets() happens before the measure pass, so we can't
229        // do that here.  Instead we will take this up in onMeasure().
230        return WindowInsets.CONSUMED;
231    }
232
233    @Override
234    protected LayoutParams generateDefaultLayoutParams() {
235        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
236    }
237
238    @Override
239    public LayoutParams generateLayoutParams(AttributeSet attrs) {
240        return new LayoutParams(getContext(), attrs);
241    }
242
243    @Override
244    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
245        return new LayoutParams(p);
246    }
247
248    @Override
249    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
250        return p instanceof LayoutParams;
251    }
252
253    @Override
254    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
255        pullChildren();
256
257        int maxHeight = 0;
258        int maxWidth = 0;
259        int childState = 0;
260
261        int topInset = 0;
262        int bottomInset = 0;
263
264        measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0);
265        LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams();
266        maxWidth = Math.max(maxWidth,
267                mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
268        maxHeight = Math.max(maxHeight,
269                mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
270        childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState());
271
272        // xlarge screen layout doesn't have bottom action bar.
273        if (mActionBarBottom != null) {
274            measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
275            lp = (LayoutParams) mActionBarBottom.getLayoutParams();
276            maxWidth = Math.max(maxWidth,
277                    mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
278            maxHeight = Math.max(maxHeight,
279                    mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
280            childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState());
281        }
282
283        final int vis = getWindowSystemUiVisibility();
284        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
285
286        if (stable) {
287            // This is the standard space needed for the action bar.  For stable measurement,
288            // we can't depend on the size currently reported by it -- this must remain constant.
289            topInset = mActionBarHeight;
290            if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
291                View tabs = mActionBarTop.getTabContainer();
292                if (tabs != null) {
293                    // If tabs are not embedded, increase space on top to account for them.
294                    topInset += mActionBarHeight;
295                }
296            }
297        } else if (mActionBarTop.getVisibility() == VISIBLE) {
298            // This is the space needed on top of the window for all of the action bar
299            // and tabs.
300            topInset = mActionBarTop.getMeasuredHeight();
301        }
302
303        if (mActionBarView.isSplitActionBar()) {
304            // If action bar is split, adjust bottom insets for it.
305            if (mActionBarBottom != null) {
306                if (stable) {
307                    bottomInset = mActionBarHeight;
308                } else {
309                    bottomInset = mActionBarBottom.getMeasuredHeight();
310                }
311            }
312        }
313
314        // If the window has not requested system UI layout flags, we need to
315        // make sure its content is not being covered by system UI...  though it
316        // will still be covered by the action bar if they have requested it to
317        // overlay.
318        mContentInsets.set(mBaseContentInsets);
319        mInnerInsets.set(mBaseInnerInsets);
320        if (!mOverlayMode && !stable) {
321            mContentInsets.top += topInset;
322            mContentInsets.bottom += bottomInset;
323        } else {
324            mInnerInsets.top += topInset;
325            mInnerInsets.bottom += bottomInset;
326        }
327        applyInsets(mContent, mContentInsets, true, true, true, true);
328
329        if (!mLastInnerInsets.equals(mInnerInsets)) {
330            // If the inner insets have changed, we need to dispatch this down to
331            // the app's fitSystemWindows().  We do this before measuring the content
332            // view to keep the same semantics as the normal fitSystemWindows() call.
333            mLastInnerInsets.set(mInnerInsets);
334            mContent.dispatchApplyWindowInsets(new WindowInsets(mInnerInsets));
335        }
336
337        measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
338        lp = (LayoutParams) mContent.getLayoutParams();
339        maxWidth = Math.max(maxWidth,
340                mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
341        maxHeight = Math.max(maxHeight,
342                mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
343        childState = combineMeasuredStates(childState, mContent.getMeasuredState());
344
345        // Account for padding too
346        maxWidth += getPaddingLeft() + getPaddingRight();
347        maxHeight += getPaddingTop() + getPaddingBottom();
348
349        // Check against our minimum height and width
350        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
351        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
352
353        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
354                resolveSizeAndState(maxHeight, heightMeasureSpec,
355                        childState << MEASURED_HEIGHT_STATE_SHIFT));
356    }
357
358    @Override
359    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
360        final int count = getChildCount();
361
362        final int parentLeft = getPaddingLeft();
363        final int parentRight = right - left - getPaddingRight();
364
365        final int parentTop = getPaddingTop();
366        final int parentBottom = bottom - top - getPaddingBottom();
367
368        for (int i = 0; i < count; i++) {
369            final View child = getChildAt(i);
370            if (child.getVisibility() != GONE) {
371                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
372
373                final int width = child.getMeasuredWidth();
374                final int height = child.getMeasuredHeight();
375
376                int childLeft = parentLeft + lp.leftMargin;
377                int childTop;
378                if (child == mActionBarBottom) {
379                    childTop = parentBottom - height - lp.bottomMargin;
380                } else {
381                    childTop = parentTop + lp.topMargin;
382                }
383
384                child.layout(childLeft, childTop, childLeft + width, childTop + height);
385            }
386        }
387    }
388
389    @Override
390    public void draw(Canvas c) {
391        super.draw(c);
392        if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) {
393            final int top = mActionBarTop.getVisibility() == VISIBLE ?
394                    (int) (mActionBarTop.getBottom() + mActionBarTop.getTranslationY() + 0.5f) : 0;
395            mWindowContentOverlay.setBounds(0, top, getWidth(),
396                    top + mWindowContentOverlay.getIntrinsicHeight());
397            mWindowContentOverlay.draw(c);
398        }
399    }
400
401    @Override
402    public boolean shouldDelayChildPressedState() {
403        return false;
404    }
405
406    void pullChildren() {
407        if (mContent == null) {
408            mContent = findViewById(com.android.internal.R.id.content);
409            mActionBarTop = (ActionBarContainer)findViewById(
410                    com.android.internal.R.id.action_bar_container);
411            mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
412            mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
413        }
414    }
415
416
417    public static class LayoutParams extends MarginLayoutParams {
418        public LayoutParams(Context c, AttributeSet attrs) {
419            super(c, attrs);
420        }
421
422        public LayoutParams(int width, int height) {
423            super(width, height);
424        }
425
426        public LayoutParams(ViewGroup.LayoutParams source) {
427            super(source);
428        }
429
430        public LayoutParams(ViewGroup.MarginLayoutParams source) {
431            super(source);
432        }
433    }
434}
435