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