1 /* 2 * Copyright (C) 2011 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 17 package androidx.appcompat.view.menu; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.content.Context; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.view.ViewGroup; 25 26 import androidx.annotation.RestrictTo; 27 28 import java.util.ArrayList; 29 30 /** 31 * Base class for MenuPresenters that have a consistent container view and item views. Behaves 32 * similarly to an AdapterView in that existing item views will be reused if possible when items 33 * change. 34 * 35 * @hide 36 */ 37 @RestrictTo(LIBRARY_GROUP) 38 public abstract class BaseMenuPresenter implements MenuPresenter { 39 40 protected Context mSystemContext; 41 protected Context mContext; 42 protected MenuBuilder mMenu; 43 protected LayoutInflater mSystemInflater; 44 protected LayoutInflater mInflater; 45 private Callback mCallback; 46 47 private int mMenuLayoutRes; 48 private int mItemLayoutRes; 49 50 protected MenuView mMenuView; 51 52 private int mId; 53 54 /** 55 * Construct a new BaseMenuPresenter. 56 * 57 * @param context Context for generating system-supplied views 58 * @param menuLayoutRes Layout resource ID for the menu container view 59 * @param itemLayoutRes Layout resource ID for a single item view 60 */ 61 public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) { 62 mSystemContext = context; 63 mSystemInflater = LayoutInflater.from(context); 64 mMenuLayoutRes = menuLayoutRes; 65 mItemLayoutRes = itemLayoutRes; 66 } 67 68 @Override 69 public void initForMenu(Context context, MenuBuilder menu) { 70 mContext = context; 71 mInflater = LayoutInflater.from(mContext); 72 mMenu = menu; 73 } 74 75 @Override 76 public MenuView getMenuView(ViewGroup root) { 77 if (mMenuView == null) { 78 mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false); 79 mMenuView.initialize(mMenu); 80 updateMenuView(true); 81 } 82 83 return mMenuView; 84 } 85 86 /** 87 * Reuses item views when it can 88 */ 89 @Override 90 public void updateMenuView(boolean cleared) { 91 final ViewGroup parent = (ViewGroup) mMenuView; 92 if (parent == null) return; 93 94 int childIndex = 0; 95 if (mMenu != null) { 96 mMenu.flagActionItems(); 97 ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems(); 98 final int itemCount = visibleItems.size(); 99 for (int i = 0; i < itemCount; i++) { 100 MenuItemImpl item = visibleItems.get(i); 101 if (shouldIncludeItem(childIndex, item)) { 102 final View convertView = parent.getChildAt(childIndex); 103 final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ? 104 ((MenuView.ItemView) convertView).getItemData() : null; 105 final View itemView = getItemView(item, convertView, parent); 106 if (item != oldItem) { 107 // Don't let old states linger with new data. 108 itemView.setPressed(false); 109 itemView.jumpDrawablesToCurrentState(); 110 } 111 if (itemView != convertView) { 112 addItemView(itemView, childIndex); 113 } 114 childIndex++; 115 } 116 } 117 } 118 119 // Remove leftover views. 120 while (childIndex < parent.getChildCount()) { 121 if (!filterLeftoverView(parent, childIndex)) { 122 childIndex++; 123 } 124 } 125 } 126 127 /** 128 * Add an item view at the given index. 129 * 130 * @param itemView View to add 131 * @param childIndex Index within the parent to insert at 132 */ 133 protected void addItemView(View itemView, int childIndex) { 134 final ViewGroup currentParent = (ViewGroup) itemView.getParent(); 135 if (currentParent != null) { 136 currentParent.removeView(itemView); 137 } 138 ((ViewGroup) mMenuView).addView(itemView, childIndex); 139 } 140 141 /** 142 * Filter the child view at index and remove it if appropriate. 143 * @param parent Parent to filter from 144 * @param childIndex Index to filter 145 * @return true if the child view at index was removed 146 */ 147 protected boolean filterLeftoverView(ViewGroup parent, int childIndex) { 148 parent.removeViewAt(childIndex); 149 return true; 150 } 151 152 @Override 153 public void setCallback(Callback cb) { 154 mCallback = cb; 155 } 156 157 public Callback getCallback() { 158 return mCallback; 159 } 160 161 /** 162 * Create a new item view that can be re-bound to other item data later. 163 * 164 * @return The new item view 165 */ 166 public MenuView.ItemView createItemView(ViewGroup parent) { 167 return (MenuView.ItemView) mSystemInflater.inflate(mItemLayoutRes, parent, false); 168 } 169 170 /** 171 * Prepare an item view for use. See AdapterView for the basic idea at work here. 172 * This may require creating a new item view, but well-behaved implementations will 173 * re-use the view passed as convertView if present. The returned view will be populated 174 * with data from the item parameter. 175 * 176 * @param item Item to present 177 * @param convertView Existing view to reuse 178 * @param parent Intended parent view - use for inflation. 179 * @return View that presents the requested menu item 180 */ 181 public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) { 182 MenuView.ItemView itemView; 183 if (convertView instanceof MenuView.ItemView) { 184 itemView = (MenuView.ItemView) convertView; 185 } else { 186 itemView = createItemView(parent); 187 } 188 bindItemView(item, itemView); 189 return (View) itemView; 190 } 191 192 /** 193 * Bind item data to an existing item view. 194 * 195 * @param item Item to bind 196 * @param itemView View to populate with item data 197 */ 198 public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView); 199 200 /** 201 * Filter item by child index and item data. 202 * 203 * @param childIndex Indended presentation index of this item 204 * @param item Item to present 205 * @return true if this item should be included in this menu presentation; false otherwise 206 */ 207 public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) { 208 return true; 209 } 210 211 @Override 212 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 213 if (mCallback != null) { 214 mCallback.onCloseMenu(menu, allMenusAreClosing); 215 } 216 } 217 218 @Override 219 public boolean onSubMenuSelected(SubMenuBuilder menu) { 220 if (mCallback != null) { 221 return mCallback.onOpenSubMenu(menu); 222 } 223 return false; 224 } 225 226 @Override 227 public boolean flagActionItems() { 228 return false; 229 } 230 231 @Override 232 public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { 233 return false; 234 } 235 236 @Override 237 public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { 238 return false; 239 } 240 241 @Override 242 public int getId() { 243 return mId; 244 } 245 246 public void setId(int id) { 247 mId = id; 248 } 249 } 250