ActionBarContextView.java revision a1e6358a4c62c8b6de1f2428901e45b688bd9e9f
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.view.ActionMode; 30import android.view.LayoutInflater; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.animation.DecelerateInterpolator; 34import android.widget.LinearLayout; 35import android.widget.TextView; 36 37/** 38 * @hide 39 */ 40public class ActionBarContextView extends ViewGroup implements AnimatorListener { 41 private static final String TAG = "ActionBarContextView"; 42 43 private int mContentHeight; 44 45 private CharSequence mTitle; 46 private CharSequence mSubtitle; 47 48 private View mClose; 49 private View mCustomView; 50 private LinearLayout mTitleLayout; 51 private TextView mTitleView; 52 private TextView mSubtitleView; 53 private int mTitleStyleRes; 54 private int mSubtitleStyleRes; 55 private ActionMenuView mMenuView; 56 57 private Animator mCurrentAnimation; 58 private boolean mAnimateInOnLayout; 59 private int mAnimationMode; 60 61 private static final int ANIMATE_IDLE = 0; 62 private static final int ANIMATE_IN = 1; 63 private static final int ANIMATE_OUT = 2; 64 65 public ActionBarContextView(Context context) { 66 this(context, null); 67 } 68 69 public ActionBarContextView(Context context, AttributeSet attrs) { 70 this(context, attrs, com.android.internal.R.attr.actionModeStyle); 71 } 72 73 public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) { 74 super(context, attrs, defStyle); 75 76 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0); 77 setBackgroundDrawable(a.getDrawable( 78 com.android.internal.R.styleable.ActionMode_background)); 79 mTitleStyleRes = a.getResourceId( 80 com.android.internal.R.styleable.ActionMode_titleTextStyle, 0); 81 mSubtitleStyleRes = a.getResourceId( 82 com.android.internal.R.styleable.ActionMode_subtitleTextStyle, 0); 83 84 mContentHeight = a.getLayoutDimension( 85 com.android.internal.R.styleable.ActionMode_height, 0); 86 a.recycle(); 87 } 88 89 @Override 90 public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) { 91 // No starting an action mode for an existing action mode UI child! (Where would it go?) 92 return null; 93 } 94 95 public void setHeight(int height) { 96 mContentHeight = height; 97 } 98 99 public void setCustomView(View view) { 100 if (mCustomView != null) { 101 removeView(mCustomView); 102 } 103 mCustomView = view; 104 if (mTitleLayout != null) { 105 removeView(mTitleLayout); 106 mTitleLayout = null; 107 } 108 if (view != null) { 109 addView(view); 110 } 111 requestLayout(); 112 } 113 114 public void setTitle(CharSequence title) { 115 mTitle = title; 116 initTitle(); 117 } 118 119 public void setSubtitle(CharSequence subtitle) { 120 mSubtitle = subtitle; 121 initTitle(); 122 } 123 124 public CharSequence getTitle() { 125 return mTitle; 126 } 127 128 public CharSequence getSubtitle() { 129 return mSubtitle; 130 } 131 132 private void initTitle() { 133 if (mTitleLayout == null) { 134 LayoutInflater inflater = LayoutInflater.from(getContext()); 135 inflater.inflate(R.layout.action_bar_title_item, this); 136 mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1); 137 mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title); 138 mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle); 139 if (mTitle != null) { 140 mTitleView.setText(mTitle); 141 if (mTitleStyleRes != 0) { 142 mTitleView.setTextAppearance(mContext, mTitleStyleRes); 143 } 144 } 145 if (mSubtitle != null) { 146 mSubtitleView.setText(mSubtitle); 147 if (mSubtitleStyleRes != 0) { 148 mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes); 149 } 150 mSubtitleView.setVisibility(VISIBLE); 151 } 152 } else { 153 mTitleView.setText(mTitle); 154 mSubtitleView.setText(mSubtitle); 155 mSubtitleView.setVisibility(mSubtitle != null ? VISIBLE : GONE); 156 if (mTitleLayout.getParent() == null) { 157 addView(mTitleLayout); 158 } 159 } 160 } 161 162 public void initForMode(final ActionMode mode) { 163 if (mClose == null) { 164 LayoutInflater inflater = LayoutInflater.from(mContext); 165 mClose = inflater.inflate(R.layout.action_mode_close_item, this, false); 166 addView(mClose); 167 } else if (mClose.getParent() == null) { 168 addView(mClose); 169 } 170 171 View closeButton = mClose.findViewById(R.id.action_mode_close_button); 172 closeButton.setOnClickListener(new OnClickListener() { 173 public void onClick(View v) { 174 mode.finish(); 175 } 176 }); 177 178 final MenuBuilder menu = (MenuBuilder) mode.getMenu(); 179 mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this); 180 mMenuView.setOverflowReserved(true); 181 mMenuView.updateChildren(false); 182 addView(mMenuView); 183 184 mAnimateInOnLayout = true; 185 } 186 187 public void closeMode() { 188 if (mAnimationMode == ANIMATE_OUT) { 189 // Called again during close; just finish what we were doing. 190 return; 191 } 192 if (mClose == null) { 193 killMode(); 194 return; 195 } 196 197 finishAnimation(); 198 mAnimationMode = ANIMATE_OUT; 199 mCurrentAnimation = makeOutAnimation(); 200 mCurrentAnimation.start(); 201 } 202 203 private void finishAnimation() { 204 final Animator a = mCurrentAnimation; 205 if (a != null) { 206 mCurrentAnimation = null; 207 a.end(); 208 } 209 } 210 211 public void killMode() { 212 finishAnimation(); 213 removeAllViews(); 214 mCustomView = null; 215 mMenuView = null; 216 mAnimateInOnLayout = false; 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