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