R.menu.main_activity)
)
78 | * @param menu The Menu to inflate into. The items and submenus will be
79 | * added to this Menu.
80 | */
81 | public void inflate(int menuRes, Menu menu) {
82 | menu.clear();
83 |
84 | mInflater.inflate(menuRes, menu);
85 |
86 | afterInflate(menuRes, menu);
87 | }
88 |
89 | private void afterInflate(int menuRes, Menu menu){
90 | IconData root = new IconData(0, 0, 0);
91 | XmlResourceParser parser = null;
92 | try {
93 | parser = mContext.getResources().getLayout(menuRes);
94 | AttributeSet attrs = Xml.asAttributeSet(parser);
95 |
96 | parseMenu(parser, attrs, root);
97 | } catch (XmlPullParserException e) {
98 | throw new InflateException("Error inflating menu XML", e);
99 | } catch (IOException e) {
100 | throw new InflateException("Error inflating menu XML", e);
101 | } finally {
102 | if (parser != null) parser.close();
103 |
104 | // populate the menu with the parsed icons
105 | populateIcons(menu, root, mDefaultColor);
106 | }
107 | }
108 |
109 | private int getDefaultColor(){
110 | TypedValue outValue = new TypedValue();
111 | mContext.getTheme().resolveAttribute(R.attr.materialIconColor, outValue, true);
112 |
113 | // Colorstatelist/color resource
114 | if(outValue.resourceId != 0 && outValue.type == TypedValue.TYPE_ATTRIBUTE){
115 | ColorStateList stateList = null;
116 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
117 | stateList = mContext.getResources().getColorStateList(outValue.resourceId, mContext.getTheme());
118 | }else{
119 | stateList = mContext.getResources().getColorStateList(outValue.resourceId);
120 | }
121 | if(stateList != null) return stateList.getDefaultColor();
122 | }
123 |
124 | // Regular inline color int
125 | if(outValue.type <= TypedValue.TYPE_LAST_COLOR_INT && outValue.type >= TypedValue.TYPE_FIRST_COLOR_INT){
126 | return outValue.data;
127 | }
128 | return Color.BLACK;
129 | }
130 |
131 | private void populateIcons(Menu menu, IconData root, int defaultIconColor) {
132 | for(int i = 0; i < menu.size(); i++){
133 | MenuItem m = menu.getItem(i);
134 | IconData d = root.children.get(i);
135 |
136 | if(m.hasSubMenu()){
137 | populateIcons(m.getSubMenu(), d, defaultIconColor);
138 | }
139 |
140 | if(d.itemIconResId >= 0)
141 | m.setIcon(
142 | MaterialDrawableBuilder.with(mContext)
143 | .setIcon(MaterialDrawableBuilder.IconValue.values()[d.itemIconResId])
144 | .setColor(d.itemColor != -1 ? d.itemColor : defaultIconColor)
145 | .setToActionbarSize()
146 | .build()
147 | );
148 | }
149 | }
150 |
151 | /**
152 | * Called internally to fill the given menu. If a sub menu is seen, it will
153 | * call this recursively.
154 | */
155 | private void parseMenu(XmlPullParser parser, AttributeSet attrs, IconData menu)
156 | throws XmlPullParserException, IOException {
157 | MenuState menuState = new MenuState(menu);
158 |
159 | int eventType = parser.getEventType();
160 | String tagName;
161 | boolean lookingForEndOfUnknownTag = false;
162 | String unknownTagName = null;
163 |
164 | // This loop will skip to the menu start tag
165 | do {
166 | if (eventType == XmlPullParser.START_TAG) {
167 | tagName = parser.getName();
168 | if (tagName.equals(XML_MENU)) {
169 | // Go to next tag
170 | eventType = parser.next();
171 | break;
172 | }
173 |
174 | throw new RuntimeException("Expecting menu, got " + tagName);
175 | }
176 | eventType = parser.next();
177 | } while (eventType != XmlPullParser.END_DOCUMENT);
178 |
179 | boolean reachedEndOfMenu = false;
180 | while (!reachedEndOfMenu) {
181 | switch (eventType) {
182 | case XmlPullParser.START_TAG:
183 | if (lookingForEndOfUnknownTag) {
184 | break;
185 | }
186 |
187 | tagName = parser.getName();
188 | if (tagName.equals(XML_GROUP)) {
189 | menuState.readGroup(attrs);
190 | } else if (tagName.equals(XML_ITEM)) {
191 | menuState.readItem(attrs);
192 | } else if (tagName.equals(XML_MENU)) {
193 | // A menu start tag denotes a submenu for an item
194 | IconData subMenu = menuState.addSubMenuItem();
195 |
196 | // Parse the submenu into returned SubMenu
197 | parseMenu(parser, attrs, subMenu);
198 | } else {
199 | lookingForEndOfUnknownTag = true;
200 | unknownTagName = tagName;
201 | }
202 | break;
203 |
204 | case XmlPullParser.END_TAG:
205 | tagName = parser.getName();
206 | if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
207 | lookingForEndOfUnknownTag = false;
208 | unknownTagName = null;
209 | } else if (tagName.equals(XML_GROUP)) {
210 | menuState.resetGroup();
211 | } else if (tagName.equals(XML_ITEM)) {
212 | // Add the item if it hasn't been added (if the item was
213 | // a submenu, it would have been added already)
214 | if (!menuState.hasAddedItem()) {
215 | menuState.addItem();
216 | }
217 | } else if (tagName.equals(XML_MENU)) {
218 | reachedEndOfMenu = true;
219 | }
220 | break;
221 |
222 | case XmlPullParser.END_DOCUMENT:
223 | throw new RuntimeException("Unexpected end of document");
224 | }
225 |
226 | eventType = parser.next();
227 | }
228 | }
229 |
230 | private class IconData {
231 | public int itemIconResId;
232 | public int itemColor;
233 | public int categoryOrder;
234 | public List
246 | * Groups can not be nested unless there is another menu (which will have
247 | * its state class).
248 | */
249 | private class MenuState {
250 |
251 | /**
252 | * This is the part of an order integer that the user can provide.
253 | *
254 | * @hide
255 | */
256 | static final int USER_MASK = 0x0000ffff;
257 | /**
258 | * Bit shift of the user portion of the order integer.
259 | *
260 | * @hide
261 | */
262 | static final int USER_SHIFT = 0;
263 |
264 | /**
265 | * This is the part of an order integer that supplies the category of the item.
266 | *
267 | * @hide
268 | */
269 | static final int CATEGORY_MASK = 0xffff0000;
270 | /**
271 | * Bit shift of the category portion of the order integer.
272 | *
273 | * @hide
274 | */
275 | static final int CATEGORY_SHIFT = 16;
276 |
277 | final int[] sCategoryToOrder = new int[]{
278 | 1, /* No category */
279 | 4, /* CONTAINER */
280 | 5, /* SYSTEM */
281 | 3, /* SECONDARY */
282 | 2, /* ALTERNATIVE */
283 | 0, /* SELECTED_ALTERNATIVE */
284 | };
285 |
286 | private static final int defaultItemCategory = 0;
287 | private static final int defaultItemOrder = 0;
288 |
289 |
290 | private boolean itemAdded;
291 | private int itemIconResId;
292 | private int itemIconColor;
293 | private int categoryOrder;
294 | private int groupCategory;
295 | private int groupOrder;
296 |
297 | private IconData menu;
298 |
299 | public MenuState(IconData menu) {
300 | this.menu = menu;
301 |
302 | resetGroup();
303 | }
304 |
305 | public void resetGroup() {
306 | groupCategory = defaultItemCategory;
307 | groupOrder = defaultItemOrder;
308 | }
309 |
310 | /**
311 | * Called when the parser is pointing to a group tag.
312 | */
313 | public void readGroup(AttributeSet attrs) {
314 | TypedArray a = mContext.obtainStyledAttributes(attrs,
315 | net.steamcrafted.materialiconlib.R.styleable.MaterialMenuGroup);
316 |
317 | groupCategory = a.getInt(net.steamcrafted.materialiconlib.R.styleable.MaterialMenuGroup_android_menuCategory, defaultItemCategory);
318 | groupOrder = a.getInt(net.steamcrafted.materialiconlib.R.styleable.MaterialMenuGroup_android_orderInCategory, defaultItemOrder);
319 |
320 | a.recycle();
321 | }
322 |
323 | /**
324 | * Called when the parser is pointing to an item tag.
325 | */
326 | public void readItem(AttributeSet attrs) {
327 | TypedArray a = mContext.getApplicationContext().obtainStyledAttributes(attrs,
328 | net.steamcrafted.materialiconlib.R.styleable.MaterialIconViewFormat);
329 |
330 | // Inherit attributes from the group as default value
331 | itemIconResId = a.getInt(net.steamcrafted.materialiconlib.R.styleable.MaterialIconViewFormat_materialIcon, -1);
332 | itemIconColor = a.getColor(net.steamcrafted.materialiconlib.R.styleable.MaterialIconViewFormat_materialIconColor, -1);
333 |
334 | a.recycle();
335 |
336 | a = mContext.obtainStyledAttributes(attrs,
337 | net.steamcrafted.materialiconlib.R.styleable.MaterialMenuItem);
338 |
339 | final int category = a.getInt(net.steamcrafted.materialiconlib.R.styleable.MaterialMenuGroup_android_menuCategory, groupCategory);
340 | final int order = a.getInt(net.steamcrafted.materialiconlib.R.styleable.MaterialMenuGroup_android_orderInCategory, groupOrder);
341 | categoryOrder = (category & CATEGORY_MASK) | (order & USER_MASK);
342 |
343 | a.recycle();
344 |
345 | itemAdded = false;
346 | }
347 |
348 | public IconData addItem() {
349 | itemAdded = true;
350 |
351 | final int ordering = getOrdering(categoryOrder);
352 |
353 | final IconData item = new IconData(itemIconResId, itemIconColor, ordering);
354 |
355 | menu.children.add(findInsertIndex(menu.children, ordering), item);
356 |
357 | return item;
358 | }
359 |
360 | public IconData addSubMenuItem() {
361 | return addItem();
362 | }
363 |
364 | public boolean hasAddedItem() {
365 | return itemAdded;
366 | }
367 |
368 | /**
369 | * Returns the ordering across all items. This will grab the category from
370 | * the upper bits, find out how to order the category with respect to other
371 | * categories, and combine it with the lower bits.
372 | *
373 | * @param categoryOrder The category order for a particular item (if it has
374 | * not been or/add with a category, the default category is
375 | * assumed).
376 | * @return An ordering integer that can be used to order this item across
377 | * all the items (even from other categories).
378 | */
379 | private int getOrdering(int categoryOrder) {
380 | final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
381 |
382 | if (index < 0 || index >= sCategoryToOrder.length) {
383 | throw new IllegalArgumentException("order does not contain a valid category.");
384 | }
385 |
386 | return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
387 | }
388 |
389 |
390 | private int findInsertIndex(List