ActionBarContainer.java revision 4423d91de5300d3fd318bf5bc2d4d7e5bb856abf
1/*
2 * Copyright (C) 2010 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.annotation.NonNull;
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.ColorFilter;
24import android.graphics.Outline;
25import android.graphics.PixelFormat;
26import android.graphics.drawable.Drawable;
27import android.util.AttributeSet;
28import android.view.ActionMode;
29import android.view.MotionEvent;
30import android.view.View;
31import android.view.ViewGroup;
32import android.widget.FrameLayout;
33
34/**
35 * This class acts as a container for the action bar view and action mode context views.
36 * It applies special styles as needed to help handle animated transitions between them.
37 * @hide
38 */
39public class ActionBarContainer extends FrameLayout {
40    private boolean mIsTransitioning;
41    private View mTabContainer;
42    private View mActionBarView;
43    private View mActionContextView;
44
45    private Drawable mBackground;
46    private Drawable mStackedBackground;
47    private Drawable mSplitBackground;
48    private boolean mIsSplit;
49    private boolean mIsStacked;
50    private int mHeight;
51
52    public ActionBarContainer(Context context) {
53        this(context, null);
54    }
55
56    public ActionBarContainer(Context context, AttributeSet attrs) {
57        super(context, attrs);
58
59        // Set a transparent background so that we project appropriately.
60        setBackground(new ActionBarBackgroundDrawable());
61
62        TypedArray a = context.obtainStyledAttributes(attrs,
63                com.android.internal.R.styleable.ActionBar);
64        mBackground = a.getDrawable(com.android.internal.R.styleable.ActionBar_background);
65        mStackedBackground = a.getDrawable(
66                com.android.internal.R.styleable.ActionBar_backgroundStacked);
67        mHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.ActionBar_height, -1);
68
69        if (getId() == com.android.internal.R.id.split_action_bar) {
70            mIsSplit = true;
71            mSplitBackground = a.getDrawable(
72                    com.android.internal.R.styleable.ActionBar_backgroundSplit);
73        }
74        a.recycle();
75
76        setWillNotDraw(mIsSplit ? mSplitBackground == null :
77                mBackground == null && mStackedBackground == null);
78    }
79
80    @Override
81    public void onFinishInflate() {
82        super.onFinishInflate();
83        mActionBarView = findViewById(com.android.internal.R.id.action_bar);
84        mActionContextView = findViewById(com.android.internal.R.id.action_context_bar);
85    }
86
87    public void setPrimaryBackground(Drawable bg) {
88        if (mBackground != null) {
89            mBackground.setCallback(null);
90            unscheduleDrawable(mBackground);
91        }
92        mBackground = bg;
93        if (bg != null) {
94            bg.setCallback(this);
95            if (mActionBarView != null) {
96                mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
97                        mActionBarView.getRight(), mActionBarView.getBottom());
98            }
99        }
100        setWillNotDraw(mIsSplit ? mSplitBackground == null :
101                mBackground == null && mStackedBackground == null);
102        invalidate();
103    }
104
105    public void setStackedBackground(Drawable bg) {
106        if (mStackedBackground != null) {
107            mStackedBackground.setCallback(null);
108            unscheduleDrawable(mStackedBackground);
109        }
110        mStackedBackground = bg;
111        if (bg != null) {
112            bg.setCallback(this);
113            if ((mIsStacked && mStackedBackground != null)) {
114                mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
115                        mTabContainer.getRight(), mTabContainer.getBottom());
116            }
117        }
118        setWillNotDraw(mIsSplit ? mSplitBackground == null :
119                mBackground == null && mStackedBackground == null);
120        invalidate();
121    }
122
123    public void setSplitBackground(Drawable bg) {
124        if (mSplitBackground != null) {
125            mSplitBackground.setCallback(null);
126            unscheduleDrawable(mSplitBackground);
127        }
128        mSplitBackground = bg;
129        if (bg != null) {
130            bg.setCallback(this);
131            if (mIsSplit && mSplitBackground != null) {
132                mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
133            }
134        }
135        setWillNotDraw(mIsSplit ? mSplitBackground == null :
136                mBackground == null && mStackedBackground == null);
137        invalidate();
138    }
139
140    @Override
141    public void setVisibility(int visibility) {
142        super.setVisibility(visibility);
143        final boolean isVisible = visibility == VISIBLE;
144        if (mBackground != null) mBackground.setVisible(isVisible, false);
145        if (mStackedBackground != null) mStackedBackground.setVisible(isVisible, false);
146        if (mSplitBackground != null) mSplitBackground.setVisible(isVisible, false);
147    }
148
149    @Override
150    protected boolean verifyDrawable(Drawable who) {
151        return (who == mBackground && !mIsSplit) || (who == mStackedBackground && mIsStacked) ||
152                (who == mSplitBackground && mIsSplit) || super.verifyDrawable(who);
153    }
154
155    @Override
156    protected void drawableStateChanged() {
157        super.drawableStateChanged();
158        if (mBackground != null && mBackground.isStateful()) {
159            mBackground.setState(getDrawableState());
160        }
161        if (mStackedBackground != null && mStackedBackground.isStateful()) {
162            mStackedBackground.setState(getDrawableState());
163        }
164        if (mSplitBackground != null && mSplitBackground.isStateful()) {
165            mSplitBackground.setState(getDrawableState());
166        }
167    }
168
169    @Override
170    public void jumpDrawablesToCurrentState() {
171        super.jumpDrawablesToCurrentState();
172        if (mBackground != null) {
173            mBackground.jumpToCurrentState();
174        }
175        if (mStackedBackground != null) {
176            mStackedBackground.jumpToCurrentState();
177        }
178        if (mSplitBackground != null) {
179            mSplitBackground.jumpToCurrentState();
180        }
181    }
182
183    /**
184     * @hide
185     */
186    @Override
187    public void onResolveDrawables(int layoutDirection) {
188        super.onResolveDrawables(layoutDirection);
189        if (mBackground != null) {
190            mBackground.setLayoutDirection(layoutDirection);
191        }
192        if (mStackedBackground != null) {
193            mStackedBackground.setLayoutDirection(layoutDirection);
194        }
195        if (mSplitBackground != null) {
196            mSplitBackground.setLayoutDirection(layoutDirection);
197        }
198    }
199
200    /**
201     * Set the action bar into a "transitioning" state. While transitioning
202     * the bar will block focus and touch from all of its descendants. This
203     * prevents the user from interacting with the bar while it is animating
204     * in or out.
205     *
206     * @param isTransitioning true if the bar is currently transitioning, false otherwise.
207     */
208    public void setTransitioning(boolean isTransitioning) {
209        mIsTransitioning = isTransitioning;
210        setDescendantFocusability(isTransitioning ? FOCUS_BLOCK_DESCENDANTS
211                : FOCUS_AFTER_DESCENDANTS);
212    }
213
214    @Override
215    public boolean onInterceptTouchEvent(MotionEvent ev) {
216        return mIsTransitioning || super.onInterceptTouchEvent(ev);
217    }
218
219    @Override
220    public boolean onTouchEvent(MotionEvent ev) {
221        super.onTouchEvent(ev);
222
223        // An action bar always eats touch events.
224        return true;
225    }
226
227    @Override
228    public boolean onHoverEvent(MotionEvent ev) {
229        super.onHoverEvent(ev);
230
231        // An action bar always eats hover events.
232        return true;
233    }
234
235    public void setTabContainer(ScrollingTabContainerView tabView) {
236        if (mTabContainer != null) {
237            removeView(mTabContainer);
238        }
239        mTabContainer = tabView;
240        if (tabView != null) {
241            addView(tabView);
242            final ViewGroup.LayoutParams lp = tabView.getLayoutParams();
243            lp.width = LayoutParams.MATCH_PARENT;
244            lp.height = LayoutParams.WRAP_CONTENT;
245            tabView.setAllowCollapse(false);
246        }
247    }
248
249    public View getTabContainer() {
250        return mTabContainer;
251    }
252
253    @Override
254    public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
255        // No starting an action mode for an action bar child! (Where would it go?)
256        return null;
257    }
258
259    @Override
260    public ActionMode startActionModeForChild(
261            View child, ActionMode.Callback callback, int type) {
262        if (type != ActionMode.TYPE_PRIMARY) {
263            return super.startActionModeForChild(child, callback, type);
264        }
265        return null;
266    }
267
268    private static boolean isCollapsed(View view) {
269        return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
270    }
271
272    private int getMeasuredHeightWithMargins(View view) {
273        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
274        return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
275    }
276
277    @Override
278    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
279        if (mActionBarView == null &&
280                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) {
281            heightMeasureSpec = MeasureSpec.makeMeasureSpec(
282                    Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST);
283        }
284        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
285
286        if (mActionBarView == null) return;
287
288        if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
289            int nonTabMaxHeight = 0;
290            final int childCount = getChildCount();
291            for (int i = 0; i < childCount; i++) {
292                final View child = getChildAt(i);
293                if (child == mTabContainer) {
294                    continue;
295                }
296                nonTabMaxHeight = Math.max(nonTabMaxHeight, isCollapsed(child) ? 0 :
297                        getMeasuredHeightWithMargins(child));
298            }
299            final int mode = MeasureSpec.getMode(heightMeasureSpec);
300            final int maxHeight = mode == MeasureSpec.AT_MOST ?
301                    MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE;
302            setMeasuredDimension(getMeasuredWidth(),
303                    Math.min(nonTabMaxHeight + getMeasuredHeightWithMargins(mTabContainer),
304                            maxHeight));
305        }
306    }
307
308    @Override
309    public void onLayout(boolean changed, int l, int t, int r, int b) {
310        super.onLayout(changed, l, t, r, b);
311
312        final View tabContainer = mTabContainer;
313        final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE;
314
315        if (tabContainer != null && tabContainer.getVisibility() != GONE) {
316            final int containerHeight = getMeasuredHeight();
317            final LayoutParams lp = (LayoutParams) tabContainer.getLayoutParams();
318            final int tabHeight = tabContainer.getMeasuredHeight();
319            tabContainer.layout(l, containerHeight - tabHeight - lp.bottomMargin, r,
320                    containerHeight - lp.bottomMargin);
321        }
322
323        boolean needsInvalidate = false;
324        if (mIsSplit) {
325            if (mSplitBackground != null) {
326                mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
327                needsInvalidate = true;
328            }
329        } else {
330            if (mBackground != null) {
331                if (mActionBarView.getVisibility() == View.VISIBLE) {
332                    mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
333                            mActionBarView.getRight(), mActionBarView.getBottom());
334                } else if (mActionContextView != null &&
335                        mActionContextView.getVisibility() == View.VISIBLE) {
336                    mBackground.setBounds(mActionContextView.getLeft(), mActionContextView.getTop(),
337                            mActionContextView.getRight(), mActionContextView.getBottom());
338                } else {
339                    mBackground.setBounds(0, 0, 0, 0);
340                }
341                needsInvalidate = true;
342            }
343            mIsStacked = hasTabs;
344            if (hasTabs && mStackedBackground != null) {
345                mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(),
346                        tabContainer.getRight(), tabContainer.getBottom());
347                needsInvalidate = true;
348            }
349        }
350
351        if (needsInvalidate) {
352            invalidate();
353        }
354    }
355
356    /**
357     * Dummy drawable so that we don't break background display lists and
358     * projection surfaces.
359     */
360    private class ActionBarBackgroundDrawable extends Drawable {
361        @Override
362        public void draw(Canvas canvas) {
363            if (mIsSplit) {
364                if (mSplitBackground != null) {
365                    mSplitBackground.draw(canvas);
366                }
367            } else {
368                if (mBackground != null) {
369                    mBackground.draw(canvas);
370                }
371                if (mStackedBackground != null && mIsStacked) {
372                    mStackedBackground.draw(canvas);
373                }
374            }
375        }
376
377        @Override
378        public void getOutline(@NonNull Outline outline) {
379            if (mIsSplit) {
380                if (mSplitBackground != null) {
381                    mSplitBackground.getOutline(outline);
382                }
383            } else {
384                // ignore the stacked background for shadow casting
385                if (mBackground != null) {
386                    mBackground.getOutline(outline);
387                }
388            }
389        }
390
391        @Override
392        public void setAlpha(int alpha) {
393        }
394
395        @Override
396        public void setColorFilter(ColorFilter colorFilter) {
397        }
398
399        @Override
400        public int getOpacity() {
401            if (mIsSplit) {
402                if (mSplitBackground != null
403                        && mSplitBackground.getOpacity() == PixelFormat.OPAQUE) {
404                    return PixelFormat.OPAQUE;
405                }
406            } else {
407                if (mIsStacked && (mStackedBackground == null
408                        || mStackedBackground.getOpacity() != PixelFormat.OPAQUE)) {
409                    return PixelFormat.UNKNOWN;
410                }
411                if (!isCollapsed(mActionBarView) && mBackground != null
412                        && mBackground.getOpacity() == PixelFormat.OPAQUE) {
413                    return PixelFormat.OPAQUE;
414                }
415            }
416
417            return PixelFormat.UNKNOWN;
418        }
419    }
420}
421