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