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 android.support.v7.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.drawable.Drawable;
22import android.os.Build;
23import android.support.annotation.RestrictTo;
24import android.support.v4.view.ViewCompat;
25import android.support.v7.appcompat.R;
26import android.support.v7.view.ActionMode;
27import android.util.AttributeSet;
28import android.view.MotionEvent;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.FrameLayout;
32
33import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
34
35/**
36 * This class acts as a container for the action bar view and action mode context views.
37 * It applies special styles as needed to help handle animated transitions between them.
38 * @hide
39 */
40@RestrictTo(GROUP_ID)
41public class ActionBarContainer extends FrameLayout {
42    private boolean mIsTransitioning;
43    private View mTabContainer;
44    private View mActionBarView;
45    private View mContextView;
46
47    Drawable mBackground;
48    Drawable mStackedBackground;
49    Drawable mSplitBackground;
50    boolean mIsSplit;
51    boolean mIsStacked;
52    private int mHeight;
53
54    public ActionBarContainer(Context context) {
55        this(context, null);
56    }
57
58    public ActionBarContainer(Context context, AttributeSet attrs) {
59        super(context, attrs);
60
61        // Set a transparent background so that we project appropriately.
62        final Drawable bg = Build.VERSION.SDK_INT >= 21
63                ? new ActionBarBackgroundDrawableV21(this)
64                : new ActionBarBackgroundDrawable(this);
65        ViewCompat.setBackground(this, bg);
66
67        TypedArray a = context.obtainStyledAttributes(attrs,
68                R.styleable.ActionBar);
69        mBackground = a.getDrawable(R.styleable.ActionBar_background);
70        mStackedBackground = a.getDrawable(
71                R.styleable.ActionBar_backgroundStacked);
72        mHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, -1);
73
74        if (getId() == R.id.split_action_bar) {
75            mIsSplit = true;
76            mSplitBackground = a.getDrawable(R.styleable.ActionBar_backgroundSplit);
77        }
78        a.recycle();
79
80        setWillNotDraw(mIsSplit ? mSplitBackground == null :
81                mBackground == null && mStackedBackground == null);
82    }
83
84    @Override
85    public void onFinishInflate() {
86        super.onFinishInflate();
87        mActionBarView = findViewById(R.id.action_bar);
88        mContextView = findViewById(R.id.action_context_bar);
89    }
90
91    public void setPrimaryBackground(Drawable bg) {
92        if (mBackground != null) {
93            mBackground.setCallback(null);
94            unscheduleDrawable(mBackground);
95        }
96        mBackground = bg;
97        if (bg != null) {
98            bg.setCallback(this);
99            if (mActionBarView != null) {
100                mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
101                        mActionBarView.getRight(), mActionBarView.getBottom());
102            }
103        }
104        setWillNotDraw(mIsSplit ? mSplitBackground == null :
105                mBackground == null && mStackedBackground == null);
106        invalidate();
107    }
108
109    public void setStackedBackground(Drawable bg) {
110        if (mStackedBackground != null) {
111            mStackedBackground.setCallback(null);
112            unscheduleDrawable(mStackedBackground);
113        }
114        mStackedBackground = bg;
115        if (bg != null) {
116            bg.setCallback(this);
117            if ((mIsStacked && mStackedBackground != null)) {
118                mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
119                        mTabContainer.getRight(), mTabContainer.getBottom());
120            }
121        }
122        setWillNotDraw(mIsSplit ? mSplitBackground == null :
123                mBackground == null && mStackedBackground == null);
124        invalidate();
125    }
126
127    public void setSplitBackground(Drawable bg) {
128        if (mSplitBackground != null) {
129            mSplitBackground.setCallback(null);
130            unscheduleDrawable(mSplitBackground);
131        }
132        mSplitBackground = bg;
133        if (bg != null) {
134            bg.setCallback(this);
135            if (mIsSplit && mSplitBackground != null) {
136                mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
137            }
138        }
139        setWillNotDraw(mIsSplit ? mSplitBackground == null :
140                mBackground == null && mStackedBackground == null);
141        invalidate();
142    }
143
144    @Override
145    public void setVisibility(int visibility) {
146        super.setVisibility(visibility);
147        final boolean isVisible = visibility == VISIBLE;
148        if (mBackground != null) mBackground.setVisible(isVisible, false);
149        if (mStackedBackground != null) mStackedBackground.setVisible(isVisible, false);
150        if (mSplitBackground != null) mSplitBackground.setVisible(isVisible, false);
151    }
152
153    @Override
154    protected boolean verifyDrawable(Drawable who) {
155        return (who == mBackground && !mIsSplit) || (who == mStackedBackground && mIsStacked) ||
156                (who == mSplitBackground && mIsSplit) || super.verifyDrawable(who);
157    }
158
159    @Override
160    protected void drawableStateChanged() {
161        super.drawableStateChanged();
162        if (mBackground != null && mBackground.isStateful()) {
163            mBackground.setState(getDrawableState());
164        }
165        if (mStackedBackground != null && mStackedBackground.isStateful()) {
166            mStackedBackground.setState(getDrawableState());
167        }
168        if (mSplitBackground != null && mSplitBackground.isStateful()) {
169            mSplitBackground.setState(getDrawableState());
170        }
171    }
172
173    public void jumpDrawablesToCurrentState() {
174        if (Build.VERSION.SDK_INT >= 11) {
175            super.jumpDrawablesToCurrentState();
176            if (mBackground != null) {
177                mBackground.jumpToCurrentState();
178            }
179            if (mStackedBackground != null) {
180                mStackedBackground.jumpToCurrentState();
181            }
182            if (mSplitBackground != null) {
183                mSplitBackground.jumpToCurrentState();
184            }
185        }
186    }
187
188    /**
189     * Set the action bar into a "transitioning" state. While transitioning the bar will block focus
190     * and touch from all of its descendants. This prevents the user from interacting with the bar
191     * while it is animating in or out.
192     *
193     * @param isTransitioning true if the bar is currently transitioning, false otherwise.
194     */
195    public void setTransitioning(boolean isTransitioning) {
196        mIsTransitioning = isTransitioning;
197        setDescendantFocusability(isTransitioning ? FOCUS_BLOCK_DESCENDANTS
198                : FOCUS_AFTER_DESCENDANTS);
199    }
200
201    @Override
202    public boolean onInterceptTouchEvent(MotionEvent ev) {
203        return mIsTransitioning || super.onInterceptTouchEvent(ev);
204    }
205
206    @Override
207    public boolean onTouchEvent(MotionEvent ev) {
208        super.onTouchEvent(ev);
209
210        // An action bar always eats touch events.
211        return true;
212    }
213
214    public void setTabContainer(ScrollingTabContainerView tabView) {
215        if (mTabContainer != null) {
216            removeView(mTabContainer);
217        }
218        mTabContainer = tabView;
219        if (tabView != null) {
220            addView(tabView);
221            final ViewGroup.LayoutParams lp = tabView.getLayoutParams();
222            lp.width = LayoutParams.MATCH_PARENT;
223            lp.height = LayoutParams.WRAP_CONTENT;
224            tabView.setAllowCollapse(false);
225        }
226    }
227
228    public View getTabContainer() {
229        return mTabContainer;
230    }
231
232    public android.view.ActionMode startActionModeForChild(View child,
233            android.view.ActionMode.Callback callback) {
234        // No starting an action mode for an action bar child! (Where would it go?)
235        return null;
236    }
237
238    public android.view.ActionMode startActionModeForChild(View child,
239            android.view.ActionMode.Callback callback, int type) {
240        if (type != android.view.ActionMode.TYPE_PRIMARY) {
241            return super.startActionModeForChild(child, callback, type);
242        }
243        return null;
244    }
245
246    private boolean isCollapsed(View view) {
247        return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
248    }
249
250    private int getMeasuredHeightWithMargins(View view) {
251        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
252        return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
253    }
254
255    @Override
256    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
257        if (mActionBarView == null &&
258                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) {
259            heightMeasureSpec = MeasureSpec.makeMeasureSpec(
260                    Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST);
261        }
262        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
263
264        if (mActionBarView == null) return;
265
266        final int mode = MeasureSpec.getMode(heightMeasureSpec);
267        if (mTabContainer != null && mTabContainer.getVisibility() != GONE
268                && mode != MeasureSpec.EXACTLY) {
269            final int topMarginForTabs;
270            if (!isCollapsed(mActionBarView)) {
271                topMarginForTabs = getMeasuredHeightWithMargins(mActionBarView);
272            } else if (!isCollapsed(mContextView)) {
273                topMarginForTabs = getMeasuredHeightWithMargins(mContextView);
274            } else {
275                topMarginForTabs = 0;
276            }
277            final int maxHeight = mode == MeasureSpec.AT_MOST ?
278                    MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE;
279            setMeasuredDimension(getMeasuredWidth(),
280                    Math.min(topMarginForTabs + getMeasuredHeightWithMargins(mTabContainer),
281                            maxHeight));
282        }
283    }
284
285    @Override
286    public void onLayout(boolean changed, int l, int t, int r, int b) {
287        super.onLayout(changed, l, t, r, b);
288
289        final View tabContainer = mTabContainer;
290        final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE;
291
292        if (tabContainer != null && tabContainer.getVisibility() != GONE) {
293            final int containerHeight = getMeasuredHeight();
294            final LayoutParams lp = (LayoutParams) tabContainer.getLayoutParams();
295            final int tabHeight = tabContainer.getMeasuredHeight();
296            tabContainer.layout(l, containerHeight - tabHeight - lp.bottomMargin, r,
297                    containerHeight - lp.bottomMargin);
298        }
299
300        boolean needsInvalidate = false;
301        if (mIsSplit) {
302            if (mSplitBackground != null) {
303                mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
304                needsInvalidate = true;
305            }
306        } else {
307            if (mBackground != null) {
308                if (mActionBarView.getVisibility() == View.VISIBLE) {
309                    mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
310                            mActionBarView.getRight(), mActionBarView.getBottom());
311                } else if (mContextView != null &&
312                        mContextView.getVisibility() == View.VISIBLE) {
313                    mBackground.setBounds(mContextView.getLeft(), mContextView.getTop(),
314                            mContextView.getRight(), mContextView.getBottom());
315                } else {
316                    mBackground.setBounds(0, 0, 0, 0);
317                }
318                needsInvalidate = true;
319            }
320            mIsStacked = hasTabs;
321            if (hasTabs && mStackedBackground != null) {
322                mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(),
323                        tabContainer.getRight(), tabContainer.getBottom());
324                needsInvalidate = true;
325            }
326        }
327
328        if (needsInvalidate) {
329            invalidate();
330        }
331    }
332}
333