MenuPopupHelper.java revision 11ed1d6cae9214335c92ac38498a4e6c7d1c8324
1/* 2 * Copyright (C) 2010 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 com.android.internal.view.menu; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.os.Parcelable; 22import android.util.DisplayMetrics; 23import android.view.KeyEvent; 24import android.view.LayoutInflater; 25import android.view.View; 26import android.view.View.MeasureSpec; 27import android.view.ViewGroup; 28import android.view.ViewTreeObserver; 29import android.widget.AdapterView; 30import android.widget.BaseAdapter; 31import android.widget.ListAdapter; 32import android.widget.ListPopupWindow; 33import android.widget.PopupWindow; 34 35import java.util.ArrayList; 36 37/** 38 * Presents a menu as a small, simple popup anchored to another view. 39 * @hide 40 */ 41public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener, 42 ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener, 43 View.OnAttachStateChangeListener, MenuPresenter { 44 private static final String TAG = "MenuPopupHelper"; 45 46 static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout; 47 48 private Context mContext; 49 private LayoutInflater mInflater; 50 private ListPopupWindow mPopup; 51 private MenuBuilder mMenu; 52 private int mPopupMaxWidth; 53 private View mAnchorView; 54 private boolean mOverflowOnly; 55 private ViewTreeObserver mTreeObserver; 56 57 private MenuAdapter mAdapter; 58 59 private Callback mPresenterCallback; 60 61 public MenuPopupHelper(Context context, MenuBuilder menu) { 62 this(context, menu, null, false); 63 } 64 65 public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) { 66 this(context, menu, anchorView, false); 67 } 68 69 public MenuPopupHelper(Context context, MenuBuilder menu, 70 View anchorView, boolean overflowOnly) { 71 mContext = context; 72 mInflater = LayoutInflater.from(context); 73 mMenu = menu; 74 mOverflowOnly = overflowOnly; 75 76 final Resources res = context.getResources(); 77 mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2, 78 res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth)); 79 80 mAnchorView = anchorView; 81 82 menu.addMenuPresenter(this); 83 } 84 85 public void setAnchorView(View anchor) { 86 mAnchorView = anchor; 87 } 88 89 public void show() { 90 if (!tryShow()) { 91 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor"); 92 } 93 } 94 95 public boolean tryShow() { 96 mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle); 97 mPopup.setOnDismissListener(this); 98 mPopup.setOnItemClickListener(this); 99 100 mAdapter = new MenuAdapter(mMenu); 101 mPopup.setAdapter(mAdapter); 102 mPopup.setModal(true); 103 104 View anchor = mAnchorView; 105 if (anchor != null) { 106 final boolean addGlobalListener = mTreeObserver == null; 107 mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest 108 if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this); 109 anchor.addOnAttachStateChangeListener(this); 110 mPopup.setAnchorView(anchor); 111 } else { 112 return false; 113 } 114 115 mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth)); 116 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 117 mPopup.show(); 118 mPopup.getListView().setOnKeyListener(this); 119 return true; 120 } 121 122 public void dismiss() { 123 if (isShowing()) { 124 mPopup.dismiss(); 125 } 126 } 127 128 public void onDismiss() { 129 mPopup = null; 130 mMenu.close(); 131 if (mTreeObserver != null) { 132 if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver(); 133 mTreeObserver.removeGlobalOnLayoutListener(this); 134 mTreeObserver = null; 135 } 136 mAnchorView.removeOnAttachStateChangeListener(this); 137 } 138 139 public boolean isShowing() { 140 return mPopup != null && mPopup.isShowing(); 141 } 142 143 @Override 144 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 145 MenuAdapter adapter = mAdapter; 146 adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0); 147 } 148 149 public boolean onKey(View v, int keyCode, KeyEvent event) { 150 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { 151 dismiss(); 152 return true; 153 } 154 return false; 155 } 156 157 private int measureContentWidth(ListAdapter adapter) { 158 // Menus don't tend to be long, so this is more sane than it looks. 159 int width = 0; 160 View itemView = null; 161 int itemType = 0; 162 final int widthMeasureSpec = 163 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 164 final int heightMeasureSpec = 165 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 166 final int count = adapter.getCount(); 167 for (int i = 0; i < count; i++) { 168 final int positionType = adapter.getItemViewType(i); 169 if (positionType != itemType) { 170 itemType = positionType; 171 itemView = null; 172 } 173 itemView = adapter.getView(i, itemView, null); 174 itemView.measure(widthMeasureSpec, heightMeasureSpec); 175 width = Math.max(width, itemView.getMeasuredWidth()); 176 } 177 return width; 178 } 179 180 @Override 181 public void onGlobalLayout() { 182 if (isShowing()) { 183 final View anchor = mAnchorView; 184 if (anchor == null || !anchor.isShown()) { 185 dismiss(); 186 } else if (isShowing()) { 187 // Recompute window size and position 188 mPopup.show(); 189 } 190 } 191 } 192 193 @Override 194 public void onViewAttachedToWindow(View v) { 195 } 196 197 @Override 198 public void onViewDetachedFromWindow(View v) { 199 if (mTreeObserver != null) { 200 if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver(); 201 mTreeObserver.removeGlobalOnLayoutListener(this); 202 } 203 v.removeOnAttachStateChangeListener(this); 204 } 205 206 @Override 207 public void initForMenu(Context context, MenuBuilder menu) { 208 // Don't need to do anything; we added as a presenter in the constructor. 209 } 210 211 @Override 212 public MenuView getMenuView(ViewGroup root) { 213 throw new UnsupportedOperationException("MenuPopupHelpers manage their own views"); 214 } 215 216 @Override 217 public void updateMenuView(boolean cleared) { 218 if (mAdapter != null) mAdapter.notifyDataSetChanged(); 219 } 220 221 @Override 222 public void setCallback(Callback cb) { 223 mPresenterCallback = cb; 224 } 225 226 @Override 227 public boolean onSubMenuSelected(SubMenuBuilder subMenu) { 228 if (subMenu.hasVisibleItems()) { 229 MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false); 230 subPopup.setCallback(mPresenterCallback); 231 if (subPopup.tryShow()) { 232 if (mPresenterCallback != null) { 233 mPresenterCallback.onOpenSubMenu(subMenu); 234 } 235 return true; 236 } 237 } 238 return false; 239 } 240 241 @Override 242 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 243 // Only care about the (sub)menu we're presenting. 244 if (menu != mMenu) return; 245 246 dismiss(); 247 if (mPresenterCallback != null) { 248 mPresenterCallback.onCloseMenu(menu, allMenusAreClosing); 249 } 250 } 251 252 @Override 253 public boolean flagActionItems() { 254 return false; 255 } 256 257 public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { 258 return false; 259 } 260 261 public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { 262 return false; 263 } 264 265 private class MenuAdapter extends BaseAdapter { 266 private MenuBuilder mAdapterMenu; 267 268 public MenuAdapter(MenuBuilder menu) { 269 mAdapterMenu = menu; 270 } 271 272 public int getCount() { 273 ArrayList<MenuItemImpl> items = mOverflowOnly ? 274 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); 275 return items.size(); 276 } 277 278 public MenuItemImpl getItem(int position) { 279 ArrayList<MenuItemImpl> items = mOverflowOnly ? 280 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); 281 return items.get(position); 282 } 283 284 public long getItemId(int position) { 285 // Since a menu item's ID is optional, we'll use the position as an 286 // ID for the item in the AdapterView 287 return position; 288 } 289 290 public View getView(int position, View convertView, ViewGroup parent) { 291 if (convertView == null) { 292 convertView = mInflater.inflate(ITEM_LAYOUT, parent, false); 293 } 294 295 MenuView.ItemView itemView = (MenuView.ItemView) convertView; 296 itemView.initialize(getItem(position), 0); 297 return convertView; 298 } 299 } 300 301 @Override 302 public int getId() { 303 return 0; 304 } 305 306 @Override 307 public Parcelable onSaveInstanceState() { 308 return null; 309 } 310 311 @Override 312 public void onRestoreInstanceState(Parcelable state) { 313 } 314} 315