MenuPopupHelper.java revision e2b03a62e5014ea60e24a989544fa549b493a520
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 mTreeObserver = anchor.getViewTreeObserver(); 105 mTreeObserver.addOnGlobalLayoutListener(this); 106 mPopup.setAnchorView(anchor); 107 } else { 108 return false; 109 } 110 111 mPopup.setContentWidth(Math.min(measureContentWidth(adapter), mPopupMaxWidth)); 112 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 113 mPopup.show(); 114 mPopup.getListView().setOnKeyListener(this); 115 return true; 116 } 117 118 public void dismiss() { 119 if (isShowing()) { 120 mPopup.dismiss(); 121 } 122 } 123 124 public void onDismiss() { 125 mPopup = null; 126 if (mTreeObserver != null) { 127 mTreeObserver.removeGlobalOnLayoutListener(MenuPopupHelper.this); 128 mTreeObserver = null; 129 } 130 } 131 132 public boolean isShowing() { 133 return mPopup != null && mPopup.isShowing(); 134 } 135 136 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 137 MenuItem item = null; 138 if (mOverflowOnly) { 139 item = mMenu.getOverflowItem(position); 140 } else { 141 item = mMenu.getVisibleItems().get(position); 142 } 143 dismiss(); 144 145 final MenuItem performItem = item; 146 mHandler.post(new Runnable() { 147 public void run() { 148 mMenu.performItemAction(performItem, 0); 149 } 150 }); 151 } 152 153 public boolean onKey(View v, int keyCode, KeyEvent event) { 154 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { 155 dismiss(); 156 return true; 157 } 158 return false; 159 } 160 161 private int measureContentWidth(MenuAdapter adapter) { 162 // Menus don't tend to be long, so this is more sane than it looks. 163 int width = 0; 164 View itemView = null; 165 int itemType = 0; 166 final int widthMeasureSpec = 167 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 168 final int heightMeasureSpec = 169 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 170 final int count = adapter.getCount(); 171 for (int i = 0; i < count; i++) { 172 final int positionType = adapter.getItemViewType(i); 173 if (positionType != itemType) { 174 itemType = positionType; 175 itemView = null; 176 } 177 itemView = adapter.getView(i, itemView, null); 178 itemView.measure(widthMeasureSpec, heightMeasureSpec); 179 width = Math.max(width, itemView.getMeasuredWidth()); 180 } 181 return width; 182 } 183 184 @Override 185 public void onGlobalLayout() { 186 if (!isShowing()) { 187 mTreeObserver.removeGlobalOnLayoutListener(this); 188 mTreeObserver = null; 189 } else { 190 final View anchor = mAnchorView != null ? mAnchorView.get() : null; 191 if (anchor != null && !anchor.isShown()) { 192 dismiss(); 193 } else { 194 // Recompute window size and position 195 mPopup.show(); 196 } 197 } 198 } 199} 200