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