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 
17 package androidx.appcompat.widget;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20 
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.graphics.drawable.Drawable;
24 import android.util.AttributeSet;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.FrameLayout;
29 
30 import androidx.annotation.RestrictTo;
31 import androidx.appcompat.R;
32 import 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)
40 public 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 
ActionBarContainer(Context context)53     public ActionBarContainer(Context context) {
54         this(context, null);
55     }
56 
ActionBarContainer(Context context, AttributeSet attrs)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
onFinishInflate()82     public void onFinishInflate() {
83         super.onFinishInflate();
84         mActionBarView = findViewById(R.id.action_bar);
85         mContextView = findViewById(R.id.action_context_bar);
86     }
87 
setPrimaryBackground(Drawable bg)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 
setStackedBackground(Drawable bg)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 
setSplitBackground(Drawable bg)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
setVisibility(int visibility)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
verifyDrawable(Drawable who)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
drawableStateChanged()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
jumpDrawablesToCurrentState()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      */
setTransitioning(boolean isTransitioning)191     public void setTransitioning(boolean isTransitioning) {
192         mIsTransitioning = isTransitioning;
193         setDescendantFocusability(isTransitioning ? FOCUS_BLOCK_DESCENDANTS
194                 : FOCUS_AFTER_DESCENDANTS);
195     }
196 
197     @Override
onInterceptTouchEvent(MotionEvent ev)198     public boolean onInterceptTouchEvent(MotionEvent ev) {
199         return mIsTransitioning || super.onInterceptTouchEvent(ev);
200     }
201 
202     @Override
onTouchEvent(MotionEvent ev)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
onHoverEvent(MotionEvent ev)211     public boolean onHoverEvent(MotionEvent ev) {
212         super.onHoverEvent(ev);
213 
214         // An action bar always eats hover events.
215         return true;
216     }
217 
setTabContainer(ScrollingTabContainerView tabView)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 
getTabContainer()232     public View getTabContainer() {
233         return mTabContainer;
234     }
235 
236     @Override
startActionModeForChild(View child, android.view.ActionMode.Callback callback)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
startActionModeForChild(View child, android.view.ActionMode.Callback callback, int type)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 
isCollapsed(View view)252     private boolean isCollapsed(View view) {
253         return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
254     }
255 
getMeasuredHeightWithMargins(View view)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
onLayout(boolean changed, int l, int t, int r, int b)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