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