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