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