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 bg.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); 97 } 98 } 99 setWillNotDraw(mIsSplit ? mSplitBackground == null : 100 mBackground == null && mStackedBackground == null); 101 invalidate(); 102 } 103 setStackedBackground(Drawable bg)104 public void setStackedBackground(Drawable bg) { 105 if (mStackedBackground != null) { 106 mStackedBackground.setCallback(null); 107 unscheduleDrawable(mStackedBackground); 108 } 109 mStackedBackground = bg; 110 if (bg != null) { 111 bg.setCallback(this); 112 if ((mIsStacked && mStackedBackground != null)) { 113 mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(), 114 mTabContainer.getRight(), mTabContainer.getBottom()); 115 } 116 } 117 setWillNotDraw(mIsSplit ? mSplitBackground == null : 118 mBackground == null && mStackedBackground == null); 119 invalidate(); 120 } 121 setSplitBackground(Drawable bg)122 public void setSplitBackground(Drawable bg) { 123 if (mSplitBackground != null) { 124 mSplitBackground.setCallback(null); 125 unscheduleDrawable(mSplitBackground); 126 } 127 mSplitBackground = bg; 128 if (bg != null) { 129 bg.setCallback(this); 130 if (mIsSplit && mSplitBackground != null) { 131 mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); 132 } 133 } 134 setWillNotDraw(mIsSplit ? mSplitBackground == null : 135 mBackground == null && mStackedBackground == null); 136 invalidate(); 137 } 138 139 @Override setVisibility(int visibility)140 public void setVisibility(int visibility) { 141 super.setVisibility(visibility); 142 final boolean isVisible = visibility == VISIBLE; 143 if (mBackground != null) mBackground.setVisible(isVisible, false); 144 if (mStackedBackground != null) mStackedBackground.setVisible(isVisible, false); 145 if (mSplitBackground != null) mSplitBackground.setVisible(isVisible, false); 146 } 147 148 @Override verifyDrawable(@onNull Drawable who)149 protected boolean verifyDrawable(@NonNull Drawable who) { 150 return (who == mBackground && !mIsSplit) || (who == mStackedBackground && mIsStacked) || 151 (who == mSplitBackground && mIsSplit) || super.verifyDrawable(who); 152 } 153 154 @Override drawableStateChanged()155 protected void drawableStateChanged() { 156 super.drawableStateChanged(); 157 158 final int[] state = getDrawableState(); 159 boolean changed = false; 160 161 final Drawable background = mBackground; 162 if (background != null && background.isStateful()) { 163 changed |= background.setState(state); 164 } 165 166 final Drawable stackedBackground = mStackedBackground; 167 if (stackedBackground != null && stackedBackground.isStateful()) { 168 changed |= stackedBackground.setState(state); 169 } 170 171 final Drawable splitBackground = mSplitBackground; 172 if (splitBackground != null && splitBackground.isStateful()) { 173 changed |= splitBackground.setState(state); 174 } 175 176 if (changed) { 177 invalidate(); 178 } 179 } 180 181 @Override jumpDrawablesToCurrentState()182 public void jumpDrawablesToCurrentState() { 183 super.jumpDrawablesToCurrentState(); 184 if (mBackground != null) { 185 mBackground.jumpToCurrentState(); 186 } 187 if (mStackedBackground != null) { 188 mStackedBackground.jumpToCurrentState(); 189 } 190 if (mSplitBackground != null) { 191 mSplitBackground.jumpToCurrentState(); 192 } 193 } 194 195 /** 196 * @hide 197 */ 198 @Override onResolveDrawables(int layoutDirection)199 public void onResolveDrawables(int layoutDirection) { 200 super.onResolveDrawables(layoutDirection); 201 if (mBackground != null) { 202 mBackground.setLayoutDirection(layoutDirection); 203 } 204 if (mStackedBackground != null) { 205 mStackedBackground.setLayoutDirection(layoutDirection); 206 } 207 if (mSplitBackground != null) { 208 mSplitBackground.setLayoutDirection(layoutDirection); 209 } 210 } 211 212 /** 213 * Set the action bar into a "transitioning" state. While transitioning 214 * the bar will block focus and touch from all of its descendants. This 215 * prevents the user from interacting with the bar while it is animating 216 * in or out. 217 * 218 * @param isTransitioning true if the bar is currently transitioning, false otherwise. 219 */ setTransitioning(boolean isTransitioning)220 public void setTransitioning(boolean isTransitioning) { 221 mIsTransitioning = isTransitioning; 222 setDescendantFocusability(isTransitioning ? FOCUS_BLOCK_DESCENDANTS 223 : FOCUS_AFTER_DESCENDANTS); 224 } 225 226 @Override onInterceptTouchEvent(MotionEvent ev)227 public boolean onInterceptTouchEvent(MotionEvent ev) { 228 return mIsTransitioning || super.onInterceptTouchEvent(ev); 229 } 230 231 @Override onTouchEvent(MotionEvent ev)232 public boolean onTouchEvent(MotionEvent ev) { 233 super.onTouchEvent(ev); 234 235 // An action bar always eats touch events. 236 return true; 237 } 238 239 @Override onHoverEvent(MotionEvent ev)240 public boolean onHoverEvent(MotionEvent ev) { 241 super.onHoverEvent(ev); 242 243 // An action bar always eats hover events. 244 return true; 245 } 246 setTabContainer(ScrollingTabContainerView tabView)247 public void setTabContainer(ScrollingTabContainerView tabView) { 248 if (mTabContainer != null) { 249 removeView(mTabContainer); 250 } 251 mTabContainer = tabView; 252 if (tabView != null) { 253 addView(tabView); 254 final ViewGroup.LayoutParams lp = tabView.getLayoutParams(); 255 lp.width = LayoutParams.MATCH_PARENT; 256 lp.height = LayoutParams.WRAP_CONTENT; 257 tabView.setAllowCollapse(false); 258 } 259 } 260 getTabContainer()261 public View getTabContainer() { 262 return mTabContainer; 263 } 264 265 @Override startActionModeForChild( View child, ActionMode.Callback callback, int type)266 public ActionMode startActionModeForChild( 267 View child, ActionMode.Callback callback, int type) { 268 if (type != ActionMode.TYPE_PRIMARY) { 269 return super.startActionModeForChild(child, callback, type); 270 } 271 return null; 272 } 273 isCollapsed(View view)274 private static boolean isCollapsed(View view) { 275 return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0; 276 } 277 getMeasuredHeightWithMargins(View view)278 private int getMeasuredHeightWithMargins(View view) { 279 final LayoutParams lp = (LayoutParams) view.getLayoutParams(); 280 return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; 281 } 282 283 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)284 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 285 if (mActionBarView == null && 286 MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) { 287 heightMeasureSpec = MeasureSpec.makeMeasureSpec( 288 Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST); 289 } 290 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 291 292 if (mActionBarView == null) return; 293 294 if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { 295 final int verticalPadding = getPaddingTop() + getPaddingBottom(); 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( 311 verticalPadding + nonTabMaxHeight 312 + getMeasuredHeightWithMargins(mTabContainer), 313 maxHeight)); 314 } 315 } 316 317 @Override onLayout(boolean changed, int l, int t, int r, int b)318 public void onLayout(boolean changed, int l, int t, int r, int b) { 319 super.onLayout(changed, l, t, r, b); 320 321 final View tabContainer = mTabContainer; 322 final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE; 323 324 if (tabContainer != null && tabContainer.getVisibility() != GONE) { 325 final int containerHeight = getMeasuredHeight(); 326 final LayoutParams lp = (LayoutParams) tabContainer.getLayoutParams(); 327 final int tabHeight = tabContainer.getMeasuredHeight(); 328 tabContainer.layout(l, containerHeight - tabHeight - lp.bottomMargin, r, 329 containerHeight - lp.bottomMargin); 330 } 331 332 boolean needsInvalidate = false; 333 if (mIsSplit) { 334 if (mSplitBackground != null) { 335 mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); 336 needsInvalidate = true; 337 } 338 } else { 339 if (mBackground != null) { 340 if ((mActionBarView.getVisibility() == View.VISIBLE) || (mActionContextView != null 341 && mActionContextView.getVisibility() == View.VISIBLE)) { 342 mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); 343 } else { 344 mBackground.setBounds(0, 0, 0, 0); 345 } 346 needsInvalidate = true; 347 } 348 mIsStacked = hasTabs; 349 if (hasTabs && mStackedBackground != null) { 350 mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(), 351 tabContainer.getRight(), tabContainer.getBottom()); 352 needsInvalidate = true; 353 } 354 } 355 356 if (needsInvalidate) { 357 invalidate(); 358 } 359 } 360 361 /** 362 * Placeholder drawable so that we don't break background display lists and 363 * projection surfaces. 364 */ 365 private class ActionBarBackgroundDrawable extends Drawable { 366 @Override draw(Canvas canvas)367 public void draw(Canvas canvas) { 368 if (mIsSplit) { 369 if (mSplitBackground != null) { 370 mSplitBackground.draw(canvas); 371 } 372 } else { 373 if (mBackground != null) { 374 mBackground.draw(canvas); 375 } 376 if (mStackedBackground != null && mIsStacked) { 377 mStackedBackground.draw(canvas); 378 } 379 } 380 } 381 382 @Override getOutline(@onNull Outline outline)383 public void getOutline(@NonNull Outline outline) { 384 if (mIsSplit) { 385 if (mSplitBackground != null) { 386 mSplitBackground.getOutline(outline); 387 } 388 } else { 389 // ignore the stacked background for shadow casting 390 if (mBackground != null) { 391 mBackground.getOutline(outline); 392 } 393 } 394 } 395 396 @Override setAlpha(int alpha)397 public void setAlpha(int alpha) { 398 } 399 400 @Override setColorFilter(ColorFilter colorFilter)401 public void setColorFilter(ColorFilter colorFilter) { 402 } 403 404 @Override getOpacity()405 public int getOpacity() { 406 if (mIsSplit) { 407 if (mSplitBackground != null 408 && mSplitBackground.getOpacity() == PixelFormat.OPAQUE) { 409 return PixelFormat.OPAQUE; 410 } 411 } else { 412 if (mIsStacked && (mStackedBackground == null 413 || mStackedBackground.getOpacity() != PixelFormat.OPAQUE)) { 414 return PixelFormat.UNKNOWN; 415 } 416 if (!isCollapsed(mActionBarView) && mBackground != null 417 && mBackground.getOpacity() == PixelFormat.OPAQUE) { 418 return PixelFormat.OPAQUE; 419 } 420 } 421 422 return PixelFormat.UNKNOWN; 423 } 424 } 425 } 426