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 com.android.internal.widget; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.graphics.Canvas; 23 import android.graphics.ColorFilter; 24 import android.graphics.Outline; 25 import android.graphics.PixelFormat; 26 import android.graphics.drawable.Drawable; 27 import android.util.AttributeSet; 28 import android.view.ActionMode; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import 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 public class ActionBarContainer extends FrameLayout { 40 private boolean mIsTransitioning; 41 private View mTabContainer; 42 private View mActionBarView; 43 private View mActionContextView; 44 45 private Drawable mBackground; 46 private Drawable mStackedBackground; 47 private Drawable mSplitBackground; 48 private boolean mIsSplit; 49 private boolean mIsStacked; 50 private int mHeight; 51 ActionBarContainer(Context context)52 public ActionBarContainer(Context context) { 53 this(context, null); 54 } 55 ActionBarContainer(Context context, AttributeSet attrs)56 public ActionBarContainer(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 59 // Set a transparent background so that we project appropriately. 60 setBackground(new ActionBarBackgroundDrawable()); 61 62 TypedArray a = context.obtainStyledAttributes(attrs, 63 com.android.internal.R.styleable.ActionBar); 64 mBackground = a.getDrawable(com.android.internal.R.styleable.ActionBar_background); 65 mStackedBackground = a.getDrawable( 66 com.android.internal.R.styleable.ActionBar_backgroundStacked); 67 mHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.ActionBar_height, -1); 68 69 if (getId() == com.android.internal.R.id.split_action_bar) { 70 mIsSplit = true; 71 mSplitBackground = a.getDrawable( 72 com.android.internal.R.styleable.ActionBar_backgroundSplit); 73 } 74 a.recycle(); 75 76 setWillNotDraw(mIsSplit ? mSplitBackground == null : 77 mBackground == null && mStackedBackground == null); 78 } 79 80 @Override onFinishInflate()81 public void onFinishInflate() { 82 super.onFinishInflate(); 83 mActionBarView = findViewById(com.android.internal.R.id.action_bar); 84 mActionContextView = findViewById(com.android.internal.R.id.action_context_bar); 85 } 86 setPrimaryBackground(Drawable bg)87 public void setPrimaryBackground(Drawable bg) { 88 if (mBackground != null) { 89 mBackground.setCallback(null); 90 unscheduleDrawable(mBackground); 91 } 92 mBackground = bg; 93 if (bg != null) { 94 bg.setCallback(this); 95 if (mActionBarView != null) { 96 mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), 97 mActionBarView.getRight(), mActionBarView.getBottom()); 98 } 99 } 100 setWillNotDraw(mIsSplit ? mSplitBackground == null : 101 mBackground == null && mStackedBackground == null); 102 invalidate(); 103 } 104 setStackedBackground(Drawable bg)105 public void setStackedBackground(Drawable bg) { 106 if (mStackedBackground != null) { 107 mStackedBackground.setCallback(null); 108 unscheduleDrawable(mStackedBackground); 109 } 110 mStackedBackground = bg; 111 if (bg != null) { 112 bg.setCallback(this); 113 if ((mIsStacked && mStackedBackground != null)) { 114 mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(), 115 mTabContainer.getRight(), mTabContainer.getBottom()); 116 } 117 } 118 setWillNotDraw(mIsSplit ? mSplitBackground == null : 119 mBackground == null && mStackedBackground == null); 120 invalidate(); 121 } 122 setSplitBackground(Drawable bg)123 public void setSplitBackground(Drawable bg) { 124 if (mSplitBackground != null) { 125 mSplitBackground.setCallback(null); 126 unscheduleDrawable(mSplitBackground); 127 } 128 mSplitBackground = bg; 129 if (bg != null) { 130 bg.setCallback(this); 131 if (mIsSplit && mSplitBackground != null) { 132 mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); 133 } 134 } 135 setWillNotDraw(mIsSplit ? mSplitBackground == null : 136 mBackground == null && mStackedBackground == null); 137 invalidate(); 138 } 139 140 @Override setVisibility(int visibility)141 public void setVisibility(int visibility) { 142 super.setVisibility(visibility); 143 final boolean isVisible = visibility == VISIBLE; 144 if (mBackground != null) mBackground.setVisible(isVisible, false); 145 if (mStackedBackground != null) mStackedBackground.setVisible(isVisible, false); 146 if (mSplitBackground != null) mSplitBackground.setVisible(isVisible, false); 147 } 148 149 @Override verifyDrawable(@onNull Drawable who)150 protected boolean verifyDrawable(@NonNull Drawable who) { 151 return (who == mBackground && !mIsSplit) || (who == mStackedBackground && mIsStacked) || 152 (who == mSplitBackground && mIsSplit) || super.verifyDrawable(who); 153 } 154 155 @Override drawableStateChanged()156 protected void drawableStateChanged() { 157 super.drawableStateChanged(); 158 159 final int[] state = getDrawableState(); 160 boolean changed = false; 161 162 final Drawable background = mBackground; 163 if (background != null && background.isStateful()) { 164 changed |= background.setState(state); 165 } 166 167 final Drawable stackedBackground = mStackedBackground; 168 if (stackedBackground != null && stackedBackground.isStateful()) { 169 changed |= stackedBackground.setState(state); 170 } 171 172 final Drawable splitBackground = mSplitBackground; 173 if (splitBackground != null && splitBackground.isStateful()) { 174 changed |= splitBackground.setState(state); 175 } 176 177 if (changed) { 178 invalidate(); 179 } 180 } 181 182 @Override jumpDrawablesToCurrentState()183 public void jumpDrawablesToCurrentState() { 184 super.jumpDrawablesToCurrentState(); 185 if (mBackground != null) { 186 mBackground.jumpToCurrentState(); 187 } 188 if (mStackedBackground != null) { 189 mStackedBackground.jumpToCurrentState(); 190 } 191 if (mSplitBackground != null) { 192 mSplitBackground.jumpToCurrentState(); 193 } 194 } 195 196 /** 197 * @hide 198 */ 199 @Override onResolveDrawables(int layoutDirection)200 public void onResolveDrawables(int layoutDirection) { 201 super.onResolveDrawables(layoutDirection); 202 if (mBackground != null) { 203 mBackground.setLayoutDirection(layoutDirection); 204 } 205 if (mStackedBackground != null) { 206 mStackedBackground.setLayoutDirection(layoutDirection); 207 } 208 if (mSplitBackground != null) { 209 mSplitBackground.setLayoutDirection(layoutDirection); 210 } 211 } 212 213 /** 214 * Set the action bar into a "transitioning" state. While transitioning 215 * the bar will block focus and touch from all of its descendants. This 216 * prevents the user from interacting with the bar while it is animating 217 * in or out. 218 * 219 * @param isTransitioning true if the bar is currently transitioning, false otherwise. 220 */ setTransitioning(boolean isTransitioning)221 public void setTransitioning(boolean isTransitioning) { 222 mIsTransitioning = isTransitioning; 223 setDescendantFocusability(isTransitioning ? FOCUS_BLOCK_DESCENDANTS 224 : FOCUS_AFTER_DESCENDANTS); 225 } 226 227 @Override onInterceptTouchEvent(MotionEvent ev)228 public boolean onInterceptTouchEvent(MotionEvent ev) { 229 return mIsTransitioning || super.onInterceptTouchEvent(ev); 230 } 231 232 @Override onTouchEvent(MotionEvent ev)233 public boolean onTouchEvent(MotionEvent ev) { 234 super.onTouchEvent(ev); 235 236 // An action bar always eats touch events. 237 return true; 238 } 239 240 @Override onHoverEvent(MotionEvent ev)241 public boolean onHoverEvent(MotionEvent ev) { 242 super.onHoverEvent(ev); 243 244 // An action bar always eats hover events. 245 return true; 246 } 247 setTabContainer(ScrollingTabContainerView tabView)248 public void setTabContainer(ScrollingTabContainerView tabView) { 249 if (mTabContainer != null) { 250 removeView(mTabContainer); 251 } 252 mTabContainer = tabView; 253 if (tabView != null) { 254 addView(tabView); 255 final ViewGroup.LayoutParams lp = tabView.getLayoutParams(); 256 lp.width = LayoutParams.MATCH_PARENT; 257 lp.height = LayoutParams.WRAP_CONTENT; 258 tabView.setAllowCollapse(false); 259 } 260 } 261 getTabContainer()262 public View getTabContainer() { 263 return mTabContainer; 264 } 265 266 @Override startActionModeForChild( View child, ActionMode.Callback callback, int type)267 public ActionMode startActionModeForChild( 268 View child, ActionMode.Callback callback, int type) { 269 if (type != ActionMode.TYPE_PRIMARY) { 270 return super.startActionModeForChild(child, callback, type); 271 } 272 return null; 273 } 274 isCollapsed(View view)275 private static boolean isCollapsed(View view) { 276 return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0; 277 } 278 getMeasuredHeightWithMargins(View view)279 private int getMeasuredHeightWithMargins(View view) { 280 final LayoutParams lp = (LayoutParams) view.getLayoutParams(); 281 return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; 282 } 283 284 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)285 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 286 if (mActionBarView == null && 287 MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) { 288 heightMeasureSpec = MeasureSpec.makeMeasureSpec( 289 Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST); 290 } 291 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 292 293 if (mActionBarView == null) return; 294 295 if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { 296 int nonTabMaxHeight = 0; 297 final int childCount = getChildCount(); 298 for (int i = 0; i < childCount; i++) { 299 final View child = getChildAt(i); 300 if (child == mTabContainer) { 301 continue; 302 } 303 nonTabMaxHeight = Math.max(nonTabMaxHeight, isCollapsed(child) ? 0 : 304 getMeasuredHeightWithMargins(child)); 305 } 306 final int mode = MeasureSpec.getMode(heightMeasureSpec); 307 final int maxHeight = mode == MeasureSpec.AT_MOST ? 308 MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE; 309 setMeasuredDimension(getMeasuredWidth(), 310 Math.min(nonTabMaxHeight + getMeasuredHeightWithMargins(mTabContainer), 311 maxHeight)); 312 } 313 } 314 315 @Override onLayout(boolean changed, int l, int t, int r, int b)316 public void onLayout(boolean changed, int l, int t, int r, int b) { 317 super.onLayout(changed, l, t, r, b); 318 319 final View tabContainer = mTabContainer; 320 final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE; 321 322 if (tabContainer != null && tabContainer.getVisibility() != GONE) { 323 final int containerHeight = getMeasuredHeight(); 324 final LayoutParams lp = (LayoutParams) tabContainer.getLayoutParams(); 325 final int tabHeight = tabContainer.getMeasuredHeight(); 326 tabContainer.layout(l, containerHeight - tabHeight - lp.bottomMargin, r, 327 containerHeight - lp.bottomMargin); 328 } 329 330 boolean needsInvalidate = false; 331 if (mIsSplit) { 332 if (mSplitBackground != null) { 333 mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); 334 needsInvalidate = true; 335 } 336 } else { 337 if (mBackground != null) { 338 if (mActionBarView.getVisibility() == View.VISIBLE) { 339 mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), 340 mActionBarView.getRight(), mActionBarView.getBottom()); 341 } else if (mActionContextView != null && 342 mActionContextView.getVisibility() == View.VISIBLE) { 343 mBackground.setBounds(mActionContextView.getLeft(), mActionContextView.getTop(), 344 mActionContextView.getRight(), mActionContextView.getBottom()); 345 } else { 346 mBackground.setBounds(0, 0, 0, 0); 347 } 348 needsInvalidate = true; 349 } 350 mIsStacked = hasTabs; 351 if (hasTabs && mStackedBackground != null) { 352 mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(), 353 tabContainer.getRight(), tabContainer.getBottom()); 354 needsInvalidate = true; 355 } 356 } 357 358 if (needsInvalidate) { 359 invalidate(); 360 } 361 } 362 363 /** 364 * Dummy drawable so that we don't break background display lists and 365 * projection surfaces. 366 */ 367 private class ActionBarBackgroundDrawable extends Drawable { 368 @Override draw(Canvas canvas)369 public void draw(Canvas canvas) { 370 if (mIsSplit) { 371 if (mSplitBackground != null) { 372 mSplitBackground.draw(canvas); 373 } 374 } else { 375 if (mBackground != null) { 376 mBackground.draw(canvas); 377 } 378 if (mStackedBackground != null && mIsStacked) { 379 mStackedBackground.draw(canvas); 380 } 381 } 382 } 383 384 @Override getOutline(@onNull Outline outline)385 public void getOutline(@NonNull Outline outline) { 386 if (mIsSplit) { 387 if (mSplitBackground != null) { 388 mSplitBackground.getOutline(outline); 389 } 390 } else { 391 // ignore the stacked background for shadow casting 392 if (mBackground != null) { 393 mBackground.getOutline(outline); 394 } 395 } 396 } 397 398 @Override setAlpha(int alpha)399 public void setAlpha(int alpha) { 400 } 401 402 @Override setColorFilter(ColorFilter colorFilter)403 public void setColorFilter(ColorFilter colorFilter) { 404 } 405 406 @Override getOpacity()407 public int getOpacity() { 408 if (mIsSplit) { 409 if (mSplitBackground != null 410 && mSplitBackground.getOpacity() == PixelFormat.OPAQUE) { 411 return PixelFormat.OPAQUE; 412 } 413 } else { 414 if (mIsStacked && (mStackedBackground == null 415 || mStackedBackground.getOpacity() != PixelFormat.OPAQUE)) { 416 return PixelFormat.UNKNOWN; 417 } 418 if (!isCollapsed(mActionBarView) && mBackground != null 419 && mBackground.getOpacity() == PixelFormat.OPAQUE) { 420 return PixelFormat.OPAQUE; 421 } 422 } 423 424 return PixelFormat.UNKNOWN; 425 } 426 } 427 } 428