MenuPopupHelper.java revision 50f784cf2dc2dea8061153ac3a843f60a9d88781
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 com.android.internal.view.menu.MenuBuilder.MenuAdapter; 20 21import android.content.Context; 22import android.util.DisplayMetrics; 23import android.view.KeyEvent; 24import android.view.MenuItem; 25import android.view.View; 26import android.view.View.MeasureSpec; 27import android.view.ViewTreeObserver; 28import android.widget.AdapterView; 29import android.widget.ListPopupWindow; 30import android.widget.PopupWindow; 31 32import java.lang.ref.WeakReference; 33 34/** 35 * @hide 36 */ 37public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener, 38 ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener { 39 private static final String TAG = "MenuPopupHelper"; 40 41 private Context mContext; 42 private ListPopupWindow mPopup; 43 private MenuBuilder mMenu; 44 private int mPopupMaxWidth; 45 private WeakReference<View> mAnchorView; 46 private boolean mOverflowOnly; 47 private ViewTreeObserver mTreeObserver; 48 49 public MenuPopupHelper(Context context, MenuBuilder menu) { 50 this(context, menu, null, false); 51 } 52 53 public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) { 54 this(context, menu, anchorView, false); 55 } 56 57 public MenuPopupHelper(Context context, MenuBuilder menu, 58 View anchorView, boolean overflowOnly) { 59 mContext = context; 60 mMenu = menu; 61 mOverflowOnly = overflowOnly; 62 63 final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 64 mPopupMaxWidth = metrics.widthPixels / 2; 65 66 if (anchorView != null) { 67 mAnchorView = new WeakReference<View>(anchorView); 68 } 69 } 70 71 public void show() { 72 mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle); 73 mPopup.setOnItemClickListener(this); 74 mPopup.setOnDismissListener(this); 75 76 final MenuAdapter adapter = mOverflowOnly ? 77 mMenu.getOverflowMenuAdapter(MenuBuilder.TYPE_POPUP) : 78 mMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP); 79 mPopup.setAdapter(adapter); 80 mPopup.setModal(true); 81 82 View anchor = mAnchorView != null ? mAnchorView.get() : null; 83 if (anchor == null && mMenu instanceof SubMenuBuilder) { 84 SubMenuBuilder subMenu = (SubMenuBuilder) mMenu; 85 final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem(); 86 anchor = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null); 87 mAnchorView = new WeakReference<View>(anchor); 88 } 89 90 if (anchor != null) { 91 mTreeObserver = anchor.getViewTreeObserver(); 92 mTreeObserver.addOnGlobalLayoutListener(this); 93 mPopup.setAnchorView(anchor); 94 } else { 95 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor"); 96 } 97 98 mPopup.setContentWidth(Math.min(measureContentWidth(adapter), mPopupMaxWidth)); 99 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 100 mPopup.show(); 101 mPopup.getListView().setOnKeyListener(this); 102 } 103 104 public void dismiss() { 105 if (isShowing()) { 106 mPopup.dismiss(); 107 } 108 } 109 110 public void onDismiss() { 111 mPopup = null; 112 if (mTreeObserver != null) { 113 mTreeObserver.removeGlobalOnLayoutListener(MenuPopupHelper.this); 114 mTreeObserver = null; 115 } 116 } 117 118 public boolean isShowing() { 119 return mPopup != null && mPopup.isShowing(); 120 } 121 122 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 123 MenuItem item = null; 124 if (mOverflowOnly) { 125 item = mMenu.getOverflowItem(position); 126 } else { 127 item = mMenu.getItem(position); 128 } 129 mMenu.performItemAction(item, 0); 130 dismiss(); 131 } 132 133 public boolean onKey(View v, int keyCode, KeyEvent event) { 134 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { 135 dismiss(); 136 return true; 137 } 138 return false; 139 } 140 141 private int measureContentWidth(MenuAdapter adapter) { 142 // Menus don't tend to be long, so this is more sane than it looks. 143 int width = 0; 144 View itemView = null; 145 int itemType = 0; 146 final int widthMeasureSpec = 147 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 148 final int heightMeasureSpec = 149 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 150 final int count = adapter.getCount(); 151 for (int i = 0; i < count; i++) { 152 final int positionType = adapter.getItemViewType(i); 153 if (positionType != itemType) { 154 itemType = positionType; 155 itemView = null; 156 } 157 itemView = adapter.getView(i, itemView, null); 158 itemView.measure(widthMeasureSpec, heightMeasureSpec); 159 width = Math.max(width, itemView.getMeasuredWidth()); 160 } 161 return width; 162 } 163 164 @Override 165 public void onGlobalLayout() { 166 if (!isShowing()) { 167 mTreeObserver.removeGlobalOnLayoutListener(this); 168 mTreeObserver = null; 169 } else { 170 final View anchor = mAnchorView != null ? mAnchorView.get() : null; 171 if (anchor != null && !anchor.isShown()) { 172 dismiss(); 173 } else { 174 // Recompute window size and position 175 mPopup.show(); 176 } 177 } 178 } 179} 180