ActionMenuView.java revision b3312b88c1f10cd29f9b55b03d5b41fc429ebfb5
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 */ 16package com.android.internal.view.menu; 17 18import android.content.Context; 19import android.content.res.Configuration; 20import android.content.res.Resources; 21import android.content.res.TypedArray; 22import android.graphics.drawable.Drawable; 23import android.util.AttributeSet; 24import android.view.Gravity; 25import android.view.SoundEffectConstants; 26import android.view.View; 27import android.view.ViewGroup; 28import android.view.ViewParent; 29import android.widget.ImageButton; 30import android.widget.ImageView; 31import android.widget.LinearLayout; 32 33import java.util.ArrayList; 34 35/** 36 * @hide 37 */ 38public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView { 39 private static final String TAG = "ActionMenuView"; 40 41 // TODO Theme/style this. 42 private static final int DIVIDER_PADDING = 12; // dips 43 44 private MenuBuilder mMenu; 45 46 private int mMaxItems; 47 private int mWidthLimit; 48 private boolean mReserveOverflow; 49 private OverflowMenuButton mOverflowButton; 50 private MenuPopupHelper mOverflowPopup; 51 52 private float mDividerPadding; 53 54 private Drawable mDivider; 55 56 private Runnable mShowOverflow = new Runnable() { 57 public void run() { 58 showOverflowMenu(); 59 } 60 }; 61 62 private class OpenOverflowRunnable implements Runnable { 63 private MenuPopupHelper mPopup; 64 65 public OpenOverflowRunnable(MenuPopupHelper popup) { 66 mPopup = popup; 67 } 68 69 public void run() { 70 if (mPopup.tryShow()) { 71 mOverflowPopup = mPopup; 72 mPostedOpenRunnable = null; 73 } 74 } 75 } 76 77 private OpenOverflowRunnable mPostedOpenRunnable; 78 79 public ActionMenuView(Context context) { 80 this(context, null); 81 } 82 83 public ActionMenuView(Context context, AttributeSet attrs) { 84 super(context, attrs); 85 86 final Resources res = getResources(); 87 88 // Measure for initial configuration 89 mMaxItems = getMaxActionButtons(); 90 91 // TODO There has to be a better way to indicate that we don't have a hard menu key. 92 final int screen = res.getConfiguration().screenLayout; 93 mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) == 94 Configuration.SCREENLAYOUT_SIZE_XLARGE; 95 mWidthLimit = res.getDisplayMetrics().widthPixels / 2; 96 97 TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme); 98 mDivider = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical); 99 a.recycle(); 100 101 mDividerPadding = DIVIDER_PADDING * res.getDisplayMetrics().density; 102 103 setBaselineAligned(false); 104 } 105 106 @Override 107 public void onConfigurationChanged(Configuration newConfig) { 108 super.onConfigurationChanged(newConfig); 109 final int screen = newConfig.screenLayout; 110 mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) == 111 Configuration.SCREENLAYOUT_SIZE_XLARGE; 112 mMaxItems = getMaxActionButtons(); 113 mWidthLimit = getResources().getDisplayMetrics().widthPixels / 2; 114 if (mMenu != null) { 115 mMenu.setMaxActionItems(mMaxItems); 116 updateChildren(false); 117 } 118 119 if (mOverflowPopup != null && mOverflowPopup.isShowing()) { 120 mOverflowPopup.dismiss(); 121 post(mShowOverflow); 122 } 123 } 124 125 @Override 126 public void onDetachedFromWindow() { 127 super.onDetachedFromWindow(); 128 if (mOverflowPopup != null && mOverflowPopup.isShowing()) { 129 mOverflowPopup.dismiss(); 130 } 131 } 132 133 private int getMaxActionButtons() { 134 return getResources().getInteger(com.android.internal.R.integer.max_action_buttons); 135 } 136 137 public boolean isOverflowReserved() { 138 return mReserveOverflow; 139 } 140 141 public void setOverflowReserved(boolean reserveOverflow) { 142 mReserveOverflow = reserveOverflow; 143 } 144 145 public View getOverflowButton() { 146 return mOverflowButton; 147 } 148 149 @Override 150 protected LayoutParams generateDefaultLayoutParams() { 151 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, 152 LayoutParams.WRAP_CONTENT); 153 params.gravity = Gravity.CENTER_VERTICAL; 154 return params; 155 } 156 157 @Override 158 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 159 if (p instanceof LayoutParams) { 160 LayoutParams result = new LayoutParams((LayoutParams) p); 161 if (result.gravity <= Gravity.NO_GRAVITY) { 162 result.gravity = Gravity.CENTER_VERTICAL; 163 } 164 return result; 165 } 166 return generateDefaultLayoutParams(); 167 } 168 169 public boolean invokeItem(MenuItemImpl item) { 170 return mMenu.performItemAction(item, 0); 171 } 172 173 public int getWindowAnimations() { 174 return 0; 175 } 176 177 public void initialize(MenuBuilder menu, int menuType) { 178 int width = mWidthLimit; 179 if (mReserveOverflow) { 180 if (mOverflowButton == null) { 181 OverflowMenuButton button = new OverflowMenuButton(mContext); 182 mOverflowButton = button; 183 } 184 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 185 mOverflowButton.measure(spec, spec); 186 width -= mOverflowButton.getMeasuredWidth(); 187 } 188 189 menu.setActionWidthLimit(width); 190 191 menu.setMaxActionItems(mMaxItems); 192 mMenu = menu; 193 updateChildren(true); 194 } 195 196 public void updateChildren(boolean cleared) { 197 final boolean reserveOverflow = mReserveOverflow; 198 removeAllViews(); 199 200 final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(reserveOverflow); 201 final int itemCount = itemsToShow.size(); 202 203 boolean needsDivider = false; 204 for (int i = 0; i < itemCount; i++) { 205 final MenuItemImpl itemData = itemsToShow.get(i); 206 boolean hasDivider = false; 207 208 if (needsDivider) { 209 addView(makeDividerView(), makeDividerLayoutParams()); 210 hasDivider = true; 211 } 212 213 View actionView = itemData.getActionView(); 214 215 if (actionView != null) { 216 final ViewParent parent = actionView.getParent(); 217 if (parent instanceof ViewGroup) { 218 ((ViewGroup) parent).removeView(actionView); 219 } 220 addView(actionView, makeActionViewLayoutParams(actionView)); 221 } else { 222 ActionMenuItemView view = (ActionMenuItemView) itemData.getItemView( 223 MenuBuilder.TYPE_ACTION_BUTTON, this); 224 view.setItemInvoker(this); 225 if (i > 0 && !hasDivider && view.hasText() && itemData.getIcon() == null) { 226 addView(makeDividerView(), makeDividerLayoutParams()); 227 } 228 addView(view); 229 needsDivider = view.hasText(); 230 } 231 } 232 233 if (reserveOverflow) { 234 if (mMenu.getNonActionItems(true).size() > 0) { 235 if (itemCount > 0) { 236 addView(makeDividerView(), makeDividerLayoutParams()); 237 } 238 if (mOverflowButton == null) { 239 OverflowMenuButton button = new OverflowMenuButton(mContext); 240 mOverflowButton = button; 241 } 242 addView(mOverflowButton); 243 } else { 244 mOverflowButton = null; 245 } 246 } 247 } 248 249 public boolean showOverflowMenu() { 250 if (mOverflowButton != null && !isOverflowMenuShowing()) { 251 mMenu.getCallback().onMenuModeChange(mMenu); 252 return true; 253 } 254 return false; 255 } 256 257 public void openOverflowMenu() { 258 OverflowPopup popup = new OverflowPopup(getContext(), mMenu, mOverflowButton, true); 259 mPostedOpenRunnable = new OpenOverflowRunnable(popup); 260 // Post this for later; we might still need a layout for the anchor to be right. 261 post(mPostedOpenRunnable); 262 } 263 264 public boolean isOverflowMenuShowing() { 265 return mOverflowPopup != null && mOverflowPopup.isShowing(); 266 } 267 268 public boolean isOverflowMenuOpen() { 269 return mOverflowPopup != null; 270 } 271 272 public boolean hideOverflowMenu() { 273 if (mPostedOpenRunnable != null) { 274 removeCallbacks(mPostedOpenRunnable); 275 return true; 276 } 277 278 MenuPopupHelper popup = mOverflowPopup; 279 if (popup != null) { 280 popup.dismiss(); 281 return true; 282 } 283 return false; 284 } 285 286 private boolean addItemView(boolean needsDivider, ActionMenuItemView view) { 287 view.setItemInvoker(this); 288 boolean hasText = view.hasText(); 289 290 if (hasText && needsDivider) { 291 addView(makeDividerView(), makeDividerLayoutParams()); 292 } 293 addView(view); 294 return hasText; 295 } 296 297 private ImageView makeDividerView() { 298 ImageView result = new ImageView(mContext); 299 result.setImageDrawable(mDivider); 300 result.setScaleType(ImageView.ScaleType.FIT_XY); 301 return result; 302 } 303 304 private LayoutParams makeDividerLayoutParams() { 305 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, 306 LayoutParams.MATCH_PARENT); 307 params.topMargin = (int) mDividerPadding; 308 params.bottomMargin = (int) mDividerPadding; 309 return params; 310 } 311 312 private LayoutParams makeActionViewLayoutParams(View view) { 313 return generateLayoutParams(view.getLayoutParams()); 314 } 315 316 private class OverflowMenuButton extends ImageButton { 317 public OverflowMenuButton(Context context) { 318 super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle); 319 320 setClickable(true); 321 setFocusable(true); 322 setVisibility(VISIBLE); 323 setEnabled(true); 324 } 325 326 @Override 327 public boolean performClick() { 328 if (super.performClick()) { 329 return true; 330 } 331 332 playSoundEffect(SoundEffectConstants.CLICK); 333 showOverflowMenu(); 334 return true; 335 } 336 } 337 338 private class OverflowPopup extends MenuPopupHelper { 339 public OverflowPopup(Context context, MenuBuilder menu, View anchorView, 340 boolean overflowOnly) { 341 super(context, menu, anchorView, overflowOnly); 342 } 343 344 @Override 345 public void onDismiss() { 346 super.onDismiss(); 347 mMenu.getCallback().onCloseMenu(mMenu, true); 348 mOverflowPopup = null; 349 } 350 } 351} 352