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