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