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.R; 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.LayoutInflater; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.view.accessibility.AccessibilityNodeInfo; 30 import android.widget.AbsListView; 31 import android.widget.CheckBox; 32 import android.widget.CompoundButton; 33 import android.widget.ImageView; 34 import android.widget.LinearLayout; 35 import android.widget.RadioButton; 36 import android.widget.TextView; 37 38 /** 39 * The item view for each item in the ListView-based MenuViews. 40 */ 41 public class ListMenuItemView extends LinearLayout 42 implements MenuView.ItemView, AbsListView.SelectionBoundsAdjuster { 43 private static final String TAG = "ListMenuItemView"; 44 private MenuItemImpl mItemData; 45 46 private ImageView mIconView; 47 private RadioButton mRadioButton; 48 private TextView mTitleView; 49 private CheckBox mCheckBox; 50 private TextView mShortcutView; 51 private ImageView mSubMenuArrowView; 52 private ImageView mGroupDivider; 53 private LinearLayout mContent; 54 55 private Drawable mBackground; 56 private int mTextAppearance; 57 private Context mTextAppearanceContext; 58 private boolean mPreserveIconSpacing; 59 private Drawable mSubMenuArrow; 60 private boolean mHasListDivider; 61 62 private int mMenuType; 63 64 private LayoutInflater mInflater; 65 66 private boolean mForceShowIcon; 67 68 public ListMenuItemView( 69 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 70 super(context, attrs, defStyleAttr, defStyleRes); 71 72 final TypedArray a = context.obtainStyledAttributes( 73 attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes); 74 75 mBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground); 76 mTextAppearance = a.getResourceId(com.android.internal.R.styleable. 77 MenuView_itemTextAppearance, -1); 78 mPreserveIconSpacing = a.getBoolean( 79 com.android.internal.R.styleable.MenuView_preserveIconSpacing, false); 80 mTextAppearanceContext = context; 81 mSubMenuArrow = a.getDrawable(com.android.internal.R.styleable.MenuView_subMenuArrow); 82 83 final TypedArray b = context.getTheme() 84 .obtainStyledAttributes(null, new int[] { com.android.internal.R.attr.divider }, 85 com.android.internal.R.attr.dropDownListViewStyle, 0); 86 mHasListDivider = b.hasValue(0); 87 88 a.recycle(); 89 b.recycle(); 90 } 91 92 public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { 93 this(context, attrs, defStyleAttr, 0); 94 } 95 96 public ListMenuItemView(Context context, AttributeSet attrs) { 97 this(context, attrs, com.android.internal.R.attr.listMenuViewStyle); 98 } 99 100 @Override 101 protected void onFinishInflate() { 102 super.onFinishInflate(); 103 104 setBackgroundDrawable(mBackground); 105 106 mTitleView = findViewById(com.android.internal.R.id.title); 107 if (mTextAppearance != -1) { 108 mTitleView.setTextAppearance(mTextAppearanceContext, 109 mTextAppearance); 110 } 111 112 mShortcutView = findViewById(com.android.internal.R.id.shortcut); 113 mSubMenuArrowView = findViewById(com.android.internal.R.id.submenuarrow); 114 if (mSubMenuArrowView != null) { 115 mSubMenuArrowView.setImageDrawable(mSubMenuArrow); 116 } 117 mGroupDivider = findViewById(com.android.internal.R.id.group_divider); 118 119 mContent = findViewById(com.android.internal.R.id.content); 120 } 121 122 public void initialize(MenuItemImpl itemData, int menuType) { 123 mItemData = itemData; 124 mMenuType = menuType; 125 126 setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); 127 128 setTitle(itemData.getTitleForItemView(this)); 129 setCheckable(itemData.isCheckable()); 130 setShortcut(itemData.shouldShowShortcut(), itemData.getShortcut()); 131 setIcon(itemData.getIcon()); 132 setEnabled(itemData.isEnabled()); 133 setSubMenuArrowVisible(itemData.hasSubMenu()); 134 setContentDescription(itemData.getContentDescription()); 135 } 136 137 private void addContentView(View v) { 138 addContentView(v, -1); 139 } 140 141 private void addContentView(View v, int index) { 142 if (mContent != null) { 143 mContent.addView(v, index); 144 } else { 145 addView(v, index); 146 } 147 } 148 149 public void setForceShowIcon(boolean forceShow) { 150 mPreserveIconSpacing = mForceShowIcon = forceShow; 151 } 152 153 public void setTitle(CharSequence title) { 154 if (title != null) { 155 mTitleView.setText(title); 156 157 if (mTitleView.getVisibility() != VISIBLE) mTitleView.setVisibility(VISIBLE); 158 } else { 159 if (mTitleView.getVisibility() != GONE) mTitleView.setVisibility(GONE); 160 } 161 } 162 163 public MenuItemImpl getItemData() { 164 return mItemData; 165 } 166 167 public void setCheckable(boolean checkable) { 168 if (!checkable && mRadioButton == null && mCheckBox == null) { 169 return; 170 } 171 172 // Depending on whether its exclusive check or not, the checkbox or 173 // radio button will be the one in use (and the other will be otherCompoundButton) 174 final CompoundButton compoundButton; 175 final CompoundButton otherCompoundButton; 176 177 if (mItemData.isExclusiveCheckable()) { 178 if (mRadioButton == null) { 179 insertRadioButton(); 180 } 181 compoundButton = mRadioButton; 182 otherCompoundButton = mCheckBox; 183 } else { 184 if (mCheckBox == null) { 185 insertCheckBox(); 186 } 187 compoundButton = mCheckBox; 188 otherCompoundButton = mRadioButton; 189 } 190 191 if (checkable) { 192 compoundButton.setChecked(mItemData.isChecked()); 193 194 final int newVisibility = checkable ? VISIBLE : GONE; 195 if (compoundButton.getVisibility() != newVisibility) { 196 compoundButton.setVisibility(newVisibility); 197 } 198 199 // Make sure the other compound button isn't visible 200 if (otherCompoundButton != null && otherCompoundButton.getVisibility() != GONE) { 201 otherCompoundButton.setVisibility(GONE); 202 } 203 } else { 204 if (mCheckBox != null) mCheckBox.setVisibility(GONE); 205 if (mRadioButton != null) mRadioButton.setVisibility(GONE); 206 } 207 } 208 209 public void setChecked(boolean checked) { 210 CompoundButton compoundButton; 211 212 if (mItemData.isExclusiveCheckable()) { 213 if (mRadioButton == null) { 214 insertRadioButton(); 215 } 216 compoundButton = mRadioButton; 217 } else { 218 if (mCheckBox == null) { 219 insertCheckBox(); 220 } 221 compoundButton = mCheckBox; 222 } 223 224 compoundButton.setChecked(checked); 225 } 226 227 private void setSubMenuArrowVisible(boolean hasSubmenu) { 228 if (mSubMenuArrowView != null) { 229 mSubMenuArrowView.setVisibility(hasSubmenu ? View.VISIBLE : View.GONE); 230 } 231 } 232 233 public void setShortcut(boolean showShortcut, char shortcutKey) { 234 final int newVisibility = (showShortcut && mItemData.shouldShowShortcut()) 235 ? VISIBLE : GONE; 236 237 if (newVisibility == VISIBLE) { 238 mShortcutView.setText(mItemData.getShortcutLabel()); 239 } 240 241 if (mShortcutView.getVisibility() != newVisibility) { 242 mShortcutView.setVisibility(newVisibility); 243 } 244 } 245 246 public void setIcon(Drawable icon) { 247 final boolean showIcon = mItemData.shouldShowIcon() || mForceShowIcon; 248 if (!showIcon && !mPreserveIconSpacing) { 249 return; 250 } 251 252 if (mIconView == null && icon == null && !mPreserveIconSpacing) { 253 return; 254 } 255 256 if (mIconView == null) { 257 insertIconView(); 258 } 259 260 if (icon != null || mPreserveIconSpacing) { 261 mIconView.setImageDrawable(showIcon ? icon : null); 262 263 if (mIconView.getVisibility() != VISIBLE) { 264 mIconView.setVisibility(VISIBLE); 265 } 266 } else { 267 mIconView.setVisibility(GONE); 268 } 269 } 270 271 @Override 272 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 273 if (mIconView != null && mPreserveIconSpacing) { 274 // Enforce minimum icon spacing 275 ViewGroup.LayoutParams lp = getLayoutParams(); 276 LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams(); 277 if (lp.height > 0 && iconLp.width <= 0) { 278 iconLp.width = lp.height; 279 } 280 } 281 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 282 } 283 284 private void insertIconView() { 285 LayoutInflater inflater = getInflater(); 286 mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon, 287 this, false); 288 addContentView(mIconView, 0); 289 } 290 291 private void insertRadioButton() { 292 LayoutInflater inflater = getInflater(); 293 mRadioButton = 294 (RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio, 295 this, false); 296 addContentView(mRadioButton); 297 } 298 299 private void insertCheckBox() { 300 LayoutInflater inflater = getInflater(); 301 mCheckBox = 302 (CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox, 303 this, false); 304 addContentView(mCheckBox); 305 } 306 307 public boolean prefersCondensedTitle() { 308 return false; 309 } 310 311 public boolean showsIcon() { 312 return mForceShowIcon; 313 } 314 315 private LayoutInflater getInflater() { 316 if (mInflater == null) { 317 mInflater = LayoutInflater.from(mContext); 318 } 319 return mInflater; 320 } 321 322 @Override 323 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 324 super.onInitializeAccessibilityNodeInfoInternal(info); 325 326 if (mItemData != null && mItemData.hasSubMenu()) { 327 info.setCanOpenPopup(true); 328 } 329 } 330 331 /** 332 * Enable or disable group dividers for this view. 333 */ 334 public void setGroupDividerEnabled(boolean groupDividerEnabled) { 335 // If mHasListDivider is true, disabling the groupDivider. 336 // Otherwise, checking enbling it according to groupDividerEnabled flag. 337 if (mGroupDivider != null) { 338 mGroupDivider.setVisibility(!mHasListDivider 339 && groupDividerEnabled ? View.VISIBLE : View.GONE); 340 } 341 } 342 343 @Override 344 public void adjustListItemSelectionBounds(Rect rect) { 345 if (mGroupDivider != null && mGroupDivider.getVisibility() == View.VISIBLE) { 346 // groupDivider is a part of MenuItemListView. 347 // If ListMenuItem with divider enabled is hovered/clicked, divider also gets selected. 348 // Clipping the selector bounds from the top divider portion when divider is enabled, 349 // so that divider does not get selected on hover or click. 350 final LayoutParams lp = (LayoutParams) mGroupDivider.getLayoutParams(); 351 rect.top += mGroupDivider.getHeight() + lp.topMargin + lp.bottomMargin; 352 } 353 } 354 } 355