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