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