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 package com.android.internal.widget; 17 18 import com.android.internal.R; 19 import com.android.internal.view.menu.ActionMenuPresenter; 20 import com.android.internal.view.menu.ActionMenuView; 21 import com.android.internal.view.menu.MenuBuilder; 22 23 import android.animation.Animator; 24 import android.animation.Animator.AnimatorListener; 25 import android.animation.AnimatorSet; 26 import android.animation.ObjectAnimator; 27 import android.content.Context; 28 import android.content.res.Configuration; 29 import android.content.res.TypedArray; 30 import android.graphics.drawable.Drawable; 31 import android.text.TextUtils; 32 import android.util.AttributeSet; 33 import android.view.ActionMode; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.view.accessibility.AccessibilityEvent; 38 import android.view.animation.DecelerateInterpolator; 39 import android.widget.LinearLayout; 40 import android.widget.TextView; 41 42 /** 43 * @hide 44 */ 45 public class ActionBarContextView extends AbsActionBarView implements AnimatorListener { 46 private static final String TAG = "ActionBarContextView"; 47 48 private CharSequence mTitle; 49 private CharSequence mSubtitle; 50 51 private View mClose; 52 private View mCustomView; 53 private LinearLayout mTitleLayout; 54 private TextView mTitleView; 55 private TextView mSubtitleView; 56 private int mTitleStyleRes; 57 private int mSubtitleStyleRes; 58 private Drawable mSplitBackground; 59 private boolean mTitleOptional; 60 61 private Animator mCurrentAnimation; 62 private boolean mAnimateInOnLayout; 63 private int mAnimationMode; 64 65 private static final int ANIMATE_IDLE = 0; 66 private static final int ANIMATE_IN = 1; 67 private static final int ANIMATE_OUT = 2; 68 69 public ActionBarContextView(Context context) { 70 this(context, null); 71 } 72 73 public ActionBarContextView(Context context, AttributeSet attrs) { 74 this(context, attrs, com.android.internal.R.attr.actionModeStyle); 75 } 76 77 public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) { 78 super(context, attrs, defStyle); 79 80 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0); 81 setBackgroundDrawable(a.getDrawable( 82 com.android.internal.R.styleable.ActionMode_background)); 83 mTitleStyleRes = a.getResourceId( 84 com.android.internal.R.styleable.ActionMode_titleTextStyle, 0); 85 mSubtitleStyleRes = a.getResourceId( 86 com.android.internal.R.styleable.ActionMode_subtitleTextStyle, 0); 87 88 mContentHeight = a.getLayoutDimension( 89 com.android.internal.R.styleable.ActionMode_height, 0); 90 91 mSplitBackground = a.getDrawable( 92 com.android.internal.R.styleable.ActionMode_backgroundSplit); 93 94 a.recycle(); 95 } 96 97 @Override 98 public void onDetachedFromWindow() { 99 super.onDetachedFromWindow(); 100 if (mActionMenuPresenter != null) { 101 mActionMenuPresenter.hideOverflowMenu(); 102 mActionMenuPresenter.hideSubMenus(); 103 } 104 } 105 106 @Override 107 public void setSplitActionBar(boolean split) { 108 if (mSplitActionBar != split) { 109 if (mActionMenuPresenter != null) { 110 // Mode is already active; move everything over and adjust the menu itself. 111 final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, 112 LayoutParams.MATCH_PARENT); 113 if (!split) { 114 mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); 115 mMenuView.setBackgroundDrawable(null); 116 final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); 117 if (oldParent != null) oldParent.removeView(mMenuView); 118 addView(mMenuView, layoutParams); 119 } else { 120 // Allow full screen width in split mode. 121 mActionMenuPresenter.setWidthLimit( 122 getContext().getResources().getDisplayMetrics().widthPixels, true); 123 // No limit to the item count; use whatever will fit. 124 mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); 125 // Span the whole width 126 layoutParams.width = LayoutParams.MATCH_PARENT; 127 layoutParams.height = mContentHeight; 128 mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); 129 mMenuView.setBackgroundDrawable(mSplitBackground); 130 final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); 131 if (oldParent != null) oldParent.removeView(mMenuView); 132 mSplitView.addView(mMenuView, layoutParams); 133 } 134 } 135 super.setSplitActionBar(split); 136 } 137 } 138 139 public void setContentHeight(int height) { 140 mContentHeight = height; 141 } 142 143 public void setCustomView(View view) { 144 if (mCustomView != null) { 145 removeView(mCustomView); 146 } 147 mCustomView = view; 148 if (mTitleLayout != null) { 149 removeView(mTitleLayout); 150 mTitleLayout = null; 151 } 152 if (view != null) { 153 addView(view); 154 } 155 requestLayout(); 156 } 157 158 public void setTitle(CharSequence title) { 159 mTitle = title; 160 initTitle(); 161 } 162 163 public void setSubtitle(CharSequence subtitle) { 164 mSubtitle = subtitle; 165 initTitle(); 166 } 167 168 public CharSequence getTitle() { 169 return mTitle; 170 } 171 172 public CharSequence getSubtitle() { 173 return mSubtitle; 174 } 175 176 private void initTitle() { 177 if (mTitleLayout == null) { 178 LayoutInflater inflater = LayoutInflater.from(getContext()); 179 inflater.inflate(R.layout.action_bar_title_item, this); 180 mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1); 181 mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title); 182 mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle); 183 if (mTitleStyleRes != 0) { 184 mTitleView.setTextAppearance(mContext, mTitleStyleRes); 185 } 186 if (mSubtitleStyleRes != 0) { 187 mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes); 188 } 189 } 190 191 mTitleView.setText(mTitle); 192 mSubtitleView.setText(mSubtitle); 193 194 final boolean hasTitle = !TextUtils.isEmpty(mTitle); 195 final boolean hasSubtitle = !TextUtils.isEmpty(mSubtitle); 196 mSubtitleView.setVisibility(hasSubtitle ? VISIBLE : GONE); 197 mTitleLayout.setVisibility(hasTitle || hasSubtitle ? VISIBLE : GONE); 198 if (mTitleLayout.getParent() == null) { 199 addView(mTitleLayout); 200 } 201 } 202 203 public void initForMode(final ActionMode mode) { 204 if (mClose == null) { 205 LayoutInflater inflater = LayoutInflater.from(mContext); 206 mClose = inflater.inflate(R.layout.action_mode_close_item, this, false); 207 addView(mClose); 208 } else if (mClose.getParent() == null) { 209 addView(mClose); 210 } 211 212 View closeButton = mClose.findViewById(R.id.action_mode_close_button); 213 closeButton.setOnClickListener(new OnClickListener() { 214 public void onClick(View v) { 215 mode.finish(); 216 } 217 }); 218 219 final MenuBuilder menu = (MenuBuilder) mode.getMenu(); 220 if (mActionMenuPresenter != null) { 221 mActionMenuPresenter.dismissPopupMenus(); 222 } 223 mActionMenuPresenter = new ActionMenuPresenter(mContext); 224 mActionMenuPresenter.setReserveOverflow(true); 225 226 final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, 227 LayoutParams.MATCH_PARENT); 228 if (!mSplitActionBar) { 229 menu.addMenuPresenter(mActionMenuPresenter); 230 mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); 231 mMenuView.setBackgroundDrawable(null); 232 addView(mMenuView, layoutParams); 233 } else { 234 // Allow full screen width in split mode. 235 mActionMenuPresenter.setWidthLimit( 236 getContext().getResources().getDisplayMetrics().widthPixels, true); 237 // No limit to the item count; use whatever will fit. 238 mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); 239 // Span the whole width 240 layoutParams.width = LayoutParams.MATCH_PARENT; 241 layoutParams.height = mContentHeight; 242 menu.addMenuPresenter(mActionMenuPresenter); 243 mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); 244 mMenuView.setBackgroundDrawable(mSplitBackground); 245 mSplitView.addView(mMenuView, layoutParams); 246 } 247 248 mAnimateInOnLayout = true; 249 } 250 251 public void closeMode() { 252 if (mAnimationMode == ANIMATE_OUT) { 253 // Called again during close; just finish what we were doing. 254 return; 255 } 256 if (mClose == null) { 257 killMode(); 258 return; 259 } 260 261 finishAnimation(); 262 mAnimationMode = ANIMATE_OUT; 263 mCurrentAnimation = makeOutAnimation(); 264 mCurrentAnimation.start(); 265 } 266 267 private void finishAnimation() { 268 final Animator a = mCurrentAnimation; 269 if (a != null) { 270 mCurrentAnimation = null; 271 a.end(); 272 } 273 } 274 275 public void killMode() { 276 finishAnimation(); 277 removeAllViews(); 278 if (mSplitView != null) { 279 mSplitView.removeView(mMenuView); 280 } 281 mCustomView = null; 282 mMenuView = null; 283 mAnimateInOnLayout = false; 284 } 285 286 @Override 287 public boolean showOverflowMenu() { 288 if (mActionMenuPresenter != null) { 289 return mActionMenuPresenter.showOverflowMenu(); 290 } 291 return false; 292 } 293 294 @Override 295 public boolean hideOverflowMenu() { 296 if (mActionMenuPresenter != null) { 297 return mActionMenuPresenter.hideOverflowMenu(); 298 } 299 return false; 300 } 301 302 @Override 303 public boolean isOverflowMenuShowing() { 304 if (mActionMenuPresenter != null) { 305 return mActionMenuPresenter.isOverflowMenuShowing(); 306 } 307 return false; 308 } 309 310 @Override 311 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 312 // Used by custom views if they don't supply layout params. Everything else 313 // added to an ActionBarContextView should have them already. 314 return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 315 } 316 317 @Override 318 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 319 return new MarginLayoutParams(getContext(), attrs); 320 } 321 322 @Override 323 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 324 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 325 if (widthMode != MeasureSpec.EXACTLY) { 326 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 327 "with android:layout_width=\"match_parent\" (or fill_parent)"); 328 } 329 330 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 331 if (heightMode == MeasureSpec.UNSPECIFIED) { 332 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 333 "with android:layout_height=\"wrap_content\""); 334 } 335 336 final int contentWidth = MeasureSpec.getSize(widthMeasureSpec); 337 338 int maxHeight = mContentHeight > 0 ? 339 mContentHeight : MeasureSpec.getSize(heightMeasureSpec); 340 341 final int verticalPadding = getPaddingTop() + getPaddingBottom(); 342 int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight(); 343 final int height = maxHeight - verticalPadding; 344 final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); 345 346 if (mClose != null) { 347 availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0); 348 MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams(); 349 availableWidth -= lp.leftMargin + lp.rightMargin; 350 } 351 352 if (mMenuView != null && mMenuView.getParent() == this) { 353 availableWidth = measureChildView(mMenuView, availableWidth, 354 childSpecHeight, 0); 355 } 356 357 if (mTitleLayout != null && mCustomView == null) { 358 if (mTitleOptional) { 359 final int titleWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 360 mTitleLayout.measure(titleWidthSpec, childSpecHeight); 361 final int titleWidth = mTitleLayout.getMeasuredWidth(); 362 final boolean titleFits = titleWidth <= availableWidth; 363 if (titleFits) { 364 availableWidth -= titleWidth; 365 } 366 mTitleLayout.setVisibility(titleFits ? VISIBLE : GONE); 367 } else { 368 availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0); 369 } 370 } 371 372 if (mCustomView != null) { 373 ViewGroup.LayoutParams lp = mCustomView.getLayoutParams(); 374 final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ? 375 MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; 376 final int customWidth = lp.width >= 0 ? 377 Math.min(lp.width, availableWidth) : availableWidth; 378 final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ? 379 MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; 380 final int customHeight = lp.height >= 0 ? 381 Math.min(lp.height, height) : height; 382 mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode), 383 MeasureSpec.makeMeasureSpec(customHeight, customHeightMode)); 384 } 385 386 if (mContentHeight <= 0) { 387 int measuredHeight = 0; 388 final int count = getChildCount(); 389 for (int i = 0; i < count; i++) { 390 View v = getChildAt(i); 391 int paddedViewHeight = v.getMeasuredHeight() + verticalPadding; 392 if (paddedViewHeight > measuredHeight) { 393 measuredHeight = paddedViewHeight; 394 } 395 } 396 setMeasuredDimension(contentWidth, measuredHeight); 397 } else { 398 setMeasuredDimension(contentWidth, maxHeight); 399 } 400 } 401 402 private Animator makeInAnimation() { 403 mClose.setTranslationX(-mClose.getWidth() - 404 ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin); 405 ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 0); 406 buttonAnimator.setDuration(200); 407 buttonAnimator.addListener(this); 408 buttonAnimator.setInterpolator(new DecelerateInterpolator()); 409 410 AnimatorSet set = new AnimatorSet(); 411 AnimatorSet.Builder b = set.play(buttonAnimator); 412 413 if (mMenuView != null) { 414 final int count = mMenuView.getChildCount(); 415 if (count > 0) { 416 for (int i = count - 1, j = 0; i >= 0; i--, j++) { 417 View child = mMenuView.getChildAt(i); 418 child.setScaleY(0); 419 ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0, 1); 420 a.setDuration(300); 421 b.with(a); 422 } 423 } 424 } 425 426 return set; 427 } 428 429 private Animator makeOutAnimation() { 430 ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 431 -mClose.getWidth() - ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin); 432 buttonAnimator.setDuration(200); 433 buttonAnimator.addListener(this); 434 buttonAnimator.setInterpolator(new DecelerateInterpolator()); 435 436 AnimatorSet set = new AnimatorSet(); 437 AnimatorSet.Builder b = set.play(buttonAnimator); 438 439 if (mMenuView != null) { 440 final int count = mMenuView.getChildCount(); 441 if (count > 0) { 442 for (int i = 0; i < 0; i++) { 443 View child = mMenuView.getChildAt(i); 444 child.setScaleY(0); 445 ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0); 446 a.setDuration(300); 447 b.with(a); 448 } 449 } 450 } 451 452 return set; 453 } 454 455 @Override 456 protected void onLayout(boolean changed, int l, int t, int r, int b) { 457 int x = getPaddingLeft(); 458 final int y = getPaddingTop(); 459 final int contentHeight = b - t - getPaddingTop() - getPaddingBottom(); 460 461 if (mClose != null && mClose.getVisibility() != GONE) { 462 MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams(); 463 x += lp.leftMargin; 464 x += positionChild(mClose, x, y, contentHeight); 465 x += lp.rightMargin; 466 467 if (mAnimateInOnLayout) { 468 mAnimationMode = ANIMATE_IN; 469 mCurrentAnimation = makeInAnimation(); 470 mCurrentAnimation.start(); 471 mAnimateInOnLayout = false; 472 } 473 } 474 475 if (mTitleLayout != null && mCustomView == null && mTitleLayout.getVisibility() != GONE) { 476 x += positionChild(mTitleLayout, x, y, contentHeight); 477 } 478 479 if (mCustomView != null) { 480 x += positionChild(mCustomView, x, y, contentHeight); 481 } 482 483 x = r - l - getPaddingRight(); 484 485 if (mMenuView != null) { 486 x -= positionChildInverse(mMenuView, x, y, contentHeight); 487 } 488 } 489 490 @Override 491 public void onAnimationStart(Animator animation) { 492 } 493 494 @Override 495 public void onAnimationEnd(Animator animation) { 496 if (mAnimationMode == ANIMATE_OUT) { 497 killMode(); 498 } 499 mAnimationMode = ANIMATE_IDLE; 500 } 501 502 @Override 503 public void onAnimationCancel(Animator animation) { 504 } 505 506 @Override 507 public void onAnimationRepeat(Animator animation) { 508 } 509 510 @Override 511 public boolean shouldDelayChildPressedState() { 512 return false; 513 } 514 515 @Override 516 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 517 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 518 // Action mode started 519 event.setSource(this); 520 event.setClassName(getClass().getName()); 521 event.setPackageName(getContext().getPackageName()); 522 event.setContentDescription(mTitle); 523 } else { 524 super.onInitializeAccessibilityEvent(event); 525 } 526 } 527 528 public void setTitleOptional(boolean titleOptional) { 529 if (titleOptional != mTitleOptional) { 530 requestLayout(); 531 } 532 mTitleOptional = titleOptional; 533 } 534 535 public boolean isTitleOptional() { 536 return mTitleOptional; 537 } 538 } 539