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