ActionMenuItemView.java revision e43340c80dc66c45edc793ecd0343774aa34d108
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 android.content.Context;
20import android.content.res.Configuration;
21import android.content.res.Resources;
22import android.content.res.TypedArray;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.text.TextUtils;
26import android.util.AttributeSet;
27import android.view.Gravity;
28import android.view.MotionEvent;
29import android.view.View;
30import android.view.accessibility.AccessibilityEvent;
31import android.widget.ActionMenuView;
32import android.widget.ListPopupWindow;
33import android.widget.TextView;
34import android.widget.Toast;
35import android.widget.ListPopupWindow.ForwardingListener;
36
37/**
38 * @hide
39 */
40public class ActionMenuItemView extends TextView
41        implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
42        ActionMenuView.ActionMenuChildView {
43    private static final String TAG = "ActionMenuItemView";
44
45    private MenuItemImpl mItemData;
46    private CharSequence mTitle;
47    private Drawable mIcon;
48    private MenuBuilder.ItemInvoker mItemInvoker;
49    private ForwardingListener mForwardingListener;
50    private PopupCallback mPopupCallback;
51
52    private boolean mAllowTextWithIcon;
53    private boolean mExpandedFormat;
54    private int mMinWidth;
55    private int mSavedPaddingLeft;
56
57    private static final int MAX_ICON_SIZE = 32; // dp
58    private int mMaxIconSize;
59
60    public ActionMenuItemView(Context context) {
61        this(context, null);
62    }
63
64    public ActionMenuItemView(Context context, AttributeSet attrs) {
65        this(context, attrs, 0);
66    }
67
68    public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
69        this(context, attrs, defStyleAttr, 0);
70    }
71
72    public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
73        super(context, attrs, defStyleAttr, defStyleRes);
74        final Resources res = context.getResources();
75        mAllowTextWithIcon = res.getBoolean(
76                com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
77        final TypedArray a = context.obtainStyledAttributes(attrs,
78                com.android.internal.R.styleable.ActionMenuItemView, defStyleAttr, defStyleRes);
79        mMinWidth = a.getDimensionPixelSize(
80                com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0);
81        a.recycle();
82
83        final float density = res.getDisplayMetrics().density;
84        mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
85
86        setOnClickListener(this);
87        setOnLongClickListener(this);
88
89        mSavedPaddingLeft = -1;
90    }
91
92    @Override
93    public void onConfigurationChanged(Configuration newConfig) {
94        super.onConfigurationChanged(newConfig);
95
96        mAllowTextWithIcon = getContext().getResources().getBoolean(
97                com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
98        updateTextButtonVisibility();
99    }
100
101    @Override
102    public void setPadding(int l, int t, int r, int b) {
103        mSavedPaddingLeft = l;
104        super.setPadding(l, t, r, b);
105    }
106
107    public MenuItemImpl getItemData() {
108        return mItemData;
109    }
110
111    @Override
112    public void initialize(MenuItemImpl itemData, int menuType) {
113        mItemData = itemData;
114
115        setIcon(itemData.getIcon());
116        setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon
117        setId(itemData.getItemId());
118
119        setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
120        setEnabled(itemData.isEnabled());
121
122        if (itemData.hasSubMenu()) {
123            if (mForwardingListener == null) {
124                mForwardingListener = new ActionMenuItemForwardingListener();
125            }
126        }
127    }
128
129    @Override
130    public boolean onTouchEvent(MotionEvent e) {
131        if (mItemData.hasSubMenu() && mForwardingListener != null
132                && mForwardingListener.onTouch(this, e)) {
133            return true;
134        }
135        return super.onTouchEvent(e);
136    }
137
138    @Override
139    public void onClick(View v) {
140        if (mItemInvoker != null) {
141            mItemInvoker.invokeItem(mItemData);
142        }
143    }
144
145    public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
146        mItemInvoker = invoker;
147    }
148
149    public void setPopupCallback(PopupCallback popupCallback) {
150        mPopupCallback = popupCallback;
151    }
152
153    public boolean prefersCondensedTitle() {
154        return true;
155    }
156
157    public void setCheckable(boolean checkable) {
158        // TODO Support checkable action items
159    }
160
161    public void setChecked(boolean checked) {
162        // TODO Support checkable action items
163    }
164
165    public void setExpandedFormat(boolean expandedFormat) {
166        if (mExpandedFormat != expandedFormat) {
167            mExpandedFormat = expandedFormat;
168            if (mItemData != null) {
169                mItemData.actionFormatChanged();
170            }
171        }
172    }
173
174    private void updateTextButtonVisibility() {
175        boolean visible = !TextUtils.isEmpty(mTitle);
176        visible &= mIcon == null ||
177                (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
178
179        setText(visible ? mTitle : null);
180    }
181
182    public void setIcon(Drawable icon) {
183        mIcon = icon;
184        if (icon != null) {
185            int width = icon.getIntrinsicWidth();
186            int height = icon.getIntrinsicHeight();
187            if (width > mMaxIconSize) {
188                final float scale = (float) mMaxIconSize / width;
189                width = mMaxIconSize;
190                height *= scale;
191            }
192            if (height > mMaxIconSize) {
193                final float scale = (float) mMaxIconSize / height;
194                height = mMaxIconSize;
195                width *= scale;
196            }
197            icon.setBounds(0, 0, width, height);
198        }
199        setCompoundDrawables(icon, null, null, null);
200
201        updateTextButtonVisibility();
202    }
203
204    public boolean hasText() {
205        return !TextUtils.isEmpty(getText());
206    }
207
208    public void setShortcut(boolean showShortcut, char shortcutKey) {
209        // Action buttons don't show text for shortcut keys.
210    }
211
212    public void setTitle(CharSequence title) {
213        mTitle = title;
214
215        setContentDescription(mTitle);
216        updateTextButtonVisibility();
217    }
218
219    @Override
220    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
221        onPopulateAccessibilityEvent(event);
222        return true;
223    }
224
225    @Override
226    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
227        super.onPopulateAccessibilityEvent(event);
228        final CharSequence cdesc = getContentDescription();
229        if (!TextUtils.isEmpty(cdesc)) {
230            event.getText().add(cdesc);
231        }
232    }
233
234    @Override
235    public boolean dispatchHoverEvent(MotionEvent event) {
236        // Don't allow children to hover; we want this to be treated as a single component.
237        return onHoverEvent(event);
238    }
239
240    public boolean showsIcon() {
241        return true;
242    }
243
244    public boolean needsDividerBefore() {
245        return hasText() && mItemData.getIcon() == null;
246    }
247
248    public boolean needsDividerAfter() {
249        return hasText();
250    }
251
252    @Override
253    public boolean onLongClick(View v) {
254        if (hasText()) {
255            // Don't show the cheat sheet for items that already show text.
256            return false;
257        }
258
259        final int[] screenPos = new int[2];
260        final Rect displayFrame = new Rect();
261        getLocationOnScreen(screenPos);
262        getWindowVisibleDisplayFrame(displayFrame);
263
264        final Context context = getContext();
265        final int width = getWidth();
266        final int height = getHeight();
267        final int midy = screenPos[1] + height / 2;
268        final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
269
270        Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
271        if (midy < displayFrame.height()) {
272            // Show along the top; follow action buttons
273            cheatSheet.setGravity(Gravity.TOP | Gravity.END,
274                    screenWidth - screenPos[0] - width / 2, height);
275        } else {
276            // Show along the bottom center
277            cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
278        }
279        cheatSheet.show();
280        return true;
281    }
282
283    @Override
284    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
285        final boolean textVisible = hasText();
286        if (textVisible && mSavedPaddingLeft >= 0) {
287            super.setPadding(mSavedPaddingLeft, getPaddingTop(),
288                    getPaddingRight(), getPaddingBottom());
289        }
290
291        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
292
293        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
294        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
295        final int oldMeasuredWidth = getMeasuredWidth();
296        final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(widthSize, mMinWidth)
297                : mMinWidth;
298
299        if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) {
300            // Remeasure at exactly the minimum width.
301            super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
302                    heightMeasureSpec);
303        }
304
305        if (!textVisible && mIcon != null) {
306            // TextView won't center compound drawables in both dimensions without
307            // a little coercion. Pad in to center the icon after we've measured.
308            final int w = getMeasuredWidth();
309            final int dw = mIcon.getBounds().width();
310            super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
311        }
312    }
313
314    private class ActionMenuItemForwardingListener extends ForwardingListener {
315        public ActionMenuItemForwardingListener() {
316            super(ActionMenuItemView.this);
317        }
318
319        @Override
320        public ListPopupWindow getPopup() {
321            if (mPopupCallback != null) {
322                return mPopupCallback.getPopup();
323            }
324            return null;
325        }
326
327        @Override
328        protected boolean onForwardingStarted() {
329            // Call the invoker, then check if the expected popup is showing.
330            if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
331                final ListPopupWindow popup = getPopup();
332                return popup != null && popup.isShowing();
333            }
334            return false;
335        }
336
337        @Override
338        protected boolean onForwardingStopped() {
339            final ListPopupWindow popup = getPopup();
340            if (popup != null) {
341                popup.dismiss();
342                return true;
343            }
344            return false;
345        }
346    }
347
348    public static abstract class PopupCallback {
349        public abstract ListPopupWindow getPopup();
350    }
351}
352