菜单项的选中状态通过其图标没有正确显示。

24

我这样定义了一个MenuItem:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_starred"
        android:icon="@drawable/btn_star"
        android:title="@string/description_star"
        android:checkable="true"
        android:checked="true"
        android:orderInCategory="1"
        android:showAsAction="always" />
</menu>

并且 btn_star.xml 是这样定义的:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item 
        android:state_checked="false" 
        android:drawable="@drawable/btn_star_off_normal" />
    <item 
        android:state_checked="true"
        android:drawable="@drawable/btn_star_on_normal" />
</selector>

然而,如果我使用这个方法创建一个选项菜单,无论 MenuItemisChecked() 属性是否为 true,其图标都不会显示为已选择状态。

我正在使用 ActionBarSherlock 控件,但是,即使我只是创建一个普通的选项菜单并调用 setChecked(true),我仍然得到相同的结果。它仍然显示 btn_star_off 可绘制对象,而不管该项的选择状态如何。

onOptionsItemSelected() 方法被正确地调用,我可以成功地更改已选择属性:

@Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(item.isCheckable()) {
            item.setChecked(!item.isChecked());
        }
        return super.onOptionsItemSelected(item);
}

在这里设置断点,可以看到isChecked属性被更改了,但图标本身没有更新以反映正确的状态。

我是否遗漏了什么?我做错了什么吗?我无法弄清楚为什么这样不正确地工作。


我发现了同样的问题,state_checked 对于菜单图标无法正常工作,原因不明。 - gregm
5个回答

41
根据官方文档所述:

注意:选项菜单中的图标菜单中的菜单项不能显示复选框或单选按钮。如果您选择使图标菜单中的项目可选,则每次更改状态时,您必须手动指示已选状态,方法是交换图标和/或文本。

希望这有所帮助。


啊,我不知道我怎么错过了那个 -- 我已经读了几遍那份文档。谢谢! - Joe Krill
1
让我感到困惑的是添加checkable="true"确实在菜单项中添加了复选框,但这个注意事项似乎仅适用于图标菜单中的菜单项而不是所有菜单。 - Flimm
4
Google为什么?你为什么不能使用drawable的state_checked? - DrBreakalot

12
如果您仍然希望在 xml drawable 中定义行为(选中或未选中),这是一个实现方法:
if (item.getItemId()==R.id.menu_item){
    item.setChecked(!item.isChecked());
    StateListDrawable stateListDrawable = (StateListDrawable) getResources().getDrawable(R.drawable.selector_drawable);
    int[] state = {item.isChecked()?android.R.attr.state_checked:android.R.attr.state_empty};
    stateListDrawable.setState(state);
    item.setIcon(stateListDrawable.getCurrent());
}

R.drawable.selector_drawable 是什么? - Flimm
包含多个状态(聚焦、按下等)的 XML 可绘制对象。在上面的示例中,它将是 btn_star.xml - Andrei Tudor Diaconu

6
一种更简单的方法(不需要xml状态文件):
configChecked = !configChecked;
item.setChecked(configChecked);
item.setIcon(configChecked ? R.drawable.check_on : R.drawable.check_off);

为什么需要变量 configChecked - Flimm
Flimm,因为每次单击复选框时都必须将其更改为相反的值。 - kolyaseg

4

这个问题有点老,但我最近遇到了这个问题。

经过一些分析后,发现菜单项的选中状态没有被正确地传递到可绘制对象中。以下是我想出的解决方案(用 Kotlin 编写)。

确保你的 menuItem 使用一个状态列表可绘制对象(如问题中的 btn_star.xml),然后创建一个可绘制对象包装类:

/** Fixes checked state being ignored by injecting checked state directly into drawable */
class CheckDrawableWrapper(val menuItem: MenuItem) : DrawableWrapper(menuItem.icon) {
    // inject checked state into drawable state set
    override fun setState(stateSet: IntArray) = super.setState(
        if (menuItem.isChecked) stateSet + android.R.attr.state_checked else stateSet
    )
}

/** Wrap icon drawable with [CheckDrawableWrapper]. */
fun MenuItem.fixCheckStateOnIcon() = apply { icon = CheckDrawableWrapper(this) }

最后一步是使用包装的drawable替换菜单项的drawable:
override fun onCreateOptionsMenu(menu: Menu) : Boolean{
    menuInflater.inflate(R.menu.menu_main, menu)
    menu.findItem(R.id.menu_starred).fixCheckStateOnIcon()
    /** ...  */
    return true
}

在此之后,当更改菜单项的选中状态时,您无需执行任何操作,图标应该是自我感知的,并在选中状态发生更改时作出反应。


DrawableWrapper类需要API级别23。 - A. Petrov
@A.Petrov 我认为你可以使用 androidx.appcompat.graphics.drawable.DrawableWrapper,只需在类顶部添加 @SuppressLint("RestrictedApi") 即可。 - Pawel
@SuppressLint 总是一种不好的方法。目前,我采用了 @Andrei Tudor Diaconu 的解决方案,需要注意的是:状态列表可包含许多状态(>2),但仅前两个状态会受到影响,并且可以在处理 Toolbar.MenuItem 时使用。 - A. Petrov
我认为这并不总是一种不好的方法。我猜他们在那个类上面放了受限注释,因为它只是一个“粗略的辅助类”,而不是一个公开的API,但我认为忽略它没有问题,因为它只是一个简单的委托给包装对象(你可以查看源代码)。 - Pawel
(你可以查看来源) - 这是关键。这个API在Jetpack的演进中没有生存保证。
- A. Petrov
是的,禁用警告存在一定的“风险”,但在我看来,在这种情况下它是不存在的,因为那个类非常简单,并且自创建以来没有被修改过(除了 androidx 迁移)。但是如果你不想使用禁用警告,总有一个选择,就是将该类复制到你的项目中以获得稳定版本。 - Pawel

1
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/fav"
        android:title=""
        app:showAsAction="ifRoom"
        android:orderInCategory="1"
        android:icon="@drawable/ic_favorite_black_unselectd"
        android:checked="false" />
    <item android:id="@+id/share"
        android:title=""
        app:showAsAction="ifRoom"
        android:orderInCategory="2"
        android:icon="@drawable/ic_share_black" />
</menu>

//and in java...

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.fav:
            boolean mState = !item.isChecked();
            item.setChecked(mState);
            item.setIcon(mState ? getResources().getDrawable(R.drawable.ic_favorite_black_selected) : getResources().getDrawable(R.drawable.ic_favorite_black_unselectd));
            Toast.makeText(this, "" + item.isChecked(), Toast.LENGTH_SHORT).show();
            return true;
        case R.id.share:
            return true;
    }
    return super.onOptionsItemSelected(item);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接