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