NavigationView.java revision 657ea1100fee4750f148f9d0dcb7e7e2028f105e
1/* 2 * Copyright (C) 2015 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 android.support.design.widget; 18 19import android.content.Context; 20import android.content.res.ColorStateList; 21import android.graphics.drawable.Drawable; 22import android.os.Bundle; 23import android.os.Parcel; 24import android.os.Parcelable; 25import android.support.annotation.DrawableRes; 26import android.support.annotation.IdRes; 27import android.support.annotation.LayoutRes; 28import android.support.annotation.NonNull; 29import android.support.annotation.Nullable; 30import android.support.annotation.StyleRes; 31import android.support.design.R; 32import android.support.design.internal.NavigationMenu; 33import android.support.design.internal.NavigationMenuPresenter; 34import android.support.design.internal.ScrimInsetsFrameLayout; 35import android.support.v4.content.ContextCompat; 36import android.support.v4.os.ParcelableCompat; 37import android.support.v4.os.ParcelableCompatCreatorCallbacks; 38import android.support.v4.view.AbsSavedState; 39import android.support.v4.view.ViewCompat; 40import android.support.v4.view.WindowInsetsCompat; 41import android.support.v7.content.res.AppCompatResources; 42import android.support.v7.view.SupportMenuInflater; 43import android.support.v7.view.menu.MenuBuilder; 44import android.support.v7.view.menu.MenuItemImpl; 45import android.support.v7.widget.TintTypedArray; 46import android.util.AttributeSet; 47import android.util.TypedValue; 48import android.view.Menu; 49import android.view.MenuInflater; 50import android.view.MenuItem; 51import android.view.View; 52 53/** 54 * Represents a standard navigation menu for application. The menu contents can be populated 55 * by a menu resource file. 56 * <p>NavigationView is typically placed inside a {@link android.support.v4.widget.DrawerLayout}. 57 * </p> 58 * <pre> 59 * <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" 60 * xmlns:app="http://schemas.android.com/apk/res-auto" 61 * android:id="@+id/drawer_layout" 62 * android:layout_width="match_parent" 63 * android:layout_height="match_parent" 64 * android:fitsSystemWindows="true"> 65 * 66 * <!-- Your contents --> 67 * 68 * <android.support.design.widget.NavigationView 69 * android:id="@+id/navigation" 70 * android:layout_width="wrap_content" 71 * android:layout_height="match_parent" 72 * android:layout_gravity="start" 73 * app:menu="@menu/my_navigation_items" /> 74 * </android.support.v4.widget.DrawerLayout> 75 * </pre> 76 */ 77public class NavigationView extends ScrimInsetsFrameLayout { 78 79 private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; 80 private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled}; 81 82 private static final int PRESENTER_NAVIGATION_VIEW_ID = 1; 83 84 private final NavigationMenu mMenu; 85 private final NavigationMenuPresenter mPresenter = new NavigationMenuPresenter(); 86 87 OnNavigationItemSelectedListener mListener; 88 private int mMaxWidth; 89 90 private MenuInflater mMenuInflater; 91 92 public NavigationView(Context context) { 93 this(context, null); 94 } 95 96 public NavigationView(Context context, AttributeSet attrs) { 97 this(context, attrs, 0); 98 } 99 100 public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) { 101 super(context, attrs, defStyleAttr); 102 103 ThemeUtils.checkAppCompatTheme(context); 104 105 // Create the menu 106 mMenu = new NavigationMenu(context); 107 108 // Custom attributes 109 TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, 110 R.styleable.NavigationView, defStyleAttr, 111 R.style.Widget_Design_NavigationView); 112 113 //noinspection deprecation 114 setBackgroundDrawable(a.getDrawable(R.styleable.NavigationView_android_background)); 115 if (a.hasValue(R.styleable.NavigationView_elevation)) { 116 ViewCompat.setElevation(this, a.getDimensionPixelSize( 117 R.styleable.NavigationView_elevation, 0)); 118 } 119 ViewCompat.setFitsSystemWindows(this, 120 a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false)); 121 122 mMaxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0); 123 124 final ColorStateList itemIconTint; 125 if (a.hasValue(R.styleable.NavigationView_itemIconTint)) { 126 itemIconTint = a.getColorStateList(R.styleable.NavigationView_itemIconTint); 127 } else { 128 itemIconTint = createDefaultColorStateList(android.R.attr.textColorSecondary); 129 } 130 131 boolean textAppearanceSet = false; 132 int textAppearance = 0; 133 if (a.hasValue(R.styleable.NavigationView_itemTextAppearance)) { 134 textAppearance = a.getResourceId(R.styleable.NavigationView_itemTextAppearance, 0); 135 textAppearanceSet = true; 136 } 137 138 ColorStateList itemTextColor = null; 139 if (a.hasValue(R.styleable.NavigationView_itemTextColor)) { 140 itemTextColor = a.getColorStateList(R.styleable.NavigationView_itemTextColor); 141 } 142 143 if (!textAppearanceSet && itemTextColor == null) { 144 // If there isn't a text appearance set, we'll use a default text color 145 itemTextColor = createDefaultColorStateList(android.R.attr.textColorPrimary); 146 } 147 148 final Drawable itemBackground = a.getDrawable(R.styleable.NavigationView_itemBackground); 149 150 mMenu.setCallback(new MenuBuilder.Callback() { 151 @Override 152 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 153 return mListener != null && mListener.onNavigationItemSelected(item); 154 } 155 156 @Override 157 public void onMenuModeChange(MenuBuilder menu) {} 158 }); 159 mPresenter.setId(PRESENTER_NAVIGATION_VIEW_ID); 160 mPresenter.initForMenu(context, mMenu); 161 mPresenter.setItemIconTintList(itemIconTint); 162 if (textAppearanceSet) { 163 mPresenter.setItemTextAppearance(textAppearance); 164 } 165 mPresenter.setItemTextColor(itemTextColor); 166 mPresenter.setItemBackground(itemBackground); 167 mMenu.addMenuPresenter(mPresenter); 168 addView((View) mPresenter.getMenuView(this)); 169 170 if (a.hasValue(R.styleable.NavigationView_menu)) { 171 inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0)); 172 } 173 174 if (a.hasValue(R.styleable.NavigationView_headerLayout)) { 175 inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0)); 176 } 177 178 a.recycle(); 179 } 180 181 @Override 182 protected Parcelable onSaveInstanceState() { 183 Parcelable superState = super.onSaveInstanceState(); 184 SavedState state = new SavedState(superState); 185 state.menuState = new Bundle(); 186 mMenu.savePresenterStates(state.menuState); 187 return state; 188 } 189 190 @Override 191 protected void onRestoreInstanceState(Parcelable savedState) { 192 if (!(savedState instanceof SavedState)) { 193 super.onRestoreInstanceState(savedState); 194 return; 195 } 196 SavedState state = (SavedState) savedState; 197 super.onRestoreInstanceState(state.getSuperState()); 198 mMenu.restorePresenterStates(state.menuState); 199 } 200 201 /** 202 * Set a listener that will be notified when a menu item is selected. 203 * 204 * @param listener The listener to notify 205 */ 206 public void setNavigationItemSelectedListener( 207 @Nullable OnNavigationItemSelectedListener listener) { 208 mListener = listener; 209 } 210 211 @Override 212 protected void onMeasure(int widthSpec, int heightSpec) { 213 switch (MeasureSpec.getMode(widthSpec)) { 214 case MeasureSpec.EXACTLY: 215 // Nothing to do 216 break; 217 case MeasureSpec.AT_MOST: 218 widthSpec = MeasureSpec.makeMeasureSpec( 219 Math.min(MeasureSpec.getSize(widthSpec), mMaxWidth), MeasureSpec.EXACTLY); 220 break; 221 case MeasureSpec.UNSPECIFIED: 222 widthSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY); 223 break; 224 } 225 // Let super sort out the height 226 super.onMeasure(widthSpec, heightSpec); 227 } 228 229 /** 230 * @hide 231 */ 232 @Override 233 protected void onInsetsChanged(WindowInsetsCompat insets) { 234 mPresenter.dispatchApplyWindowInsets(insets); 235 } 236 237 /** 238 * Inflate a menu resource into this navigation view. 239 * 240 * <p>Existing items in the menu will not be modified or removed.</p> 241 * 242 * @param resId ID of a menu resource to inflate 243 */ 244 public void inflateMenu(int resId) { 245 mPresenter.setUpdateSuspended(true); 246 getMenuInflater().inflate(resId, mMenu); 247 mPresenter.setUpdateSuspended(false); 248 mPresenter.updateMenuView(false); 249 } 250 251 /** 252 * Returns the {@link Menu} instance associated with this navigation view. 253 */ 254 public Menu getMenu() { 255 return mMenu; 256 } 257 258 /** 259 * Inflates a View and add it as a header of the navigation menu. 260 * 261 * @param res The layout resource ID. 262 * @return a newly inflated View. 263 */ 264 public View inflateHeaderView(@LayoutRes int res) { 265 return mPresenter.inflateHeaderView(res); 266 } 267 268 /** 269 * Adds a View as a header of the navigation menu. 270 * 271 * @param view The view to be added as a header of the navigation menu. 272 */ 273 public void addHeaderView(@NonNull View view) { 274 mPresenter.addHeaderView(view); 275 } 276 277 /** 278 * Removes a previously-added header view. 279 * 280 * @param view The view to remove 281 */ 282 public void removeHeaderView(@NonNull View view) { 283 mPresenter.removeHeaderView(view); 284 } 285 286 /** 287 * Gets the number of headers in this NavigationView. 288 * 289 * @return A positive integer representing the number of headers. 290 */ 291 public int getHeaderCount() { 292 return mPresenter.getHeaderCount(); 293 } 294 295 /** 296 * Gets the header view at the specified position. 297 * 298 * @param index The position at which to get the view from. 299 * @return The header view the specified position or null if the position does not exist in this 300 * NavigationView. 301 */ 302 public View getHeaderView(int index) { 303 return mPresenter.getHeaderView(index); 304 } 305 306 /** 307 * Returns the tint which is applied to our menu items' icons. 308 * 309 * @see #setItemIconTintList(ColorStateList) 310 * 311 * @attr ref R.styleable#NavigationView_itemIconTint 312 */ 313 @Nullable 314 public ColorStateList getItemIconTintList() { 315 return mPresenter.getItemTintList(); 316 } 317 318 /** 319 * Set the tint which is applied to our menu items' icons. 320 * 321 * @param tint the tint to apply. 322 * 323 * @attr ref R.styleable#NavigationView_itemIconTint 324 */ 325 public void setItemIconTintList(@Nullable ColorStateList tint) { 326 mPresenter.setItemIconTintList(tint); 327 } 328 329 /** 330 * Returns the tint which is applied to our menu items' icons. 331 * 332 * @see #setItemTextColor(ColorStateList) 333 * 334 * @attr ref R.styleable#NavigationView_itemTextColor 335 */ 336 @Nullable 337 public ColorStateList getItemTextColor() { 338 return mPresenter.getItemTextColor(); 339 } 340 341 /** 342 * Set the text color to be used on our menu items. 343 * 344 * @see #getItemTextColor() 345 * 346 * @attr ref R.styleable#NavigationView_itemTextColor 347 */ 348 public void setItemTextColor(@Nullable ColorStateList textColor) { 349 mPresenter.setItemTextColor(textColor); 350 } 351 352 /** 353 * Returns the background drawable for our menu items. 354 * 355 * @see #setItemBackgroundResource(int) 356 * 357 * @attr ref R.styleable#NavigationView_itemBackground 358 */ 359 @Nullable 360 public Drawable getItemBackground() { 361 return mPresenter.getItemBackground(); 362 } 363 364 /** 365 * Set the background of our menu items to the given resource. 366 * 367 * @param resId The identifier of the resource. 368 * 369 * @attr ref R.styleable#NavigationView_itemBackground 370 */ 371 public void setItemBackgroundResource(@DrawableRes int resId) { 372 setItemBackground(ContextCompat.getDrawable(getContext(), resId)); 373 } 374 375 /** 376 * Set the background of our menu items to a given resource. The resource should refer to 377 * a Drawable object or null to use the default background set on this navigation menu. 378 * 379 * @attr ref R.styleable#NavigationView_itemBackground 380 */ 381 public void setItemBackground(@Nullable Drawable itemBackground) { 382 mPresenter.setItemBackground(itemBackground); 383 } 384 385 /** 386 * Sets the currently checked item in this navigation menu. 387 * 388 * @param id The item ID of the currently checked item. 389 */ 390 public void setCheckedItem(@IdRes int id) { 391 MenuItem item = mMenu.findItem(id); 392 if (item != null) { 393 mPresenter.setCheckedItem((MenuItemImpl) item); 394 } 395 } 396 397 /** 398 * Set the text appearance of the menu items to a given resource. 399 * 400 * @attr ref R.styleable#NavigationView_itemTextAppearance 401 */ 402 public void setItemTextAppearance(@StyleRes int resId) { 403 mPresenter.setItemTextAppearance(resId); 404 } 405 406 private MenuInflater getMenuInflater() { 407 if (mMenuInflater == null) { 408 mMenuInflater = new SupportMenuInflater(getContext()); 409 } 410 return mMenuInflater; 411 } 412 413 private ColorStateList createDefaultColorStateList(int baseColorThemeAttr) { 414 final TypedValue value = new TypedValue(); 415 if (!getContext().getTheme().resolveAttribute(baseColorThemeAttr, value, true)) { 416 return null; 417 } 418 ColorStateList baseColor = AppCompatResources.getColorStateList( 419 getContext(), value.resourceId); 420 if (!getContext().getTheme().resolveAttribute( 421 android.support.v7.appcompat.R.attr.colorPrimary, value, true)) { 422 return null; 423 } 424 int colorPrimary = value.data; 425 int defaultColor = baseColor.getDefaultColor(); 426 return new ColorStateList(new int[][]{ 427 DISABLED_STATE_SET, 428 CHECKED_STATE_SET, 429 EMPTY_STATE_SET 430 }, new int[]{ 431 baseColor.getColorForState(DISABLED_STATE_SET, defaultColor), 432 colorPrimary, 433 defaultColor 434 }); 435 } 436 437 /** 438 * Listener for handling events on navigation items. 439 */ 440 public interface OnNavigationItemSelectedListener { 441 442 /** 443 * Called when an item in the navigation menu is selected. 444 * 445 * @param item The selected item 446 * 447 * @return true to display the item as the selected item 448 */ 449 public boolean onNavigationItemSelected(@NonNull MenuItem item); 450 } 451 452 /** 453 * User interface state that is stored by NavigationView for implementing 454 * onSaveInstanceState(). 455 */ 456 public static class SavedState extends AbsSavedState { 457 public Bundle menuState; 458 459 public SavedState(Parcel in, ClassLoader loader) { 460 super(in, loader); 461 menuState = in.readBundle(loader); 462 } 463 464 public SavedState(Parcelable superState) { 465 super(superState); 466 } 467 468 @Override 469 public void writeToParcel(@NonNull Parcel dest, int flags) { 470 super.writeToParcel(dest, flags); 471 dest.writeBundle(menuState); 472 } 473 474 public static final Parcelable.Creator<SavedState> CREATOR 475 = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() { 476 @Override 477 public SavedState createFromParcel(Parcel parcel, ClassLoader loader) { 478 return new SavedState(parcel, loader); 479 } 480 481 @Override 482 public SavedState[] newArray(int size) { 483 return new SavedState[size]; 484 } 485 }); 486 } 487 488} 489