ActionBarView.java revision 661c908e4e26c99adc2cab7558a02129eaee059d
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 */ 16 17package com.android.internal.widget; 18 19import com.android.internal.R; 20import com.android.internal.view.menu.ActionMenuItem; 21import com.android.internal.view.menu.ActionMenuView; 22import com.android.internal.view.menu.MenuBuilder; 23 24import android.app.ActionBar; 25import android.app.Activity; 26import android.app.ActionBar.NavigationCallback; 27import android.content.Context; 28import android.content.pm.ApplicationInfo; 29import android.content.pm.PackageManager; 30import android.content.res.TypedArray; 31import android.graphics.PorterDuff; 32import android.graphics.PorterDuffColorFilter; 33import android.graphics.drawable.Drawable; 34import android.text.TextUtils.TruncateAt; 35import android.util.AttributeSet; 36import android.util.DisplayMetrics; 37import android.view.Gravity; 38import android.view.LayoutInflater; 39import android.view.Menu; 40import android.view.View; 41import android.view.ViewGroup; 42import android.widget.AdapterView; 43import android.widget.ImageView; 44import android.widget.LinearLayout; 45import android.widget.Spinner; 46import android.widget.SpinnerAdapter; 47import android.widget.TextView; 48 49/** 50 * @hide 51 */ 52public class ActionBarView extends ViewGroup { 53 private static final String TAG = "ActionBarView"; 54 55 // TODO: This must be defined in the default theme 56 private static final int CONTENT_HEIGHT_DIP = 50; 57 private static final int CONTENT_PADDING_DIP = 3; 58 private static final int CONTENT_SPACING_DIP = 6; 59 private static final int CONTENT_ACTION_SPACING_DIP = 12; 60 61 /** 62 * Display options applied by default 63 */ 64 public static final int DISPLAY_DEFAULT = 0; 65 66 /** 67 * Display options that require re-layout as opposed to a simple invalidate 68 */ 69 private static final int DISPLAY_RELAYOUT_MASK = 70 ActionBar.DISPLAY_HIDE_HOME | 71 ActionBar.DISPLAY_USE_LOGO; 72 73 private final int mContentHeight; 74 75 private int mNavigationMode; 76 private int mDisplayOptions; 77 private int mSpacing; 78 private int mActionSpacing; 79 private CharSequence mTitle; 80 private CharSequence mSubtitle; 81 private Drawable mIcon; 82 private Drawable mLogo; 83 84 private ImageView mIconView; 85 private ImageView mLogoView; 86 private LinearLayout mTitleLayout; 87 private TextView mTitleView; 88 private TextView mSubtitleView; 89 private Spinner mSpinner; 90 private LinearLayout mTabLayout; 91 private View mCustomNavView; 92 93 private boolean mShowMenu; 94 95 private MenuBuilder mOptionsMenu; 96 private ActionMenuView mMenuView; 97 98 private ActionMenuItem mLogoNavItem; 99 100 private NavigationCallback mCallback; 101 102 private final AdapterView.OnItemSelectedListener mNavItemSelectedListener = 103 new AdapterView.OnItemSelectedListener() { 104 public void onItemSelected(AdapterView parent, View view, int position, long id) { 105 if (mCallback != null) { 106 mCallback.onNavigationItemSelected(position, id); 107 } 108 } 109 public void onNothingSelected(AdapterView parent) { 110 // Do nothing 111 } 112 }; 113 114 private OnClickListener mHomeClickListener = null; 115 116 public ActionBarView(Context context, AttributeSet attrs) { 117 super(context, attrs); 118 119 final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 120 mContentHeight = (int) (CONTENT_HEIGHT_DIP * metrics.density + 0.5f); 121 122 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar); 123 124 final int colorFilter = a.getColor(R.styleable.ActionBar_colorFilter, 0); 125 126 if (colorFilter != 0) { 127 final Drawable d = getBackground(); 128 d.setDither(true); 129 d.setColorFilter(new PorterDuffColorFilter(colorFilter, PorterDuff.Mode.OVERLAY)); 130 } 131 132 ApplicationInfo info = context.getApplicationInfo(); 133 PackageManager pm = context.getPackageManager(); 134 mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode, 135 ActionBar.NAVIGATION_MODE_STANDARD); 136 mTitle = a.getText(R.styleable.ActionBar_title); 137 mSubtitle = a.getText(R.styleable.ActionBar_subtitle); 138 mDisplayOptions = a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT); 139 140 mLogo = a.getDrawable(R.styleable.ActionBar_logo); 141 if (mLogo == null) { 142 mLogo = info.loadLogo(pm); 143 } 144 mIcon = a.getDrawable(R.styleable.ActionBar_icon); 145 if (mIcon == null) { 146 mIcon = info.loadIcon(pm); 147 } 148 149 Drawable background = a.getDrawable(R.styleable.ActionBar_background); 150 if (background != null) { 151 setBackgroundDrawable(background); 152 } 153 154 final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0); 155 if (customNavId != 0) { 156 LayoutInflater inflater = LayoutInflater.from(context); 157 mCustomNavView = (View) inflater.inflate(customNavId, null); 158 mNavigationMode = ActionBar.NAVIGATION_MODE_CUSTOM; 159 } 160 161 a.recycle(); 162 163 // TODO: Set this in the theme 164 int padding = (int) (CONTENT_PADDING_DIP * metrics.density + 0.5f); 165 setPadding(padding, padding, padding, padding); 166 167 mSpacing = (int) (CONTENT_SPACING_DIP * metrics.density + 0.5f); 168 mActionSpacing = (int) (CONTENT_ACTION_SPACING_DIP * metrics.density + 0.5f); 169 170 if (mLogo != null || mIcon != null || mTitle != null) { 171 mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle); 172 mHomeClickListener = new OnClickListener() { 173 public void onClick(View v) { 174 Context context = getContext(); 175 if (context instanceof Activity) { 176 Activity activity = (Activity) context; 177 activity.onOptionsItemSelected(mLogoNavItem); 178 } 179 } 180 }; 181 } 182 } 183 184 public void setCallback(NavigationCallback callback) { 185 mCallback = callback; 186 } 187 188 public void setMenu(Menu menu) { 189 MenuBuilder builder = (MenuBuilder) menu; 190 mOptionsMenu = builder; 191 if (mMenuView != null) { 192 removeView(mMenuView); 193 } 194 final ActionMenuView menuView = (ActionMenuView) builder.getMenuView( 195 MenuBuilder.TYPE_ACTION_BUTTON, null); 196 mActionSpacing = menuView.getItemMargin(); 197 final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, 198 LayoutParams.MATCH_PARENT); 199 menuView.setLayoutParams(layoutParams); 200 addView(menuView); 201 mMenuView = menuView; 202 } 203 204 public void setCustomNavigationView(View view) { 205 mCustomNavView = view; 206 if (view != null) { 207 setNavigationMode(ActionBar.NAVIGATION_MODE_CUSTOM); 208 } 209 } 210 211 public CharSequence getTitle() { 212 return mTitle; 213 } 214 215 public void setTitle(CharSequence title) { 216 mTitle = title; 217 if (mTitleView != null) { 218 mTitleView.setText(title); 219 } 220 if (mLogoNavItem != null) { 221 mLogoNavItem.setTitle(title); 222 } 223 } 224 225 public CharSequence getSubtitle() { 226 return mSubtitle; 227 } 228 229 public void setSubtitle(CharSequence subtitle) { 230 mSubtitle = subtitle; 231 if (mSubtitleView != null) { 232 mSubtitleView.setText(subtitle); 233 } 234 } 235 236 public void setDisplayOptions(int options) { 237 final int flagsChanged = options ^ mDisplayOptions; 238 mDisplayOptions = options; 239 if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) { 240 final int vis = (options & ActionBar.DISPLAY_HIDE_HOME) != 0 ? GONE : VISIBLE; 241 if (mLogoView != null) { 242 mLogoView.setVisibility(vis); 243 } 244 if (mIconView != null) { 245 mIconView.setVisibility(vis); 246 } 247 248 requestLayout(); 249 } else { 250 invalidate(); 251 } 252 } 253 254 public void setNavigationMode(int mode) { 255 final int oldMode = mNavigationMode; 256 if (mode != oldMode) { 257 switch (oldMode) { 258 case ActionBar.NAVIGATION_MODE_STANDARD: 259 if (mTitleLayout != null) { 260 removeView(mTitleLayout); 261 mTitleLayout = null; 262 mTitleView = null; 263 mSubtitleView = null; 264 } 265 break; 266 case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: 267 if (mSpinner != null) { 268 removeView(mSpinner); 269 mSpinner = null; 270 } 271 break; 272 case ActionBar.NAVIGATION_MODE_CUSTOM: 273 if (mCustomNavView != null) { 274 removeView(mCustomNavView); 275 mCustomNavView = null; 276 } 277 break; 278 case ActionBar.NAVIGATION_MODE_TABS: 279 if (mTabLayout != null) { 280 removeView(mTabLayout); 281 mTabLayout = null; 282 } 283 } 284 285 switch (mode) { 286 case ActionBar.NAVIGATION_MODE_STANDARD: 287 initTitle(); 288 break; 289 case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: 290 mSpinner = new Spinner(mContext, null, 291 com.android.internal.R.attr.dropDownSpinnerStyle); 292 mSpinner.setOnItemSelectedListener(mNavItemSelectedListener); 293 addView(mSpinner); 294 break; 295 case ActionBar.NAVIGATION_MODE_CUSTOM: 296 addView(mCustomNavView); 297 break; 298 case ActionBar.NAVIGATION_MODE_TABS: 299 mTabLayout = new LinearLayout(getContext()); 300 addView(mTabLayout); 301 break; 302 } 303 mNavigationMode = mode; 304 requestLayout(); 305 } 306 } 307 308 public void setDropdownAdapter(SpinnerAdapter adapter) { 309 mSpinner.setAdapter(adapter); 310 } 311 312 public View getCustomNavigationView() { 313 return mCustomNavView; 314 } 315 316 public int getNavigationMode() { 317 return mNavigationMode; 318 } 319 320 public int getDisplayOptions() { 321 return mDisplayOptions; 322 } 323 324 private TabView createTabView(ActionBar.Tab tab) { 325 final TabView tabView = new TabView(getContext(), tab); 326 tabView.setFocusable(true); 327 tabView.setOnClickListener(new TabClickListener()); 328 return tabView; 329 } 330 331 public void addTab(ActionBar.Tab tab) { 332 final boolean isFirst = mTabLayout.getChildCount() == 0; 333 final TabView tabView = createTabView(tab); 334 mTabLayout.addView(tabView); 335 if (isFirst) { 336 tabView.setSelected(true); 337 } 338 } 339 340 public void insertTab(ActionBar.Tab tab, int position) { 341 final boolean isFirst = mTabLayout.getChildCount() == 0; 342 final TabView tabView = createTabView(tab); 343 mTabLayout.addView(tabView, position); 344 if (isFirst) { 345 tabView.setSelected(true); 346 } 347 } 348 349 public void removeTabAt(int position) { 350 mTabLayout.removeViewAt(position); 351 } 352 353 @Override 354 protected LayoutParams generateDefaultLayoutParams() { 355 // Used by custom nav views if they don't supply layout params. Everything else 356 // added to an ActionBarView should have them already. 357 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 358 } 359 360 @Override 361 protected void onFinishInflate() { 362 super.onFinishInflate(); 363 364 if ((mDisplayOptions & ActionBar.DISPLAY_HIDE_HOME) == 0) { 365 if (mLogo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) { 366 mLogoView = new ImageView(getContext()); 367 mLogoView.setAdjustViewBounds(true); 368 mLogoView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, 369 LayoutParams.MATCH_PARENT)); 370 mLogoView.setImageDrawable(mLogo); 371 mLogoView.setClickable(true); 372 mLogoView.setFocusable(true); 373 mLogoView.setOnClickListener(mHomeClickListener); 374 addView(mLogoView); 375 } else if (mIcon != null) { 376 mIconView = new ImageView(getContext()); 377 mIconView.setAdjustViewBounds(true); 378 mIconView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, 379 LayoutParams.MATCH_PARENT)); 380 mIconView.setImageDrawable(mIcon); 381 mIconView.setClickable(true); 382 mIconView.setFocusable(true); 383 mIconView.setOnClickListener(mHomeClickListener); 384 addView(mIconView); 385 } 386 } 387 388 switch (mNavigationMode) { 389 case ActionBar.NAVIGATION_MODE_STANDARD: 390 if (mLogoView == null) { 391 initTitle(); 392 } 393 break; 394 395 case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: 396 throw new UnsupportedOperationException( 397 "Inflating dropdown list navigation isn't supported yet!"); 398 399 case ActionBar.NAVIGATION_MODE_TABS: 400 throw new UnsupportedOperationException( 401 "Inflating tab navigation isn't supported yet!"); 402 403 case ActionBar.NAVIGATION_MODE_CUSTOM: 404 if (mCustomNavView != null) { 405 addView(mCustomNavView); 406 } 407 break; 408 } 409 } 410 411 private void initTitle() { 412 LayoutInflater inflater = LayoutInflater.from(getContext()); 413 mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item, null); 414 mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title); 415 mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle); 416 if (mTitle != null) { 417 mTitleView.setText(mTitle); 418 } 419 if (mSubtitle != null) { 420 mSubtitleView.setText(mSubtitle); 421 mSubtitleView.setVisibility(VISIBLE); 422 } 423 addView(mTitleLayout); 424 } 425 426 public void setTabSelected(int position) { 427 final int tabCount = mTabLayout.getChildCount(); 428 for (int i = 0; i < tabCount; i++) { 429 final View child = mTabLayout.getChildAt(i); 430 child.setSelected(i == position); 431 } 432 } 433 434 @Override 435 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 436 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 437 if (widthMode != MeasureSpec.EXACTLY) { 438 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 439 "with android:layout_width=\"match_parent\" (or fill_parent)"); 440 } 441 442 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 443 if (heightMode != MeasureSpec.AT_MOST) { 444 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 445 "with android:layout_height=\"wrap_content\""); 446 } 447 448 int contentWidth = MeasureSpec.getSize(widthMeasureSpec); 449 450 int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight(); 451 final int height = mContentHeight - getPaddingTop() - getPaddingBottom(); 452 final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); 453 454 if (mLogoView != null && mLogoView.getVisibility() != GONE) { 455 availableWidth = measureChildView(mLogoView, availableWidth, childSpecHeight, mSpacing); 456 } 457 if (mIconView != null && mIconView.getVisibility() != GONE) { 458 availableWidth = measureChildView(mIconView, availableWidth, childSpecHeight, mSpacing); 459 } 460 461 if (mMenuView != null) { 462 availableWidth = measureChildView(mMenuView, availableWidth, 463 childSpecHeight, 0); 464 } 465 466 switch (mNavigationMode) { 467 case ActionBar.NAVIGATION_MODE_STANDARD: 468 if (mTitleLayout != null) { 469 measureChildView(mTitleLayout, availableWidth, childSpecHeight, mSpacing); 470 } 471 break; 472 case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: 473 if (mSpinner != null) { 474 mSpinner.measure( 475 MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), 476 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 477 } 478 break; 479 case ActionBar.NAVIGATION_MODE_CUSTOM: 480 if (mCustomNavView != null) { 481 LayoutParams lp = mCustomNavView.getLayoutParams(); 482 final int customNavWidthMode = lp.width != LayoutParams.WRAP_CONTENT ? 483 MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; 484 final int customNavWidth = lp.width >= 0 ? 485 Math.min(lp.width, availableWidth) : availableWidth; 486 final int customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ? 487 MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; 488 final int customNavHeight = lp.height >= 0 ? 489 Math.min(lp.height, height) : height; 490 mCustomNavView.measure( 491 MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode), 492 MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode)); 493 } 494 break; 495 case ActionBar.NAVIGATION_MODE_TABS: 496 if (mTabLayout != null) { 497 mTabLayout.measure( 498 MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), 499 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 500 } 501 break; 502 } 503 504 setMeasuredDimension(contentWidth, mContentHeight); 505 } 506 507 private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) { 508 child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), 509 childSpecHeight); 510 511 availableWidth -= child.getMeasuredWidth(); 512 availableWidth -= spacing; 513 514 return availableWidth; 515 } 516 517 @Override 518 protected void onLayout(boolean changed, int l, int t, int r, int b) { 519 int x = getPaddingLeft(); 520 final int y = getPaddingTop(); 521 final int contentHeight = b - t - getPaddingTop() - getPaddingBottom(); 522 523 if (mLogoView != null && mLogoView.getVisibility() != GONE) { 524 x += positionChild(mLogoView, x, y, contentHeight) + mSpacing; 525 } 526 if (mIconView != null && mIconView.getVisibility() != GONE) { 527 x += positionChild(mIconView, x, y, contentHeight) + mSpacing; 528 } 529 530 switch (mNavigationMode) { 531 case ActionBar.NAVIGATION_MODE_STANDARD: 532 if (mTitleLayout != null) { 533 x += positionChild(mTitleLayout, x, y, contentHeight) + mSpacing; 534 } 535 break; 536 case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: 537 if (mSpinner != null) { 538 x += positionChild(mSpinner, x, y, contentHeight) + mSpacing; 539 } 540 break; 541 case ActionBar.NAVIGATION_MODE_CUSTOM: 542 if (mCustomNavView != null) { 543 x += positionChild(mCustomNavView, x, y, contentHeight) + mSpacing; 544 } 545 break; 546 case ActionBar.NAVIGATION_MODE_TABS: 547 if (mTabLayout != null) { 548 x += positionChild(mTabLayout, x, y, contentHeight) + mSpacing; 549 } 550 } 551 552 x = r - l - getPaddingRight(); 553 554 if (mMenuView != null) { 555 x -= positionChildInverse(mMenuView, x + mActionSpacing, y, contentHeight) 556 - mActionSpacing; 557 } 558 } 559 560 private int positionChild(View child, int x, int y, int contentHeight) { 561 int childWidth = child.getMeasuredWidth(); 562 int childHeight = child.getMeasuredHeight(); 563 int childTop = y + (contentHeight - childHeight) / 2; 564 565 child.layout(x, childTop, x + childWidth, childTop + childHeight); 566 567 return childWidth; 568 } 569 570 private int positionChildInverse(View child, int x, int y, int contentHeight) { 571 int childWidth = child.getMeasuredWidth(); 572 int childHeight = child.getMeasuredHeight(); 573 int childTop = y + (contentHeight - childHeight) / 2; 574 575 child.layout(x - childWidth, childTop, x, childTop + childHeight); 576 577 return childWidth; 578 } 579 580 private static class TabView extends LinearLayout { 581 private ActionBar.Tab mTab; 582 583 public TabView(Context context, ActionBar.Tab tab) { 584 super(context); 585 mTab = tab; 586 587 // TODO Style tabs based on the theme 588 589 final Drawable icon = tab.getIcon(); 590 final CharSequence text = tab.getText(); 591 592 if (icon != null) { 593 ImageView iconView = new ImageView(context); 594 iconView.setImageDrawable(icon); 595 LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, 596 LayoutParams.WRAP_CONTENT); 597 lp.gravity = Gravity.CENTER_VERTICAL; 598 iconView.setLayoutParams(lp); 599 addView(iconView); 600 } 601 602 if (text != null) { 603 TextView textView = new TextView(context); 604 textView.setText(text); 605 textView.setSingleLine(); 606 textView.setEllipsize(TruncateAt.END); 607 LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, 608 LayoutParams.WRAP_CONTENT); 609 lp.gravity = Gravity.CENTER_VERTICAL; 610 textView.setLayoutParams(lp); 611 addView(textView); 612 } 613 614 setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, 615 LayoutParams.MATCH_PARENT, 1)); 616 } 617 618 public ActionBar.Tab getTab() { 619 return mTab; 620 } 621 } 622 623 private class TabClickListener implements OnClickListener { 624 public void onClick(View view) { 625 TabView tabView = (TabView) view; 626 tabView.getTab().select(); 627 final int tabCount = mTabLayout.getChildCount(); 628 for (int i = 0; i < tabCount; i++) { 629 final View child = mTabLayout.getChildAt(i); 630 child.setSelected(child == view); 631 } 632 } 633 } 634} 635