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