ActionBarContextView.java revision 00bba682efedbe121f31c98697f91101b1c22b82
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.ActionMenuView; 20import com.android.internal.view.menu.MenuBuilder; 21 22import android.animation.Animator; 23import android.animation.Animator.AnimatorListener; 24import android.animation.AnimatorSet; 25import android.animation.ObjectAnimator; 26import android.content.Context; 27import android.content.res.TypedArray; 28import android.util.AttributeSet; 29import android.util.Log; 30import android.view.ActionMode; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.view.ViewGroup; 34import android.view.animation.DecelerateInterpolator; 35import android.widget.LinearLayout; 36import android.widget.TextView; 37 38/** 39 * @hide 40 */ 41public class ActionBarContextView extends ViewGroup implements AnimatorListener { 42 private int mContentHeight; 43 44 private CharSequence mTitle; 45 private CharSequence mSubtitle; 46 47 private View mClose; 48 private View mCustomView; 49 private LinearLayout mTitleLayout; 50 private TextView mTitleView; 51 private TextView mSubtitleView; 52 private int mTitleStyleRes; 53 private int mSubtitleStyleRes; 54 private ActionMenuView mMenuView; 55 56 private Animator mCurrentAnimation; 57 private boolean mAnimateInOnLayout; 58 private int mAnimationMode; 59 60 private static final int ANIMATE_IDLE = 0; 61 private static final int ANIMATE_IN = 1; 62 private static final int ANIMATE_OUT = 2; 63 64 public ActionBarContextView(Context context) { 65 this(context, null); 66 } 67 68 public ActionBarContextView(Context context, AttributeSet attrs) { 69 this(context, attrs, com.android.internal.R.attr.actionModeStyle); 70 } 71 72 public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) { 73 super(context, attrs, defStyle); 74 75 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0); 76 setBackgroundDrawable(a.getDrawable( 77 com.android.internal.R.styleable.ActionMode_background)); 78 mTitleStyleRes = a.getResourceId( 79 com.android.internal.R.styleable.ActionMode_titleTextStyle, 0); 80 mSubtitleStyleRes = a.getResourceId( 81 com.android.internal.R.styleable.ActionMode_subtitleTextStyle, 0); 82 83 mContentHeight = a.getLayoutDimension( 84 com.android.internal.R.styleable.ActionMode_height, 0); 85 a.recycle(); 86 } 87 88 @Override 89 public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) { 90 // No starting an action mode for an existing action mode UI child! (Where would it go?) 91 return null; 92 } 93 94 public void setHeight(int height) { 95 mContentHeight = height; 96 } 97 98 public void setCustomView(View view) { 99 if (mCustomView != null) { 100 removeView(mCustomView); 101 } 102 mCustomView = view; 103 if (mTitleLayout != null) { 104 removeView(mTitleLayout); 105 mTitleLayout = null; 106 } 107 if (view != null) { 108 addView(view); 109 } 110 requestLayout(); 111 } 112 113 public void setTitle(CharSequence title) { 114 mTitle = title; 115 initTitle(); 116 } 117 118 public void setSubtitle(CharSequence subtitle) { 119 mSubtitle = subtitle; 120 initTitle(); 121 } 122 123 public CharSequence getTitle() { 124 return mTitle; 125 } 126 127 public CharSequence getSubtitle() { 128 return mSubtitle; 129 } 130 131 private void initTitle() { 132 if (mTitleLayout == null) { 133 LayoutInflater inflater = LayoutInflater.from(getContext()); 134 inflater.inflate(R.layout.action_bar_title_item, this); 135 mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1); 136 mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title); 137 mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle); 138 if (mTitle != null) { 139 mTitleView.setText(mTitle); 140 if (mTitleStyleRes != 0) { 141 mTitleView.setTextAppearance(mContext, mTitleStyleRes); 142 } 143 } 144 if (mSubtitle != null) { 145 mSubtitleView.setText(mSubtitle); 146 if (mSubtitleStyleRes != 0) { 147 mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes); 148 } 149 mSubtitleView.setVisibility(VISIBLE); 150 } 151 } else { 152 mTitleView.setText(mTitle); 153 mSubtitleView.setText(mSubtitle); 154 mSubtitleView.setVisibility(mSubtitle != null ? VISIBLE : GONE); 155 if (mTitleLayout.getParent() == null) { 156 addView(mTitleLayout); 157 } 158 } 159 } 160 161 public void initForMode(final ActionMode mode) { 162 if (mAnimationMode != ANIMATE_IDLE || mAnimateInOnLayout) { 163 killMode(); 164 } 165 166 if (mClose == null) { 167 LayoutInflater inflater = LayoutInflater.from(mContext); 168 mClose = inflater.inflate(R.layout.action_mode_close_item, this, false); 169 addView(mClose); 170 } else if (mClose.getParent() == null) { 171 addView(mClose); 172 } 173 174 View closeButton = mClose.findViewById(R.id.action_mode_close_button); 175 closeButton.setOnClickListener(new OnClickListener() { 176 public void onClick(View v) { 177 mode.finish(); 178 } 179 }); 180 181 final MenuBuilder menu = (MenuBuilder) mode.getMenu(); 182 mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this); 183 mMenuView.setOverflowReserved(true); 184 mMenuView.updateChildren(false); 185 addView(mMenuView); 186 187 mAnimateInOnLayout = true; 188 } 189 190 public void closeMode() { 191 if (mAnimationMode == ANIMATE_OUT) { 192 // Called again during close; just finish what we were doing. 193 return; 194 } 195 if (mClose == null) { 196 killMode(); 197 return; 198 } 199 200 mAnimationMode = ANIMATE_OUT; 201 finishAnimation(); 202 mCurrentAnimation = makeOutAnimation(); 203 mCurrentAnimation.start(); 204 } 205 206 private void finishAnimation() { 207 final Animator a = mCurrentAnimation; 208 if (a != null && a.isRunning()) { 209 mCurrentAnimation = null; 210 a.end(); 211 } 212 } 213 214 public void killMode() { 215 finishAnimation(); 216 removeAllViews(); 217 mCustomView = null; 218 mMenuView = null; 219 mAnimateInOnLayout = false; 220 } 221 222 public boolean showOverflowMenu() { 223 if (mMenuView != null) { 224 return mMenuView.showOverflowMenu(); 225 } 226 return false; 227 } 228 229 public void openOverflowMenu() { 230 if (mMenuView != null) { 231 mMenuView.openOverflowMenu(); 232 } 233 } 234 235 public boolean hideOverflowMenu() { 236 if (mMenuView != null) { 237 return mMenuView.hideOverflowMenu(); 238 } 239 return false; 240 } 241 242 public boolean isOverflowMenuShowing() { 243 if (mMenuView != null) { 244 return mMenuView.isOverflowMenuShowing(); 245 } 246 return false; 247 } 248 249 @Override 250 protected LayoutParams generateDefaultLayoutParams() { 251 // Used by custom views if they don't supply layout params. Everything else 252 // added to an ActionBarContextView should have them already. 253 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 254 } 255 256 @Override 257 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 258 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 259 if (widthMode != MeasureSpec.EXACTLY) { 260 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 261 "with android:layout_width=\"match_parent\" (or fill_parent)"); 262 } 263 264 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 265 if (heightMode == MeasureSpec.UNSPECIFIED) { 266 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 267 "with android:layout_height=\"wrap_content\""); 268 } 269 270 final int contentWidth = MeasureSpec.getSize(widthMeasureSpec); 271 272 int maxHeight = mContentHeight > 0 ? 273 mContentHeight : MeasureSpec.getSize(heightMeasureSpec); 274 275 final int verticalPadding = getPaddingTop() + getPaddingBottom(); 276 int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight(); 277 final int height = maxHeight - verticalPadding; 278 final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); 279 280 if (mClose != null) { 281 availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0); 282 } 283 284 if (mTitleLayout != null && mCustomView == null) { 285 availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0); 286 } 287 288 final int childCount = getChildCount(); 289 for (int i = 0; i < childCount; i++) { 290 final View child = getChildAt(i); 291 if (child == mClose || child == mTitleLayout || child == mCustomView) { 292 continue; 293 } 294 295 availableWidth = measureChildView(child, availableWidth, childSpecHeight, 0); 296 } 297 298 if (mCustomView != null) { 299 LayoutParams lp = mCustomView.getLayoutParams(); 300 final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ? 301 MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; 302 final int customWidth = lp.width >= 0 ? 303 Math.min(lp.width, availableWidth) : availableWidth; 304 final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ? 305 MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; 306 final int customHeight = lp.height >= 0 ? 307 Math.min(lp.height, height) : height; 308 mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode), 309 MeasureSpec.makeMeasureSpec(customHeight, customHeightMode)); 310 } 311 312 if (mContentHeight <= 0) { 313 int measuredHeight = 0; 314 final int count = getChildCount(); 315 for (int i = 0; i < count; i++) { 316 View v = getChildAt(i); 317 int paddedViewHeight = v.getMeasuredHeight() + verticalPadding; 318 if (paddedViewHeight > measuredHeight) { 319 measuredHeight = paddedViewHeight; 320 } 321 } 322 setMeasuredDimension(contentWidth, measuredHeight); 323 } else { 324 setMeasuredDimension(contentWidth, maxHeight); 325 } 326 } 327 328 private Animator makeInAnimation() { 329 mClose.setTranslationX(-mClose.getWidth()); 330 ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 0); 331 buttonAnimator.setDuration(200); 332 buttonAnimator.addListener(this); 333 buttonAnimator.setInterpolator(new DecelerateInterpolator()); 334 335 AnimatorSet set = new AnimatorSet(); 336 AnimatorSet.Builder b = set.play(buttonAnimator); 337 338 if (mMenuView != null) { 339 final int count = mMenuView.getChildCount(); 340 if (count > 0) { 341 for (int i = count - 1, j = 0; i >= 0; i--, j++) { 342 View child = mMenuView.getChildAt(i); 343 child.setScaleY(0); 344 ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0, 1); 345 a.setDuration(100); 346 a.setStartDelay(j * 70); 347 b.with(a); 348 } 349 } 350 } 351 352 return set; 353 } 354 355 private Animator makeOutAnimation() { 356 ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 357 0, -mClose.getWidth()); 358 buttonAnimator.setDuration(200); 359 buttonAnimator.addListener(this); 360 buttonAnimator.setInterpolator(new DecelerateInterpolator()); 361 362 AnimatorSet set = new AnimatorSet(); 363 AnimatorSet.Builder b = set.play(buttonAnimator); 364 365 if (mMenuView != null) { 366 final int count = mMenuView.getChildCount(); 367 if (count > 0) { 368 for (int i = 0; i < 0; i++) { 369 View child = mMenuView.getChildAt(i); 370 child.setScaleY(0); 371 ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 1, 0); 372 a.setDuration(100); 373 a.setStartDelay(i * 70); 374 b.with(a); 375 } 376 } 377 } 378 379 return set; 380 } 381 382 @Override 383 protected void onLayout(boolean changed, int l, int t, int r, int b) { 384 int x = getPaddingLeft(); 385 final int y = getPaddingTop(); 386 final int contentHeight = b - t - getPaddingTop() - getPaddingBottom(); 387 388 if (mClose != null && mClose.getVisibility() != GONE) { 389 x += positionChild(mClose, x, y, contentHeight); 390 391 if (mAnimateInOnLayout) { 392 mAnimationMode = ANIMATE_IN; 393 mCurrentAnimation = makeInAnimation(); 394 mCurrentAnimation.start(); 395 mAnimateInOnLayout = false; 396 } 397 } 398 399 if (mTitleLayout != null && mCustomView == null) { 400 x += positionChild(mTitleLayout, x, y, contentHeight); 401 } 402 403 if (mCustomView != null) { 404 x += positionChild(mCustomView, x, y, contentHeight); 405 } 406 407 x = r - l - getPaddingRight(); 408 409 if (mMenuView != null) { 410 x -= positionChildInverse(mMenuView, x, y, contentHeight); 411 } 412 } 413 414 private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) { 415 child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), 416 childSpecHeight); 417 418 availableWidth -= child.getMeasuredWidth(); 419 availableWidth -= spacing; 420 421 return availableWidth; 422 } 423 424 private int positionChild(View child, int x, int y, int contentHeight) { 425 int childWidth = child.getMeasuredWidth(); 426 int childHeight = child.getMeasuredHeight(); 427 int childTop = y + (contentHeight - childHeight) / 2; 428 429 child.layout(x, childTop, x + childWidth, childTop + childHeight); 430 431 return childWidth; 432 } 433 434 private int positionChildInverse(View child, int x, int y, int contentHeight) { 435 int childWidth = child.getMeasuredWidth(); 436 int childHeight = child.getMeasuredHeight(); 437 int childTop = y + (contentHeight - childHeight) / 2; 438 439 child.layout(x - childWidth, childTop, x, childTop + childHeight); 440 441 return childWidth; 442 } 443 444 @Override 445 public void onAnimationStart(Animator animation) { 446 } 447 448 @Override 449 public void onAnimationEnd(Animator animation) { 450 if (mAnimationMode == ANIMATE_OUT) { 451 killMode(); 452 } 453 mAnimationMode = ANIMATE_IDLE; 454 } 455 456 @Override 457 public void onAnimationCancel(Animator animation) { 458 } 459 460 @Override 461 public void onAnimationRepeat(Animator animation) { 462 } 463} 464