NavigationMenuPresenter.java revision 01090f9556e7518c9ee206de6efe42de9003f6f5
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.internal; 18 19import android.content.Context; 20import android.os.Bundle; 21import android.os.Parcelable; 22import android.support.annotation.LayoutRes; 23import android.support.annotation.NonNull; 24import android.support.design.R; 25import android.support.v7.internal.view.menu.MenuBuilder; 26import android.support.v7.internal.view.menu.MenuItemImpl; 27import android.support.v7.internal.view.menu.MenuPresenter; 28import android.support.v7.internal.view.menu.MenuView; 29import android.support.v7.internal.view.menu.SubMenuBuilder; 30import android.util.SparseArray; 31import android.view.LayoutInflater; 32import android.view.MenuItem; 33import android.view.SubMenu; 34import android.view.View; 35import android.view.ViewGroup; 36import android.widget.AdapterView; 37import android.widget.BaseAdapter; 38import android.widget.TextView; 39 40import java.util.ArrayList; 41 42/** 43 * @hide 44 */ 45public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener { 46 47 private static final String STATE_HIERARCHY = "android:menu:list"; 48 49 private NavigationMenuView mMenuView; 50 51 private Callback mCallback; 52 53 private MenuBuilder mMenu; 54 55 private ArrayList<NavigationMenuItem> mItems = new ArrayList<>(); 56 57 private int mId; 58 59 private NavigationMenuAdapter mAdapter; 60 61 private LayoutInflater mLayoutInflater; 62 63 private View mSpace; 64 65 /** 66 * Padding to be inserted at the top of the list to avoid the first menu item 67 * from being placed underneath the status bar. 68 */ 69 private int mPaddingTopDefault; 70 71 @Override 72 public void initForMenu(Context context, MenuBuilder menu) { 73 mLayoutInflater = LayoutInflater.from(context); 74 mMenu = menu; 75 mPaddingTopDefault = context.getResources().getDimensionPixelOffset( 76 R.dimen.drawer_padding_top_default); 77 } 78 79 @Override 80 public MenuView getMenuView(ViewGroup root) { 81 if (mMenuView == null) { 82 mMenuView = (NavigationMenuView) mLayoutInflater.inflate( 83 R.layout.design_drawer_menu, root, false); 84 if (mAdapter == null) { 85 mAdapter = new NavigationMenuAdapter(); 86 } 87 mMenuView.setAdapter(mAdapter); 88 mMenuView.setOnItemClickListener(this); 89 } 90 return mMenuView; 91 } 92 93 @Override 94 public void updateMenuView(boolean cleared) { 95 if (mAdapter != null) { 96 prepareMenuItems(); 97 mAdapter.notifyDataSetChanged(); 98 } 99 } 100 101 /** 102 * Flattens the visible menu items of {@link #mMenu} into {@link #mItems}, 103 * while inserting separators between items when necessary. 104 */ 105 private void prepareMenuItems() { 106 mItems.clear(); 107 int currentGroupId = 0; 108 for (MenuItemImpl item : mMenu.getVisibleItems()) { 109 if (item.hasSubMenu()) { 110 SubMenu subMenu = item.getSubMenu(); 111 if (subMenu.hasVisibleItems()) { 112 mItems.add(NavigationMenuItem.SEPARATOR); 113 mItems.add(NavigationMenuItem.of(item)); 114 for (int i = 0, size = subMenu.size(); i < size; i++) { 115 MenuItem subMenuItem = subMenu.getItem(i); 116 if (subMenuItem.isVisible()) { 117 mItems.add(NavigationMenuItem.of((MenuItemImpl) subMenuItem)); 118 } 119 } 120 } 121 } else { 122 int groupId = item.getGroupId(); 123 if (groupId != currentGroupId) { 124 mItems.add(NavigationMenuItem.SEPARATOR); 125 } 126 mItems.add(NavigationMenuItem.of(item)); 127 currentGroupId = groupId; 128 } 129 } 130 } 131 132 @Override 133 public void setCallback(Callback cb) { 134 mCallback = cb; 135 } 136 137 @Override 138 public boolean onSubMenuSelected(SubMenuBuilder subMenu) { 139 return false; 140 } 141 142 @Override 143 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 144 if (mCallback != null) { 145 mCallback.onCloseMenu(menu, allMenusAreClosing); 146 } 147 } 148 149 @Override 150 public boolean flagActionItems() { 151 return false; 152 } 153 154 @Override 155 public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { 156 return false; 157 } 158 159 @Override 160 public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { 161 return false; 162 } 163 164 @Override 165 public int getId() { 166 return mId; 167 } 168 169 public void setId(int id) { 170 mId = id; 171 } 172 173 @Override 174 public Parcelable onSaveInstanceState() { 175 Bundle state = new Bundle(); 176 SparseArray<Parcelable> hierarchy = new SparseArray<>(); 177 if (mMenuView != null) { 178 mMenuView.saveHierarchyState(hierarchy); 179 } 180 state.putSparseParcelableArray(STATE_HIERARCHY, hierarchy); 181 return state; 182 } 183 184 @Override 185 public void onRestoreInstanceState(Parcelable parcelable) { 186 Bundle state = (Bundle) parcelable; 187 SparseArray<Parcelable> hierarchy = state.getSparseParcelableArray(STATE_HIERARCHY); 188 if (hierarchy != null) { 189 mMenuView.restoreHierarchyState(hierarchy); 190 } 191 } 192 193 @Override 194 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 195 int positionInAdapter = position - mMenuView.getHeaderViewsCount(); 196 if (positionInAdapter >= 0) { 197 mMenu.performItemAction(mAdapter.getItem(positionInAdapter).getMenuItem(), this, 0); 198 } 199 } 200 201 public View inflateHeaderView(@LayoutRes int res) { 202 View view = mLayoutInflater.inflate(res, mMenuView, false); 203 addHeaderView(view); 204 onHeaderAdded(); 205 return view; 206 } 207 208 public void addHeaderView(@NonNull View view) { 209 mMenuView.addHeaderView(view); 210 onHeaderAdded(); 211 } 212 213 private void onHeaderAdded() { 214 // If we have just added the first header, we also need to insert a space 215 // between the header and the menu items. 216 if (mMenuView.getHeaderViewsCount() == 1) { 217 mSpace = mLayoutInflater.inflate(R.layout.design_drawer_item_space, mMenuView, false); 218 mMenuView.addHeaderView(mSpace); 219 } 220 // The padding on top should be cleared. 221 mMenuView.setPadding(0, 0, 0, 0); 222 } 223 224 public void removeHeaderView(@NonNull View view) { 225 if (mMenuView.removeHeaderView(view)) { 226 // Remove the space if it is the only remained header 227 if (mMenuView.getHeaderViewsCount() == 1) { 228 mMenuView.removeHeaderView(mSpace); 229 mMenuView.setPadding(0, mPaddingTopDefault, 0, 0); 230 } 231 } 232 } 233 234 private class NavigationMenuAdapter extends BaseAdapter { 235 236 private static final int VIEW_TYPE_NORMAL = 0; 237 238 private static final int VIEW_TYPE_SUBHEADER = 1; 239 240 private static final int VIEW_TYPE_SEPARATOR = 2; 241 242 @Override 243 public int getCount() { 244 return mItems.size(); 245 } 246 247 @Override 248 public NavigationMenuItem getItem(int position) { 249 return mItems.get(position); 250 } 251 252 @Override 253 public long getItemId(int position) { 254 return position; 255 } 256 257 @Override 258 public int getViewTypeCount() { 259 return 3; 260 } 261 262 @Override 263 public int getItemViewType(int position) { 264 NavigationMenuItem item = getItem(position); 265 if (item.isSeparator()) { 266 return VIEW_TYPE_SEPARATOR; 267 } else if (item.getMenuItem().hasSubMenu()) { 268 return VIEW_TYPE_SUBHEADER; 269 } else { 270 return VIEW_TYPE_NORMAL; 271 } 272 } 273 274 @Override 275 public View getView(int position, View convertView, ViewGroup parent) { 276 NavigationMenuItem item = getItem(position); 277 int viewType = getItemViewType(position); 278 switch (viewType) { 279 case VIEW_TYPE_NORMAL: 280 if (convertView == null) { 281 convertView = mLayoutInflater.inflate(R.layout.design_drawer_item, parent, 282 false); 283 } 284 MenuView.ItemView itemView = (MenuView.ItemView) convertView; 285 itemView.initialize(item.getMenuItem(), 0); 286 break; 287 case VIEW_TYPE_SUBHEADER: 288 if (convertView == null) { 289 convertView = mLayoutInflater.inflate(R.layout.design_drawer_item_subheader, 290 parent, false); 291 } 292 TextView subHeader = (TextView) convertView; 293 subHeader.setText(item.getMenuItem().getTitle()); 294 break; 295 case VIEW_TYPE_SEPARATOR: 296 if (convertView == null) { 297 convertView = mLayoutInflater.inflate(R.layout.design_drawer_item_separator, 298 parent, false); 299 } 300 break; 301 } 302 return convertView; 303 } 304 305 @Override 306 public boolean areAllItemsEnabled() { 307 return false; 308 } 309 310 @Override 311 public boolean isEnabled(int position) { 312 return getItem(position).isEnabled(); 313 } 314 315 } 316 317 /** 318 * Wraps {@link MenuItemImpl}. This allows separators to be counted as items in list. 319 */ 320 private static class NavigationMenuItem { 321 322 private static final NavigationMenuItem SEPARATOR = new NavigationMenuItem(null); 323 324 private final MenuItemImpl mMenuItem; 325 326 private NavigationMenuItem(MenuItemImpl item) { 327 mMenuItem = item; 328 } 329 330 public static NavigationMenuItem of(MenuItemImpl item) { 331 return new NavigationMenuItem(item); 332 } 333 334 public boolean isSeparator() { 335 return this == SEPARATOR; 336 } 337 338 public MenuItemImpl getMenuItem() { 339 return mMenuItem; 340 } 341 342 public boolean isEnabled() { 343 // Separators and subheaders never respond to click 344 return mMenuItem != null && !mMenuItem.hasSubMenu() && mMenuItem.isEnabled(); 345 } 346 347 } 348 349} 350