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