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