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 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 = getLayoutDirection(); 285 Gravity.apply(Gravity.CENTER_VERTICAL | Gravity.START, 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