MenuPopupHelper.java revision 4afd62b18c52a55371ab923d54f93615ad68fd7a
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 View.OnAttachStateChangeListener { 41 private static final String TAG = "MenuPopupHelper"; 42 43 private Context mContext; 44 private ListPopupWindow mPopup; 45 private MenuBuilder mMenu; 46 private int mPopupMaxWidth; 47 private View mAnchorView; 48 private boolean mOverflowOnly; 49 private ViewTreeObserver mTreeObserver; 50 51 private final Handler mHandler = new Handler(); 52 53 public MenuPopupHelper(Context context, MenuBuilder menu) { 54 this(context, menu, null, false); 55 } 56 57 public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) { 58 this(context, menu, anchorView, false); 59 } 60 61 public MenuPopupHelper(Context context, MenuBuilder menu, 62 View anchorView, boolean overflowOnly) { 63 mContext = context; 64 mMenu = menu; 65 mOverflowOnly = overflowOnly; 66 67 final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 68 mPopupMaxWidth = metrics.widthPixels / 2; 69 70 mAnchorView = anchorView; 71 } 72 73 public void setAnchorView(View anchor) { 74 mAnchorView = anchor; 75 } 76 77 public void show() { 78 if (!tryShow()) { 79 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor"); 80 } 81 } 82 83 public boolean tryShow() { 84 mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle); 85 mPopup.setOnItemClickListener(this); 86 mPopup.setOnDismissListener(this); 87 88 final MenuAdapter adapter = mOverflowOnly ? 89 mMenu.getOverflowMenuAdapter(MenuBuilder.TYPE_POPUP) : 90 mMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP); 91 mPopup.setAdapter(adapter); 92 mPopup.setModal(true); 93 94 View anchor = mAnchorView; 95 if (anchor == null && mMenu instanceof SubMenuBuilder) { 96 SubMenuBuilder subMenu = (SubMenuBuilder) mMenu; 97 final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem(); 98 anchor = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null); 99 mAnchorView = anchor; 100 } 101 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(adapter), 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 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 140 if (!isShowing()) return; 141 142 MenuItem item = null; 143 if (mOverflowOnly) { 144 item = mMenu.getOverflowItem(position); 145 } else { 146 item = mMenu.getVisibleItems().get(position); 147 } 148 dismiss(); 149 150 final MenuItem performItem = item; 151 mHandler.post(new Runnable() { 152 public void run() { 153 mMenu.performItemAction(performItem, 0); 154 } 155 }); 156 } 157 158 public boolean onKey(View v, int keyCode, KeyEvent event) { 159 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { 160 dismiss(); 161 return true; 162 } 163 return false; 164 } 165 166 private int measureContentWidth(MenuAdapter adapter) { 167 // Menus don't tend to be long, so this is more sane than it looks. 168 int width = 0; 169 View itemView = null; 170 int itemType = 0; 171 final int widthMeasureSpec = 172 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 173 final int heightMeasureSpec = 174 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 175 final int count = adapter.getCount(); 176 for (int i = 0; i < count; i++) { 177 final int positionType = adapter.getItemViewType(i); 178 if (positionType != itemType) { 179 itemType = positionType; 180 itemView = null; 181 } 182 itemView = adapter.getView(i, itemView, null); 183 itemView.measure(widthMeasureSpec, heightMeasureSpec); 184 width = Math.max(width, itemView.getMeasuredWidth()); 185 } 186 return width; 187 } 188 189 @Override 190 public void onGlobalLayout() { 191 if (isShowing()) { 192 final View anchor = mAnchorView; 193 if (anchor == null || !anchor.isShown()) { 194 dismiss(); 195 } else if (isShowing()) { 196 // Recompute window size and position 197 mPopup.show(); 198 } 199 } 200 } 201 202 @Override 203 public void onViewAttachedToWindow(View v) { 204 } 205 206 @Override 207 public void onViewDetachedFromWindow(View v) { 208 if (mTreeObserver != null) { 209 if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver(); 210 mTreeObserver.removeGlobalOnLayoutListener(this); 211 } 212 v.removeOnAttachStateChangeListener(this); 213 } 214} 215