ActionBarContainer.java revision ac5fe7c617c66850fff75a9fce9979c6e5674b0f
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.os.Build;
25import androidx.annotation.RestrictTo;
26import androidx.core.view.ViewCompat;
27import androidx.appcompat.R;
28import android.util.AttributeSet;
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 */
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 = Build.VERSION.SDK_INT >= 21
62                ? new ActionBarBackgroundDrawableV21(this)
63                : new ActionBarBackgroundDrawable(this);
64        ViewCompat.setBackground(this, bg);
65
66        TypedArray a = context.obtainStyledAttributes(attrs,
67                R.styleable.ActionBar);
68        mBackground = a.getDrawable(R.styleable.ActionBar_background);
69        mStackedBackground = a.getDrawable(
70                R.styleable.ActionBar_backgroundStacked);
71        mHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, -1);
72
73        if (getId() == R.id.split_action_bar) {
74            mIsSplit = true;
75            mSplitBackground = a.getDrawable(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(R.id.action_bar);
87        mContextView = findViewById(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     * Set the action bar into a "transitioning" state. While transitioning the bar will block focus
188     * and touch from all of its descendants. This prevents the user from interacting with the bar
189     * while it is animating in or out.
190     *
191     * @param isTransitioning true if the bar is currently transitioning, false otherwise.
192     */
193    public void setTransitioning(boolean isTransitioning) {
194        mIsTransitioning = isTransitioning;
195        setDescendantFocusability(isTransitioning ? FOCUS_BLOCK_DESCENDANTS
196                : FOCUS_AFTER_DESCENDANTS);
197    }
198
199    @Override
200    public boolean onInterceptTouchEvent(MotionEvent ev) {
201        return mIsTransitioning || super.onInterceptTouchEvent(ev);
202    }
203
204    @Override
205    public boolean onTouchEvent(MotionEvent ev) {
206        super.onTouchEvent(ev);
207
208        // An action bar always eats touch events.
209        return true;
210    }
211
212    @Override
213    public boolean onHoverEvent(MotionEvent ev) {
214        super.onHoverEvent(ev);
215
216        // An action bar always eats hover events.
217        return true;
218    }
219
220    public void setTabContainer(ScrollingTabContainerView tabView) {
221        if (mTabContainer != null) {
222            removeView(mTabContainer);
223        }
224        mTabContainer = tabView;
225        if (tabView != null) {
226            addView(tabView);
227            final ViewGroup.LayoutParams lp = tabView.getLayoutParams();
228            lp.width = LayoutParams.MATCH_PARENT;
229            lp.height = LayoutParams.WRAP_CONTENT;
230            tabView.setAllowCollapse(false);
231        }
232    }
233
234    public View getTabContainer() {
235        return mTabContainer;
236    }
237
238    @Override
239    public android.view.ActionMode startActionModeForChild(View child,
240            android.view.ActionMode.Callback callback) {
241        // No starting an action mode for an action bar child! (Where would it go?)
242        return null;
243    }
244
245    @Override
246    public android.view.ActionMode startActionModeForChild(View child,
247            android.view.ActionMode.Callback callback, int type) {
248        if (type != android.view.ActionMode.TYPE_PRIMARY) {
249            return super.startActionModeForChild(child, callback, type);
250        }
251        return null;
252    }
253
254    private boolean isCollapsed(View view) {
255        return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
256    }
257
258    private int getMeasuredHeightWithMargins(View view) {
259        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
260        return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
261    }
262
263    @Override
264    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
265        if (mActionBarView == null &&
266                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) {
267            heightMeasureSpec = MeasureSpec.makeMeasureSpec(
268                    Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST);
269        }
270        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
271
272        if (mActionBarView == null) return;
273
274        final int mode = MeasureSpec.getMode(heightMeasureSpec);
275        if (mTabContainer != null && mTabContainer.getVisibility() != GONE
276                && mode != MeasureSpec.EXACTLY) {
277            final int topMarginForTabs;
278            if (!isCollapsed(mActionBarView)) {
279                topMarginForTabs = getMeasuredHeightWithMargins(mActionBarView);
280            } else if (!isCollapsed(mContextView)) {
281                topMarginForTabs = getMeasuredHeightWithMargins(mContextView);
282            } else {
283                topMarginForTabs = 0;
284            }
285            final int maxHeight = mode == MeasureSpec.AT_MOST ?
286                    MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE;
287            setMeasuredDimension(getMeasuredWidth(),
288                    Math.min(topMarginForTabs + getMeasuredHeightWithMargins(mTabContainer),
289                            maxHeight));
290        }
291    }
292
293    @Override
294    public void onLayout(boolean changed, int l, int t, int r, int b) {
295        super.onLayout(changed, l, t, r, b);
296
297        final View tabContainer = mTabContainer;
298        final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE;
299
300        if (tabContainer != null && tabContainer.getVisibility() != GONE) {
301            final int containerHeight = getMeasuredHeight();
302            final LayoutParams lp = (LayoutParams) tabContainer.getLayoutParams();
303            final int tabHeight = tabContainer.getMeasuredHeight();
304            tabContainer.layout(l, containerHeight - tabHeight - lp.bottomMargin, r,
305                    containerHeight - lp.bottomMargin);
306        }
307
308        boolean needsInvalidate = false;
309        if (mIsSplit) {
310            if (mSplitBackground != null) {
311                mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
312                needsInvalidate = true;
313            }
314        } else {
315            if (mBackground != null) {
316                if (mActionBarView.getVisibility() == View.VISIBLE) {
317                    mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
318                            mActionBarView.getRight(), mActionBarView.getBottom());
319                } else if (mContextView != null &&
320                        mContextView.getVisibility() == View.VISIBLE) {
321                    mBackground.setBounds(mContextView.getLeft(), mContextView.getTop(),
322                            mContextView.getRight(), mContextView.getBottom());
323                } else {
324                    mBackground.setBounds(0, 0, 0, 0);
325                }
326                needsInvalidate = true;
327            }
328            mIsStacked = hasTabs;
329            if (hasTabs && mStackedBackground != null) {
330                mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(),
331                        tabContainer.getRight(), tabContainer.getBottom());
332                needsInvalidate = true;
333            }
334        }
335
336        if (needsInvalidate) {
337            invalidate();
338        }
339    }
340}
341