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