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