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 17 package com.android.internal.view.menu; 18 19 import com.android.internal.view.menu.MenuBuilder.ItemInvoker; 20 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.graphics.Rect; 24 import android.graphics.drawable.Drawable; 25 import android.util.AttributeSet; 26 import android.view.Gravity; 27 import android.view.SoundEffectConstants; 28 import android.view.View; 29 import android.view.ViewDebug; 30 import android.widget.TextView; 31 import android.text.Layout; 32 33 /** 34 * The item view for each item in the {@link IconMenuView}. 35 */ 36 public 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 @Override 116 public boolean performClick() { 117 // Let the view's click listener have top priority (the More button relies on this) 118 if (super.performClick()) { 119 return true; 120 } 121 122 if ((mItemInvoker != null) && (mItemInvoker.invokeItem(mItemData))) { 123 playSoundEffect(SoundEffectConstants.CLICK); 124 return true; 125 } else { 126 return false; 127 } 128 } 129 130 public void setTitle(CharSequence title) { 131 132 if (mShortcutCaptionMode) { 133 /* 134 * Don't set the title directly since it will replace the 135 * shortcut+title being shown. Instead, re-set the shortcut caption 136 * mode so the new title is shown. 137 */ 138 setCaptionMode(true); 139 140 } else if (title != null) { 141 setText(title); 142 } 143 } 144 145 void setCaptionMode(boolean shortcut) { 146 /* 147 * If there is no item model, don't do any of the below (for example, 148 * the 'More' item doesn't have a model) 149 */ 150 if (mItemData == null) { 151 return; 152 } 153 154 mShortcutCaptionMode = shortcut && (mItemData.shouldShowShortcut()); 155 156 CharSequence text = mItemData.getTitleForItemView(this); 157 158 if (mShortcutCaptionMode) { 159 160 if (mShortcutCaption == null) { 161 mShortcutCaption = mItemData.getShortcutLabel(); 162 } 163 164 text = mShortcutCaption; 165 } 166 167 setText(text); 168 } 169 170 public void setIcon(Drawable icon) { 171 mIcon = icon; 172 173 if (icon != null) { 174 175 /* Set the bounds of the icon since setCompoundDrawables needs it. */ 176 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 177 178 // Set the compound drawables 179 setCompoundDrawables(null, icon, null, null); 180 181 // When there is an icon, make sure the text is at the bottom 182 setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL); 183 184 /* 185 * Request a layout to reposition the icon. The positioning of icon 186 * depends on this TextView's line bounds, which is only available 187 * after a layout. 188 */ 189 requestLayout(); 190 } else { 191 setCompoundDrawables(null, null, null, null); 192 193 // When there is no icon, make sure the text is centered vertically 194 setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL); 195 } 196 } 197 198 public void setItemInvoker(ItemInvoker itemInvoker) { 199 mItemInvoker = itemInvoker; 200 } 201 202 @ViewDebug.CapturedViewProperty(retrieveReturn = true) 203 public MenuItemImpl getItemData() { 204 return mItemData; 205 } 206 207 @Override 208 public void setVisibility(int v) { 209 super.setVisibility(v); 210 211 if (mIconMenuView != null) { 212 // On visibility change, mark the IconMenuView to refresh itself eventually 213 mIconMenuView.markStaleChildren(); 214 } 215 } 216 217 void setIconMenuView(IconMenuView iconMenuView) { 218 mIconMenuView = iconMenuView; 219 } 220 221 @Override 222 protected void drawableStateChanged() { 223 super.drawableStateChanged(); 224 225 if (mItemData != null && mIcon != null) { 226 // When disabled, the not-focused state and the pressed state should 227 // drop alpha on the icon 228 final boolean isInAlphaState = !mItemData.isEnabled() && (isPressed() || !isFocused()); 229 mIcon.setAlpha(isInAlphaState ? (int) (mDisabledAlpha * NO_ALPHA) : NO_ALPHA); 230 } 231 } 232 233 @Override 234 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 235 super.onLayout(changed, left, top, right, bottom); 236 237 positionIcon(); 238 } 239 240 @Override 241 protected void onTextChanged(CharSequence text, int start, int before, int after) { 242 super.onTextChanged(text, start, before, after); 243 244 // our layout params depend on the length of the text 245 setLayoutParams(getTextAppropriateLayoutParams()); 246 } 247 248 /** 249 * @return layout params appropriate for this view. If layout params already exist, it will 250 * augment them to be appropriate to the current text size. 251 */ 252 IconMenuView.LayoutParams getTextAppropriateLayoutParams() { 253 IconMenuView.LayoutParams lp = (IconMenuView.LayoutParams) getLayoutParams(); 254 if (lp == null) { 255 // Default layout parameters 256 lp = new IconMenuView.LayoutParams( 257 IconMenuView.LayoutParams.MATCH_PARENT, IconMenuView.LayoutParams.MATCH_PARENT); 258 } 259 260 // Set the desired width of item 261 lp.desiredWidth = (int) Layout.getDesiredWidth(getText(), getPaint()); 262 263 return lp; 264 } 265 266 /** 267 * Positions the icon vertically (horizontal centering is taken care of by 268 * the TextView's gravity). 269 */ 270 private void positionIcon() { 271 272 if (mIcon == null) { 273 return; 274 } 275 276 // We reuse the output rectangle as a temp rect 277 Rect tmpRect = mPositionIconOutput; 278 getLineBounds(0, tmpRect); 279 mPositionIconAvailable.set(0, 0, getWidth(), tmpRect.top); 280 Gravity.apply(Gravity.CENTER_VERTICAL | Gravity.LEFT, mIcon.getIntrinsicWidth(), mIcon 281 .getIntrinsicHeight(), mPositionIconAvailable, mPositionIconOutput); 282 mIcon.setBounds(mPositionIconOutput); 283 } 284 285 public void setCheckable(boolean checkable) { 286 } 287 288 public void setChecked(boolean checked) { 289 } 290 291 public void setShortcut(boolean showShortcut, char shortcutKey) { 292 293 if (mShortcutCaptionMode) { 294 /* 295 * Shortcut has changed and we're showing it right now, need to 296 * update (clear the old one first). 297 */ 298 mShortcutCaption = null; 299 setCaptionMode(true); 300 } 301 } 302 303 public boolean prefersCondensedTitle() { 304 return true; 305 } 306 307 public boolean showsIcon() { 308 return true; 309 } 310 311 } 312