ActionBarContextView.java revision 45f1e08c348ccb129bcc25e438c05421f7123f41
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 finishAnimation(); 163 164 if (mClose == null) { 165 LayoutInflater inflater = LayoutInflater.from(mContext); 166 mClose = inflater.inflate(R.layout.action_mode_close_item, this, false); 167 addView(mClose); 168 } else if (mClose.getParent() == null) { 169 addView(mClose); 170 } 171 172 View closeButton = mClose.findViewById(R.id.action_mode_close_button); 173 closeButton.setOnClickListener(new OnClickListener() { 174 public void onClick(View v) { 175 mode.finish(); 176 } 177 }); 178 179 final MenuBuilder menu = (MenuBuilder) mode.getMenu(); 180 mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this); 181 mMenuView.setOverflowReserved(true); 182 mMenuView.updateChildren(false); 183 addView(mMenuView); 184 185 mAnimateInOnLayout = true; 186 } 187 188 public void closeMode() { 189 if (mAnimationMode == ANIMATE_OUT) { 190 // Called again during close; just finish what we were doing. 191 return; 192 } 193 if (mClose == null) { 194 killMode(); 195 return; 196 } 197 198 mAnimationMode = ANIMATE_OUT; 199 finishAnimation(); 200 mCurrentAnimation = makeOutAnimation(); 201 mCurrentAnimation.start(); 202 } 203 204 private void finishAnimation() { 205 final Animator a = mCurrentAnimation; 206 if (a != null && a.isRunning()) { 207 mCurrentAnimation = null; 208 a.end(); 209 } 210 } 211 212 public void killMode() { 213 finishAnimation(); 214 removeAllViews(); 215 mCustomView = null; 216 mMenuView = null; 217 } 218 219 public boolean showOverflowMenu() { 220 if (mMenuView != null) { 221 return mMenuView.showOverflowMenu(); 222 } 223 return false; 224 } 225 226 public void openOverflowMenu() { 227 if (mMenuView != null) { 228 mMenuView.openOverflowMenu(); 229 } 230 } 231 232 public boolean hideOverflowMenu() { 233 if (mMenuView != null) { 234 return mMenuView.hideOverflowMenu(); 235 } 236 return false; 237 } 238 239 public boolean isOverflowMenuShowing() { 240 if (mMenuView != null) { 241 return mMenuView.isOverflowMenuShowing(); 242 } 243 return false; 244 } 245 246 @Override 247 protected LayoutParams generateDefaultLayoutParams() { 248 // Used by custom views if they don't supply layout params. Everything else 249 // added to an ActionBarContextView should have them already. 250 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 251 } 252 253 @Override 254 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 255 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 256 if (widthMode != MeasureSpec.EXACTLY) { 257 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 258 "with android:layout_width=\"match_parent\" (or fill_parent)"); 259 } 260 261 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 262 if (heightMode == MeasureSpec.UNSPECIFIED) { 263 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 264 "with android:layout_height=\"wrap_content\""); 265 } 266 267 final int contentWidth = MeasureSpec.getSize(widthMeasureSpec); 268 269 int maxHeight = mContentHeight > 0 ? 270 mContentHeight : MeasureSpec.getSize(heightMeasureSpec); 271 272 final int verticalPadding = getPaddingTop() + getPaddingBottom(); 273 int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight(); 274 final int height = maxHeight - verticalPadding; 275 final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); 276 277 if (mClose != null) { 278 availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0); 279 } 280 281 if (mTitleLayout != null && mCustomView == null) { 282 availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0); 283 } 284 285 final int childCount = getChildCount(); 286 for (int i = 0; i < childCount; i++) { 287 final View child = getChildAt(i); 288 if (child == mClose || child == mTitleLayout || child == mCustomView) { 289 continue; 290 } 291 292 availableWidth = measureChildView(child, availableWidth, childSpecHeight, 0); 293 } 294 295 if (mCustomView != null) { 296 LayoutParams lp = mCustomView.getLayoutParams(); 297 final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ? 298 MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; 299 final int customWidth = lp.width >= 0 ? 300 Math.min(lp.width, availableWidth) : availableWidth; 301 final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ? 302 MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; 303 final int customHeight = lp.height >= 0 ? 304 Math.min(lp.height, height) : height; 305 mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode), 306 MeasureSpec.makeMeasureSpec(customHeight, customHeightMode)); 307 } 308 309 if (mContentHeight <= 0) { 310 int measuredHeight = 0; 311 final int count = getChildCount(); 312 for (int i = 0; i < count; i++) { 313 View v = getChildAt(i); 314 int paddedViewHeight = v.getMeasuredHeight() + verticalPadding; 315 if (paddedViewHeight > measuredHeight) { 316 measuredHeight = paddedViewHeight; 317 } 318 } 319 setMeasuredDimension(contentWidth, measuredHeight); 320 } else { 321 setMeasuredDimension(contentWidth, maxHeight); 322 } 323 } 324 325 private Animator makeInAnimation() { 326 mClose.setTranslationX(-mClose.getWidth()); 327 ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 0); 328 buttonAnimator.setDuration(200); 329 buttonAnimator.addListener(this); 330 buttonAnimator.setInterpolator(new DecelerateInterpolator()); 331 332 AnimatorSet set = new AnimatorSet(); 333 AnimatorSet.Builder b = set.play(buttonAnimator); 334 335 if (mMenuView != null) { 336 final int count = mMenuView.getChildCount(); 337 if (count > 0) { 338 for (int i = count - 1, j = 0; i >= 0; i--, j++) { 339 View child = mMenuView.getChildAt(i); 340 child.setScaleY(0); 341 ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0, 1); 342 a.setDuration(100); 343 a.setStartDelay(j * 70); 344 b.with(a); 345 } 346 } 347 } 348 349 return set; 350 } 351 352 private Animator makeOutAnimation() { 353 ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 354 0, -mClose.getWidth()); 355 buttonAnimator.setDuration(200); 356 buttonAnimator.addListener(this); 357 buttonAnimator.setInterpolator(new DecelerateInterpolator()); 358 359 AnimatorSet set = new AnimatorSet(); 360 AnimatorSet.Builder b = set.play(buttonAnimator); 361 362 if (mMenuView != null) { 363 final int count = mMenuView.getChildCount(); 364 if (count > 0) { 365 for (int i = 0; i < 0; i++) { 366 View child = mMenuView.getChildAt(i); 367 child.setScaleY(0); 368 ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 1, 0); 369 a.setDuration(100); 370 a.setStartDelay(i * 70); 371 b.with(a); 372 } 373 } 374 } 375 376 return set; 377 } 378 379 @Override 380 protected void onLayout(boolean changed, int l, int t, int r, int b) { 381 int x = getPaddingLeft(); 382 final int y = getPaddingTop(); 383 final int contentHeight = b - t - getPaddingTop() - getPaddingBottom(); 384 385 if (mClose != null && mClose.getVisibility() != GONE) { 386 x += positionChild(mClose, x, y, contentHeight); 387 388 if (mAnimateInOnLayout) { 389 mAnimationMode = ANIMATE_IN; 390 mCurrentAnimation = makeInAnimation(); 391 mCurrentAnimation.start(); 392 mAnimateInOnLayout = false; 393 } 394 } 395 396 if (mTitleLayout != null && mCustomView == null) { 397 x += positionChild(mTitleLayout, x, y, contentHeight); 398 } 399 400 if (mCustomView != null) { 401 x += positionChild(mCustomView, x, y, contentHeight); 402 } 403 404 x = r - l - getPaddingRight(); 405 406 if (mMenuView != null) { 407 x -= positionChildInverse(mMenuView, x, y, contentHeight); 408 } 409 } 410 411 private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) { 412 child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), 413 childSpecHeight); 414 415 availableWidth -= child.getMeasuredWidth(); 416 availableWidth -= spacing; 417 418 return availableWidth; 419 } 420 421 private int positionChild(View child, int x, int y, int contentHeight) { 422 int childWidth = child.getMeasuredWidth(); 423 int childHeight = child.getMeasuredHeight(); 424 int childTop = y + (contentHeight - childHeight) / 2; 425 426 child.layout(x, childTop, x + childWidth, childTop + childHeight); 427 428 return childWidth; 429 } 430 431 private int positionChildInverse(View child, int x, int y, int contentHeight) { 432 int childWidth = child.getMeasuredWidth(); 433 int childHeight = child.getMeasuredHeight(); 434 int childTop = y + (contentHeight - childHeight) / 2; 435 436 child.layout(x - childWidth, childTop, x, childTop + childHeight); 437 438 return childWidth; 439 } 440 441 @Override 442 public void onAnimationStart(Animator animation) { 443 } 444 445 @Override 446 public void onAnimationEnd(Animator animation) { 447 if (mAnimationMode == ANIMATE_OUT) { 448 killMode(); 449 } 450 mAnimationMode = ANIMATE_IDLE; 451 } 452 453 @Override 454 public void onAnimationCancel(Animator animation) { 455 } 456 457 @Override 458 public void onAnimationRepeat(Animator animation) { 459 } 460} 461