MenuPopupHelper.java revision 38639b1d75933bfb720db79fce26f09478709614
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 if (mTreeObserver != null) { 130 if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver(); 131 mTreeObserver.removeGlobalOnLayoutListener(this); 132 mTreeObserver = null; 133 } 134 mAnchorView.removeOnAttachStateChangeListener(this); 135 } 136 137 public boolean isShowing() { 138 return mPopup != null && mPopup.isShowing(); 139 } 140 141 @Override 142 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 143 MenuAdapter adapter = mAdapter; 144 adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0); 145 } 146 147 public boolean onKey(View v, int keyCode, KeyEvent event) { 148 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { 149 dismiss(); 150 return true; 151 } 152 return false; 153 } 154 155 private int measureContentWidth(ListAdapter adapter) { 156 // Menus don't tend to be long, so this is more sane than it looks. 157 int width = 0; 158 View itemView = null; 159 int itemType = 0; 160 final int widthMeasureSpec = 161 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 162 final int heightMeasureSpec = 163 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 164 final int count = adapter.getCount(); 165 for (int i = 0; i < count; i++) { 166 final int positionType = adapter.getItemViewType(i); 167 if (positionType != itemType) { 168 itemType = positionType; 169 itemView = null; 170 } 171 itemView = adapter.getView(i, itemView, null); 172 itemView.measure(widthMeasureSpec, heightMeasureSpec); 173 width = Math.max(width, itemView.getMeasuredWidth()); 174 } 175 return width; 176 } 177 178 @Override 179 public void onGlobalLayout() { 180 if (isShowing()) { 181 final View anchor = mAnchorView; 182 if (anchor == null || !anchor.isShown()) { 183 dismiss(); 184 } else if (isShowing()) { 185 // Recompute window size and position 186 mPopup.show(); 187 } 188 } 189 } 190 191 @Override 192 public void onViewAttachedToWindow(View v) { 193 } 194 195 @Override 196 public void onViewDetachedFromWindow(View v) { 197 if (mTreeObserver != null) { 198 if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver(); 199 mTreeObserver.removeGlobalOnLayoutListener(this); 200 } 201 v.removeOnAttachStateChangeListener(this); 202 } 203 204 @Override 205 public void initForMenu(Context context, MenuBuilder menu) { 206 // Don't need to do anything; we added as a presenter in the constructor. 207 } 208 209 @Override 210 public MenuView getMenuView(ViewGroup root) { 211 throw new UnsupportedOperationException("MenuPopupHelpers manage their own views"); 212 } 213 214 @Override 215 public void updateMenuView(boolean cleared) { 216 if (mAdapter != null) mAdapter.notifyDataSetChanged(); 217 } 218 219 @Override 220 public void setCallback(Callback cb) { 221 mPresenterCallback = cb; 222 } 223 224 @Override 225 public boolean onSubMenuSelected(SubMenuBuilder subMenu) { 226 if (subMenu.hasVisibleItems()) { 227 MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false); 228 subPopup.setCallback(mPresenterCallback); 229 if (subPopup.tryShow()) { 230 if (mPresenterCallback != null) { 231 mPresenterCallback.onOpenSubMenu(subMenu); 232 } 233 return true; 234 } 235 } 236 return false; 237 } 238 239 @Override 240 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 241 // Only care about the (sub)menu we're presenting. 242 if (menu != mMenu) return; 243 244 dismiss(); 245 if (mPresenterCallback != null) { 246 mPresenterCallback.onCloseMenu(menu, allMenusAreClosing); 247 } 248 } 249 250 @Override 251 public boolean flagActionItems() { 252 return false; 253 } 254 255 public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { 256 return false; 257 } 258 259 public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { 260 return false; 261 } 262 263 private class MenuAdapter extends BaseAdapter { 264 private MenuBuilder mAdapterMenu; 265 266 public MenuAdapter(MenuBuilder menu) { 267 mAdapterMenu = menu; 268 } 269 270 public int getCount() { 271 ArrayList<MenuItemImpl> items = mOverflowOnly ? 272 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); 273 return items.size(); 274 } 275 276 public MenuItemImpl getItem(int position) { 277 ArrayList<MenuItemImpl> items = mOverflowOnly ? 278 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); 279 return items.get(position); 280 } 281 282 public long getItemId(int position) { 283 // Since a menu item's ID is optional, we'll use the position as an 284 // ID for the item in the AdapterView 285 return position; 286 } 287 288 public View getView(int position, View convertView, ViewGroup parent) { 289 if (convertView == null) { 290 convertView = mInflater.inflate(ITEM_LAYOUT, parent, false); 291 } 292 293 MenuView.ItemView itemView = (MenuView.ItemView) convertView; 294 itemView.initialize(getItem(position), 0); 295 return convertView; 296 } 297 } 298} 299