| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.internal.view.menu; |
| |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.graphics.drawable.Drawable; |
| import android.util.AttributeSet; |
| import android.view.Gravity; |
| import android.view.SoundEffectConstants; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ImageButton; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * @hide |
| */ |
| public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView { |
| private static final String TAG = "ActionMenuView"; |
| |
| // TODO Theme/style this. |
| private static final int DIVIDER_PADDING = 12; // dips |
| |
| private MenuBuilder mMenu; |
| |
| private int mMaxItems; |
| private int mWidthLimit; |
| private boolean mReserveOverflow; |
| private OverflowMenuButton mOverflowButton; |
| private MenuPopupHelper mOverflowPopup; |
| |
| private float mDividerPadding; |
| |
| private Drawable mDivider; |
| |
| private final Runnable mShowOverflow = new Runnable() { |
| public void run() { |
| showOverflowMenu(); |
| } |
| }; |
| |
| private class OpenOverflowRunnable implements Runnable { |
| private MenuPopupHelper mPopup; |
| |
| public OpenOverflowRunnable(MenuPopupHelper popup) { |
| mPopup = popup; |
| } |
| |
| public void run() { |
| if (mPopup.tryShow()) { |
| mOverflowPopup = mPopup; |
| mPostedOpenRunnable = null; |
| } |
| } |
| } |
| |
| private OpenOverflowRunnable mPostedOpenRunnable; |
| |
| public ActionMenuView(Context context) { |
| this(context, null); |
| } |
| |
| public ActionMenuView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| |
| final Resources res = getResources(); |
| |
| // Measure for initial configuration |
| mMaxItems = getMaxActionButtons(); |
| |
| // TODO There has to be a better way to indicate that we don't have a hard menu key. |
| final int screen = res.getConfiguration().screenLayout; |
| mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) == |
| Configuration.SCREENLAYOUT_SIZE_XLARGE; |
| mWidthLimit = res.getDisplayMetrics().widthPixels / 2; |
| |
| TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme); |
| mDivider = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical); |
| a.recycle(); |
| |
| mDividerPadding = DIVIDER_PADDING * res.getDisplayMetrics().density; |
| |
| setBaselineAligned(false); |
| } |
| |
| @Override |
| public void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| final int screen = newConfig.screenLayout; |
| mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) == |
| Configuration.SCREENLAYOUT_SIZE_XLARGE; |
| mMaxItems = getMaxActionButtons(); |
| mWidthLimit = getResources().getDisplayMetrics().widthPixels / 2; |
| if (mMenu != null) { |
| mMenu.setMaxActionItems(mMaxItems); |
| updateChildren(false); |
| } |
| |
| if (mOverflowPopup != null && mOverflowPopup.isShowing()) { |
| mOverflowPopup.dismiss(); |
| post(mShowOverflow); |
| } |
| } |
| |
| @Override |
| public void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| if (mOverflowPopup != null && mOverflowPopup.isShowing()) { |
| mOverflowPopup.dismiss(); |
| } |
| removeCallbacks(mShowOverflow); |
| if (mPostedOpenRunnable != null) { |
| removeCallbacks(mPostedOpenRunnable); |
| } |
| } |
| |
| private int getMaxActionButtons() { |
| return getResources().getInteger(com.android.internal.R.integer.max_action_buttons); |
| } |
| |
| public boolean isOverflowReserved() { |
| return mReserveOverflow; |
| } |
| |
| public void setOverflowReserved(boolean reserveOverflow) { |
| mReserveOverflow = reserveOverflow; |
| } |
| |
| public View getOverflowButton() { |
| return mOverflowButton; |
| } |
| |
| @Override |
| protected LayoutParams generateDefaultLayoutParams() { |
| LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, |
| LayoutParams.WRAP_CONTENT); |
| params.gravity = Gravity.CENTER_VERTICAL; |
| return params; |
| } |
| |
| @Override |
| protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { |
| if (p instanceof LayoutParams) { |
| LayoutParams result = new LayoutParams((LayoutParams) p); |
| if (result.gravity <= Gravity.NO_GRAVITY) { |
| result.gravity = Gravity.CENTER_VERTICAL; |
| } |
| return result; |
| } |
| return generateDefaultLayoutParams(); |
| } |
| |
| public boolean invokeItem(MenuItemImpl item) { |
| return mMenu.performItemAction(item, 0); |
| } |
| |
| public int getWindowAnimations() { |
| return 0; |
| } |
| |
| public void initialize(MenuBuilder menu, int menuType) { |
| int width = mWidthLimit; |
| if (mReserveOverflow) { |
| if (mOverflowButton == null) { |
| OverflowMenuButton button = new OverflowMenuButton(mContext); |
| mOverflowButton = button; |
| } |
| final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); |
| mOverflowButton.measure(spec, spec); |
| width -= mOverflowButton.getMeasuredWidth(); |
| } |
| |
| menu.setActionWidthLimit(width); |
| |
| menu.setMaxActionItems(mMaxItems); |
| final boolean cleared = mMenu != menu; |
| mMenu = menu; |
| updateChildren(cleared); |
| } |
| |
| public void updateChildren(boolean cleared) { |
| final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(mReserveOverflow); |
| final int itemCount = itemsToShow.size(); |
| |
| boolean needsDivider = false; |
| int childIndex = 0; |
| for (int i = 0; i < itemCount; i++) { |
| final MenuItemImpl itemData = itemsToShow.get(i); |
| boolean hasDivider = false; |
| |
| if (needsDivider) { |
| if (!isDivider(getChildAt(childIndex))) { |
| addView(makeDividerView(), childIndex, makeDividerLayoutParams()); |
| } |
| hasDivider = true; |
| childIndex++; |
| } |
| |
| View childToAdd = itemData.getActionView(); |
| boolean needsPreDivider = false; |
| if (childToAdd != null) { |
| childToAdd.setLayoutParams(makeActionViewLayoutParams(childToAdd)); |
| } else { |
| ActionMenuItemView view = (ActionMenuItemView) itemData.getItemView( |
| MenuBuilder.TYPE_ACTION_BUTTON, this); |
| view.setItemInvoker(this); |
| needsPreDivider = i > 0 && !hasDivider && view.hasText() && |
| itemData.getIcon() == null; |
| needsDivider = view.hasText(); |
| childToAdd = view; |
| } |
| |
| boolean addPreDivider = removeChildrenUntil(childIndex, childToAdd, needsPreDivider); |
| |
| if (addPreDivider) addView(makeDividerView(), childIndex, makeDividerLayoutParams()); |
| if (needsPreDivider) childIndex++; |
| |
| if (getChildAt(childIndex) != childToAdd) { |
| addView(childToAdd, childIndex); |
| } |
| childIndex++; |
| } |
| |
| final boolean hasOverflow = mOverflowButton != null && mOverflowButton.getParent() == this; |
| final boolean needsOverflow = mReserveOverflow && mMenu.getNonActionItems(true).size() > 0; |
| |
| if (hasOverflow != needsOverflow) { |
| if (needsOverflow) { |
| if (mOverflowButton == null) { |
| OverflowMenuButton button = new OverflowMenuButton(mContext); |
| mOverflowButton = button; |
| } |
| boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton, true); |
| if (addDivider && itemCount > 0) { |
| addView(makeDividerView(), childIndex, makeDividerLayoutParams()); |
| childIndex++; |
| } |
| addView(mOverflowButton, childIndex); |
| childIndex++; |
| } else { |
| removeView(mOverflowButton); |
| } |
| } else { |
| if (needsOverflow) { |
| boolean overflowDivider = itemCount > 0; |
| boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton, |
| overflowDivider); |
| if (addDivider && itemCount > 0) { |
| addView(makeDividerView(), childIndex, makeDividerLayoutParams()); |
| } |
| if (overflowDivider) { |
| childIndex += 2; |
| } else { |
| childIndex++; |
| } |
| } |
| } |
| |
| while (getChildCount() > childIndex) { |
| removeViewAt(childIndex); |
| } |
| } |
| |
| private boolean removeChildrenUntil(int start, View targetChild, boolean needsPreDivider) { |
| final int childCount = getChildCount(); |
| boolean found = false; |
| for (int i = start; i < childCount; i++) { |
| final View child = getChildAt(i); |
| if (child == targetChild) { |
| found = true; |
| break; |
| } |
| } |
| |
| if (!found) { |
| return needsPreDivider; |
| } |
| |
| for (int i = start; i < getChildCount(); ) { |
| final View child = getChildAt(i); |
| if (needsPreDivider && isDivider(child)) { |
| needsPreDivider = false; |
| i++; |
| continue; |
| } |
| if (child == targetChild) break; |
| removeViewAt(i); |
| } |
| |
| return needsPreDivider; |
| } |
| |
| private static boolean isDivider(View v) { |
| return v != null && v.getId() == com.android.internal.R.id.action_menu_divider; |
| } |
| |
| public boolean showOverflowMenu() { |
| if (mOverflowButton != null && !isOverflowMenuShowing()) { |
| mMenu.getCallback().onMenuModeChange(mMenu); |
| return true; |
| } |
| return false; |
| } |
| |
| public void openOverflowMenu() { |
| OverflowPopup popup = new OverflowPopup(getContext(), mMenu, mOverflowButton, true); |
| mPostedOpenRunnable = new OpenOverflowRunnable(popup); |
| // Post this for later; we might still need a layout for the anchor to be right. |
| post(mPostedOpenRunnable); |
| } |
| |
| public boolean isOverflowMenuShowing() { |
| return mOverflowPopup != null && mOverflowPopup.isShowing(); |
| } |
| |
| public boolean isOverflowMenuOpen() { |
| return mOverflowPopup != null; |
| } |
| |
| public boolean hideOverflowMenu() { |
| if (mPostedOpenRunnable != null) { |
| removeCallbacks(mPostedOpenRunnable); |
| return true; |
| } |
| |
| MenuPopupHelper popup = mOverflowPopup; |
| if (popup != null) { |
| popup.dismiss(); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean addItemView(boolean needsDivider, ActionMenuItemView view) { |
| view.setItemInvoker(this); |
| boolean hasText = view.hasText(); |
| |
| if (hasText && needsDivider) { |
| addView(makeDividerView(), makeDividerLayoutParams()); |
| } |
| addView(view); |
| return hasText; |
| } |
| |
| private ImageView makeDividerView() { |
| ImageView result = new ImageView(mContext); |
| result.setImageDrawable(mDivider); |
| result.setScaleType(ImageView.ScaleType.FIT_XY); |
| result.setId(com.android.internal.R.id.action_menu_divider); |
| return result; |
| } |
| |
| private LayoutParams makeDividerLayoutParams() { |
| LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, |
| LayoutParams.MATCH_PARENT); |
| params.topMargin = (int) mDividerPadding; |
| params.bottomMargin = (int) mDividerPadding; |
| return params; |
| } |
| |
| private LayoutParams makeActionViewLayoutParams(View view) { |
| return generateLayoutParams(view.getLayoutParams()); |
| } |
| |
| private class OverflowMenuButton extends ImageButton { |
| public OverflowMenuButton(Context context) { |
| super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle); |
| |
| setClickable(true); |
| setFocusable(true); |
| setVisibility(VISIBLE); |
| setEnabled(true); |
| } |
| |
| @Override |
| public boolean performClick() { |
| if (super.performClick()) { |
| return true; |
| } |
| |
| playSoundEffect(SoundEffectConstants.CLICK); |
| showOverflowMenu(); |
| return true; |
| } |
| } |
| |
| private class OverflowPopup extends MenuPopupHelper { |
| public OverflowPopup(Context context, MenuBuilder menu, View anchorView, |
| boolean overflowOnly) { |
| super(context, menu, anchorView, overflowOnly); |
| } |
| |
| @Override |
| public void onDismiss() { |
| super.onDismiss(); |
| mMenu.getCallback().onCloseMenu(mMenu, true); |
| mOverflowPopup = null; |
| } |
| } |
| } |