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