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