ListMenuPresenter.java revision da10fdd1400ecfd8d7f2e55651dd528d0614dfc5
1/*
2 * Copyright (C) 2013 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.v7.internal.view.menu;
18
19import android.content.Context;
20import android.database.DataSetObserver;
21import android.os.Bundle;
22import android.os.Parcelable;
23import android.support.v7.appcompat.R;
24import android.util.SparseArray;
25import android.view.ContextThemeWrapper;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.AdapterView;
30import android.widget.BaseAdapter;
31import android.widget.ListAdapter;
32
33import java.util.ArrayList;
34
35/**
36 * MenuPresenter for list-style menus.
37 */
38public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener {
39    private static final String TAG = "ListMenuPresenter";
40
41    Context mContext;
42    LayoutInflater mInflater;
43    MenuBuilder mMenu;
44
45    ExpandedMenuView mMenuView;
46
47    private int mItemIndexOffset;
48    int mThemeRes;
49    int mItemLayoutRes;
50
51    private Callback mCallback;
52    MenuAdapter mAdapter;
53
54    private int mId;
55
56    public static final String VIEWS_TAG = "android:menu:list";
57
58    /**
59     * Construct a new ListMenuPresenter.
60     * @param context Context to use for theming. This will supersede the context provided
61     *                to initForMenu when this presenter is added.
62     * @param itemLayoutRes Layout resource for individual item views.
63     */
64    public ListMenuPresenter(Context context, int itemLayoutRes) {
65        this(itemLayoutRes, 0);
66        mContext = context;
67        mInflater = LayoutInflater.from(mContext);
68    }
69
70    /**
71     * Construct a new ListMenuPresenter.
72     * @param itemLayoutRes Layout resource for individual item views.
73     * @param themeRes Resource ID of a theme to use for views.
74     */
75    public ListMenuPresenter(int itemLayoutRes, int themeRes) {
76        mItemLayoutRes = itemLayoutRes;
77        mThemeRes = themeRes;
78    }
79
80    @Override
81    public void initForMenu(Context context, MenuBuilder menu) {
82        if (mThemeRes != 0) {
83            mContext = new ContextThemeWrapper(context, mThemeRes);
84            mInflater = LayoutInflater.from(mContext);
85        } else if (mContext != null) {
86            mContext = context;
87            if (mInflater == null) {
88                mInflater = LayoutInflater.from(mContext);
89            }
90        }
91        mMenu = menu;
92        if (mAdapter != null) {
93            mAdapter.notifyDataSetChanged();
94        }
95    }
96
97    @Override
98    public MenuView getMenuView(ViewGroup root) {
99        if (mAdapter == null) {
100            mAdapter = new MenuAdapter();
101        }
102
103        if (!mAdapter.isEmpty()) {
104            if (mMenuView == null) {
105                mMenuView = (ExpandedMenuView) mInflater.inflate(
106                        R.layout.expanded_menu_layout, root, false);
107                mMenuView.setAdapter(mAdapter);
108                mMenuView.setOnItemClickListener(this);
109            }
110            return mMenuView;
111        }
112
113        // If we reach here, the Menu is empty so we have nothing to display
114        return null;
115    }
116
117    /**
118     * Call this instead of getMenuView if you want to manage your own ListView.
119     * For proper operation, the ListView hosting this adapter should add
120     * this presenter as an OnItemClickListener.
121     *
122     * @return A ListAdapter containing the items in the menu.
123     */
124    public ListAdapter getAdapter() {
125        if (mAdapter == null) {
126            mAdapter = new MenuAdapter();
127        }
128        return mAdapter;
129    }
130
131    @Override
132    public void updateMenuView(boolean cleared) {
133        if (mAdapter != null) mAdapter.notifyDataSetChanged();
134    }
135
136    @Override
137    public void setCallback(Callback cb) {
138        mCallback = cb;
139    }
140
141    @Override
142    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
143        if (!subMenu.hasVisibleItems()) return false;
144
145        // The window manager will give us a token.
146        new MenuDialogHelper(subMenu).show(null);
147        if (mCallback != null) {
148            mCallback.onOpenSubMenu(subMenu);
149        }
150        return true;
151    }
152
153    @Override
154    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
155        if (mCallback != null) {
156            mCallback.onCloseMenu(menu, allMenusAreClosing);
157        }
158    }
159
160    int getItemIndexOffset() {
161        return mItemIndexOffset;
162    }
163
164    public void setItemIndexOffset(int offset) {
165        mItemIndexOffset = offset;
166        if (mMenuView != null) {
167            updateMenuView(false);
168        }
169    }
170
171    @Override
172    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
173        mMenu.performItemAction(mAdapter.getItem(position), 0);
174    }
175
176    @Override
177    public boolean flagActionItems() {
178        return false;
179    }
180
181    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
182        return false;
183    }
184
185    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
186        return false;
187    }
188
189    public void saveHierarchyState(Bundle outState) {
190        SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
191        if (mMenuView != null) {
192            ((View) mMenuView).saveHierarchyState(viewStates);
193        }
194        outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
195    }
196
197    public void restoreHierarchyState(Bundle inState) {
198        SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
199        if (viewStates != null) {
200            ((View) mMenuView).restoreHierarchyState(viewStates);
201        }
202    }
203
204    public void setId(int id) {
205        mId = id;
206    }
207
208    @Override
209    public int getId() {
210        return mId;
211    }
212
213    @Override
214    public Parcelable onSaveInstanceState() {
215        if (mMenuView == null) {
216            return null;
217        }
218
219        Bundle state = new Bundle();
220        saveHierarchyState(state);
221        return state;
222    }
223
224    @Override
225    public void onRestoreInstanceState(Parcelable state) {
226        restoreHierarchyState((Bundle) state);
227    }
228
229    private class MenuAdapter extends BaseAdapter {
230        private int mExpandedIndex = -1;
231
232        public MenuAdapter() {
233            findExpandedIndex();
234        }
235
236        public int getCount() {
237            ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
238            int count = items.size() - mItemIndexOffset;
239            if (mExpandedIndex < 0) {
240                return count;
241            }
242            return count - 1;
243        }
244
245        public MenuItemImpl getItem(int position) {
246            ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
247            position += mItemIndexOffset;
248            if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
249                position++;
250            }
251            return items.get(position);
252        }
253
254        public long getItemId(int position) {
255            // Since a menu item's ID is optional, we'll use the position as an
256            // ID for the item in the AdapterView
257            return position;
258        }
259
260        public View getView(int position, View convertView, ViewGroup parent) {
261            if (convertView == null) {
262                convertView = mInflater.inflate(mItemLayoutRes, parent, false);
263            }
264
265            MenuView.ItemView itemView = (MenuView.ItemView) convertView;
266            itemView.initialize(getItem(position), 0);
267            return convertView;
268        }
269
270        void findExpandedIndex() {
271            final MenuItemImpl expandedItem = mMenu.getExpandedItem();
272            if (expandedItem != null) {
273                final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
274                final int count = items.size();
275                for (int i = 0; i < count; i++) {
276                    final MenuItemImpl item = items.get(i);
277                    if (item == expandedItem) {
278                        mExpandedIndex = i;
279                        return;
280                    }
281                }
282            }
283            mExpandedIndex = -1;
284        }
285
286        @Override
287        public void notifyDataSetChanged() {
288            findExpandedIndex();
289            super.notifyDataSetChanged();
290        }
291    }
292}
293