IconMenuItemView.java revision c0053223bedf33581b0830fb87be32c1f26e5372
1/*
2 * Copyright (C) 2006 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 com.android.internal.view.menu.MenuBuilder.ItemInvoker;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.util.AttributeSet;
26import android.view.Gravity;
27import android.view.SoundEffectConstants;
28import android.view.View;
29import android.view.ViewDebug;
30import android.widget.TextView;
31import android.text.Layout;
32
33/**
34 * The item view for each item in the {@link IconMenuView}.
35 */
36public final class IconMenuItemView extends TextView implements MenuView.ItemView {
37
38    private static final int NO_ALPHA = 0xFF;
39
40    private IconMenuView mIconMenuView;
41
42    private ItemInvoker mItemInvoker;
43    private MenuItemImpl mItemData;
44
45    private Drawable mIcon;
46
47    private int mTextAppearance;
48    private Context mTextAppearanceContext;
49
50    private float mDisabledAlpha;
51
52    private Rect mPositionIconAvailable = new Rect();
53    private Rect mPositionIconOutput = new Rect();
54
55    private boolean mShortcutCaptionMode;
56    private String mShortcutCaption;
57
58    private static String sPrependShortcutLabel;
59
60    public IconMenuItemView(Context context, AttributeSet attrs, int defStyle) {
61        super(context, attrs);
62
63        if (sPrependShortcutLabel == null) {
64            /*
65             * Views should only be constructed from the UI thread, so no
66             * synchronization needed
67             */
68            sPrependShortcutLabel = getResources().getString(
69                    com.android.internal.R.string.prepend_shortcut_label);
70        }
71
72        TypedArray a =
73            context.obtainStyledAttributes(
74                attrs, com.android.internal.R.styleable.MenuView, defStyle, 0);
75
76        mDisabledAlpha = a.getFloat(
77                com.android.internal.R.styleable.MenuView_itemIconDisabledAlpha, 0.8f);
78        mTextAppearance = a.getResourceId(com.android.internal.R.styleable.
79                                          MenuView_itemTextAppearance, -1);
80        mTextAppearanceContext = context;
81
82        a.recycle();
83    }
84
85    public IconMenuItemView(Context context, AttributeSet attrs) {
86        this(context, attrs, 0);
87    }
88
89    /**
90     * Initializes with the provided title and icon
91     * @param title The title of this item
92     * @param icon The icon of this item
93     */
94    void initialize(CharSequence title, Drawable icon) {
95        setClickable(true);
96        setFocusable(true);
97
98        if (mTextAppearance != -1) {
99            setTextAppearance(mTextAppearanceContext, mTextAppearance);
100        }
101
102        setTitle(title);
103        setIcon(icon);
104    }
105
106    public void initialize(MenuItemImpl itemData, int menuType) {
107        mItemData = itemData;
108
109        initialize(itemData.getTitleForItemView(this), itemData.getIcon());
110
111        setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
112        setEnabled(itemData.isEnabled());
113    }
114
115    public void setItemData(MenuItemImpl data) {
116        mItemData = data;
117    }
118
119    @Override
120    public boolean performClick() {
121        // Let the view's click listener have top priority (the More button relies on this)
122        if (super.performClick()) {
123            return true;
124        }
125
126        if ((mItemInvoker != null) && (mItemInvoker.invokeItem(mItemData))) {
127            playSoundEffect(SoundEffectConstants.CLICK);
128            return true;
129        } else {
130            return false;
131        }
132    }
133
134    public void setTitle(CharSequence title) {
135
136        if (mShortcutCaptionMode) {
137            /*
138             * Don't set the title directly since it will replace the
139             * shortcut+title being shown. Instead, re-set the shortcut caption
140             * mode so the new title is shown.
141             */
142            setCaptionMode(true);
143
144        } else if (title != null) {
145            setText(title);
146        }
147    }
148
149    void setCaptionMode(boolean shortcut) {
150        /*
151         * If there is no item model, don't do any of the below (for example,
152         * the 'More' item doesn't have a model)
153         */
154        if (mItemData == null) {
155            return;
156        }
157
158        mShortcutCaptionMode = shortcut && (mItemData.shouldShowShortcut());
159
160        CharSequence text = mItemData.getTitleForItemView(this);
161
162        if (mShortcutCaptionMode) {
163
164            if (mShortcutCaption == null) {
165                mShortcutCaption = mItemData.getShortcutLabel();
166            }
167
168            text = mShortcutCaption;
169        }
170
171        setText(text);
172    }
173
174    public void setIcon(Drawable icon) {
175        mIcon = icon;
176
177        if (icon != null) {
178
179            /* Set the bounds of the icon since setCompoundDrawables needs it. */
180            icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
181
182            // Set the compound drawables
183            setCompoundDrawables(null, icon, null, null);
184
185            // When there is an icon, make sure the text is at the bottom
186            setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
187
188            /*
189             * Request a layout to reposition the icon. The positioning of icon
190             * depends on this TextView's line bounds, which is only available
191             * after a layout.
192             */
193            requestLayout();
194        } else {
195            setCompoundDrawables(null, null, null, null);
196
197            // When there is no icon, make sure the text is centered vertically
198            setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
199        }
200    }
201
202    public void setItemInvoker(ItemInvoker itemInvoker) {
203        mItemInvoker = itemInvoker;
204    }
205
206    @ViewDebug.CapturedViewProperty(retrieveReturn = true)
207    public MenuItemImpl getItemData() {
208        return mItemData;
209    }
210
211    @Override
212    public void setVisibility(int v) {
213        super.setVisibility(v);
214
215        if (mIconMenuView != null) {
216            // On visibility change, mark the IconMenuView to refresh itself eventually
217            mIconMenuView.markStaleChildren();
218        }
219    }
220
221    void setIconMenuView(IconMenuView iconMenuView) {
222        mIconMenuView = iconMenuView;
223    }
224
225    @Override
226    protected void drawableStateChanged() {
227        super.drawableStateChanged();
228
229        if (mItemData != null && mIcon != null) {
230            // When disabled, the not-focused state and the pressed state should
231            // drop alpha on the icon
232            final boolean isInAlphaState = !mItemData.isEnabled() && (isPressed() || !isFocused());
233            mIcon.setAlpha(isInAlphaState ? (int) (mDisabledAlpha * NO_ALPHA) : NO_ALPHA);
234        }
235    }
236
237    @Override
238    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
239        super.onLayout(changed, left, top, right, bottom);
240
241        positionIcon();
242    }
243
244    @Override
245    protected void onTextChanged(CharSequence text, int start, int before, int after) {
246        super.onTextChanged(text, start, before, after);
247
248        // our layout params depend on the length of the text
249        setLayoutParams(getTextAppropriateLayoutParams());
250    }
251
252    /**
253     * @return layout params appropriate for this view.  If layout params already exist, it will
254     *         augment them to be appropriate to the current text size.
255     */
256    IconMenuView.LayoutParams getTextAppropriateLayoutParams() {
257        IconMenuView.LayoutParams lp = (IconMenuView.LayoutParams) getLayoutParams();
258        if (lp == null) {
259            // Default layout parameters
260            lp = new IconMenuView.LayoutParams(
261                    IconMenuView.LayoutParams.MATCH_PARENT, IconMenuView.LayoutParams.MATCH_PARENT);
262        }
263
264        // Set the desired width of item
265        lp.desiredWidth = (int) Layout.getDesiredWidth(getText(), getPaint());
266
267        return lp;
268    }
269
270    /**
271     * Positions the icon vertically (horizontal centering is taken care of by
272     * the TextView's gravity).
273     */
274    private void positionIcon() {
275
276        if (mIcon == null) {
277            return;
278        }
279
280        // We reuse the output rectangle as a temp rect
281        Rect tmpRect = mPositionIconOutput;
282        getLineBounds(0, tmpRect);
283        mPositionIconAvailable.set(0, 0, getWidth(), tmpRect.top);
284        final int layoutDirection = getResolvedLayoutDirection();
285        Gravity.apply(Gravity.CENTER_VERTICAL | Gravity.LEFT, mIcon.getIntrinsicWidth(), mIcon
286                .getIntrinsicHeight(), mPositionIconAvailable, mPositionIconOutput,
287                layoutDirection);
288        mIcon.setBounds(mPositionIconOutput);
289    }
290
291    public void setCheckable(boolean checkable) {
292    }
293
294    public void setChecked(boolean checked) {
295    }
296
297    public void setShortcut(boolean showShortcut, char shortcutKey) {
298
299        if (mShortcutCaptionMode) {
300            /*
301             * Shortcut has changed and we're showing it right now, need to
302             * update (clear the old one first).
303             */
304            mShortcutCaption = null;
305            setCaptionMode(true);
306        }
307    }
308
309    public boolean prefersCondensedTitle() {
310        return true;
311    }
312
313    public boolean showsIcon() {
314        return true;
315    }
316
317}
318