# 弹出菜单(PluginPopupMenu)

PluginPopupMenu 提供弹出式菜单功能,可在指定的锚点视图附近显示菜单列表。支持菜单项图标、可选中状态、分组管理、多级子菜单等丰富功能,适用于上下文菜单、选项菜单等场景。

# 快速开始

// 创建弹出菜单
PluginPopupMenu popupMenu = pluginUI.createPopupMenu(anchorView);

// 获取菜单并添加菜单项
PluginMenu menu = popupMenu.getMenu();
menu.add("item1", "菜单项 1");
menu.add("item2", "菜单项 2");
menu.add("item3", "菜单项 3");

// 设置菜单项点击监听器
popupMenu.setOnMenuItemClickListener(item -> {
    pluginUI.showToast("点击了:" + item.getTitle());
    return true;  // 返回 true 表示已处理该事件
});

// 显示菜单
popupMenu.show();

# 接口概览

# PluginPopupMenu 接口

方法 说明
getMenu() 获取菜单对象
setGravity(int) 设置菜单对齐方式
getGravity() 获取菜单对齐方式
show() 显示弹出菜单
dismiss() 关闭弹出菜单
setOnMenuItemClickListener() 设置菜单项点击监听器
setOnDismissListener() 设置菜单关闭监听器

# PluginMenu 接口

方法 说明
add(CharSequence) 添加菜单项(ID 和标题相同)
add(String, CharSequence) 添加菜单项(指定 ID 和标题)
add(String, CharSequence, String) 添加菜单项(指定 ID、标题和分组)
addSubMenu(CharSequence) 添加子菜单(ID 和标题相同)
addSubMenu(String, CharSequence) 添加子菜单(指定 ID 和标题)
addSubMenu(String, CharSequence, String) 添加子菜单(指定 ID、标题和分组)
findItem(String) 根据 ID 查找菜单项
getItem(int) 获取指定索引的菜单项
size() 获取菜单项总数
setGroupCheckable() 设置分组的可选中状态
setGroupVisible() 设置分组的可见性
setGroupEnabled() 设置分组的启用状态
setGroupDividerEnabled() 设置是否显示分组分隔线
clear() 清空菜单

# PluginMenuItem 接口

方法 说明
getItemId() 获取菜单项 ID
getGroupId() 获取菜单项所属分组 ID
setTitle(CharSequence) 设置标题
getTitle() 获取标题
setIcon(Drawable) 设置图标
getIcon() 获取图标
setIconTintList(ColorStateList) 设置图标着色
getIconTintList() 获取图标着色
setCheckable(boolean) 设置是否可选中
isCheckable() 获取是否可选中
setChecked(boolean) 设置选中状态
isChecked() 获取选中状态
setVisible(boolean) 设置可见性
isVisible() 获取可见性
setEnabled(boolean) 设置启用状态
isEnabled() 获取启用状态
hasSubMenu() 判断是否包含子菜单
getSubMenu() 获取子菜单

# PluginSubMenu 接口

方法 说明
setIcon(Drawable) 设置子菜单图标
getItem() 获取代表此子菜单的菜单项

提示:PluginSubMenu 继承自 PluginMenu,因此也具有 PluginMenu 的所有方法。

# 创建弹出菜单

# createPopupMenu

PluginPopupMenu createPopupMenu(PluginView anchor)

通过 pluginUI.createPopupMenu(anchor) 创建弹出菜单,菜单将相对于锚点视图显示。

参数:

  • anchor - 锚点视图,菜单将相对于该视图显示

返回值: PluginPopupMenu 弹出菜单实例

示例:

// 在按钮点击时显示弹出菜单
button.setOnClickListener(view -> {
    PluginPopupMenu popupMenu = pluginUI.createPopupMenu(view);
    PluginMenu menu = popupMenu.getMenu();
    menu.add("选项1");
    menu.add("选项2");
    popupMenu.show();
});

# 菜单管理

# 添加菜单项

PluginMenu 提供三种重载形式的 add() 方法:

# add(CharSequence idAndTitle)

PluginMenuItem add(CharSequence idAndTitle)

添加菜单项,使用相同的值作为 ID 和标题。

参数:

  • idAndTitle - 同时作为 ID 和标题的文本

示例:

menu.add("菜单项1");  // ID 和标题都是 "菜单项1"
menu.add("{menu2}");  // ID 是 "{menu2}",标题会转为本地化文本

注意:如果 idAndTitle{key} 格式,ID 仍然是 {key}(不会转换),但标题会自动转换为本地化文本。

# add(String id, CharSequence title)

PluginMenuItem add(String id, CharSequence title)

添加菜单项,分别指定 ID 和标题。

参数:

  • id - 菜单项的唯一标识符
  • title - 菜单项的标题,支持 {key} 格式的本地化文本

示例:

menu.add("item1", "菜单项 1");
menu.add("item2", "{menu_title}");  // 标题使用本地化文本

# add(String id, CharSequence title, String groupId)

PluginMenuItem add(String id, CharSequence title, String groupId)

添加菜单项,指定 ID、标题和分组。

参数:

  • id - 菜单项的唯一标识符
  • title - 菜单项的标题,支持 {key} 格式的本地化文本
  • groupId - 菜单项所属的分组 ID,null 表示不分组

示例:

// 添加到同一分组
menu.add("option1", "选项1", "group1");
menu.add("option2", "选项2", "group1");
menu.add("option3", "选项3", "group1");

# 添加子菜单

PluginMenu 提供三种重载形式的 addSubMenu() 方法,用法与 add() 类似:

PluginSubMenu addSubMenu(CharSequence idAndTitle)
PluginSubMenu addSubMenu(String id, CharSequence title)
PluginSubMenu addSubMenu(String id, CharSequence title, String groupId)

示例:

// 创建子菜单
PluginSubMenu subMenu = menu.addSubMenu("sub1", "子菜单1");
subMenu.add("item1", "子项1");
subMenu.add("item2", "子项2");
subMenu.add("item3", "子项3");

// 为子菜单设置图标
subMenu.setIcon(MaterialIcons.get("folder"));

# 查找菜单项

# findItem

PluginMenuItem findItem(String id)

根据 ID 查找菜单项。

参数:

  • id - 菜单项 ID

返回值: 对应的菜单项,如果未找到则返回 null

示例:

PluginMenuItem item = menu.findItem("item1");
if (item != null) {
    item.setEnabled(false);  // 禁用该菜单项
}

# getItem

PluginMenuItem getItem(int index)

获取指定索引位置的菜单项。

参数:

  • index - 菜单项索引(从 0 开始)

返回值: 对应索引的菜单项

# size

int size()

获取菜单项总数。

返回值: 菜单中的项目数量

# 清空菜单

void clear()

清空菜单中的所有项目。

示例:

// 动态更新菜单
menu.clear();
menu.add("新选项1");
menu.add("新选项2");

# 菜单项配置

# 标题和图标

# setTitle

PluginMenuItem setTitle(CharSequence title)

设置菜单项的标题。

参数:

  • title - 菜单项标题,支持 {key} 格式的本地化文本

返回值: 当前菜单项实例,支持链式调用

示例:

menu.add("item1", "原标题")
    .setTitle("新标题");

# setIcon

PluginMenuItem setIcon(Drawable icon)

设置菜单项图标。

参数:

  • icon - 图标 Drawable,null 表示移除图标

返回值: 当前菜单项实例,支持链式调用

示例:

menu.add("search", "搜索")
    .setIcon(MaterialIcons.get("search"));

menu.add("copy", "复制")
    .setIcon(MaterialIcons.get("content_copy"));

# setIconTintList

PluginMenuItem setIconTintList(ColorStateList tint)

设置图标的着色列表。

参数:

  • tint - 颜色状态列表,null 表示移除着色

返回值: 当前菜单项实例,支持链式调用

# 可选中状态

# setCheckable

PluginMenuItem setCheckable(boolean checkable)

设置菜单项是否可选中。

参数:

  • checkable - 是否可选中

返回值: 当前菜单项实例,支持链式调用

# setChecked

PluginMenuItem setChecked(boolean checked)

设置菜单项的选中状态。

参数:

  • checked - 是否选中

返回值: 当前菜单项实例,支持链式调用

示例:

menu.add("option1", "选项1")
    .setCheckable(true)
    .setChecked(true);  // 默认选中

# 可见性和启用状态

# setVisible

PluginMenuItem setVisible(boolean visible)

设置菜单项的可见性。

参数:

  • visible - 是否可见

返回值: 当前菜单项实例,支持链式调用

# setEnabled

PluginMenuItem setEnabled(boolean enabled)

设置菜单项的启用状态。

参数:

  • enabled - 是否启用

返回值: 当前菜单项实例,支持链式调用

示例:

menu.add("disabled", "禁用选项")
    .setEnabled(false);  // 禁用该菜单项

menu.add("hidden", "隐藏选项")
    .setVisible(false);  // 隐藏该菜单项

# 子菜单

# hasSubMenu

boolean hasSubMenu()

判断菜单项是否包含子菜单。

返回值: true 表示包含子菜单,false 表示不包含

# getSubMenu

PluginSubMenu getSubMenu()

获取菜单项的子菜单。

返回值: 子菜单对象,如果没有子菜单则返回 null

# 分组管理

# 分组可选中

# setGroupCheckable

void setGroupCheckable(String groupId, boolean checkable, boolean exclusive)

设置分组的可选中状态,通常用于实现单选菜单。

参数:

  • groupId - 分组 ID
  • checkable - 是否可选中
  • exclusive - 是否为互斥选择(单选模式),设置为 true 时同一分组中只能选中一个

单选示例:

menu.add("item1", "选项1", "group1").setChecked(true);
menu.add("item2", "选项2", "group1").setChecked(false);
menu.add("item3", "选项3", "group1").setChecked(false);

// 设置为单选模式(互斥选择)
menu.setGroupCheckable("group1", true, true);

# 分组可见性

# setGroupVisible

void setGroupVisible(String groupId, boolean visible)

设置分组的可见性。

参数:

  • groupId - 分组 ID
  • visible - 是否可见

示例:

// 隐藏整个分组
menu.setGroupVisible("group1", false);

# 分组启用状态

# setGroupEnabled

void setGroupEnabled(String groupId, boolean enabled)

设置分组的启用状态。

参数:

  • groupId - 分组 ID
  • enabled - 是否启用

示例:

// 禁用整个分组
menu.setGroupEnabled("group1", false);

# 分组分割线

# setGroupDividerEnabled

void setGroupDividerEnabled(boolean groupDividerEnabled)

设置是否显示分组分隔线。不同分组之间会显示分割线。

参数:

  • groupDividerEnabled - 是否显示分隔线

示例:

menu.add("item1", "菜单项1");
menu.add("item2", "菜单项2");
menu.add("item3", "菜单项3", "group1");
menu.add("item4", "菜单项4", "group1");

// 启用分组分割线,未分组的菜单项和 group1 之间会显示分割线
menu.setGroupDividerEnabled(true);

# 显示和关闭

# show

void show()

显示弹出菜单。菜单将相对于锚点视图显示,具体位置取决于对齐方式设置。

# dismiss

void dismiss()

关闭弹出菜单。

# 对齐方式

# setGravity

void setGravity(int gravity)

设置弹出菜单的对齐方式。

参数:

  • gravity - 对齐方式,使用 Gravity 常量
    • Gravity.START - 开始对齐
    • Gravity.END - 结束对齐
    • Gravity.TOP - 顶部对齐
    • Gravity.BOTTOM - 底部对齐
    • 可以使用 | 组合多个方向

示例:

PluginPopupMenu popupMenu = pluginUI.createPopupMenu(anchorView);
popupMenu.setGravity(Gravity.END | Gravity.BOTTOM);
popupMenu.show();

# getGravity

int getGravity()

获取弹出菜单的对齐方式。

返回值: 对齐方式常量值

# 事件监听

# setOnMenuItemClickListener

void setOnMenuItemClickListener(PluginMenu.OnMenuItemClickListener listener)

设置菜单项点击监听器。

参数:

  • listener - 点击监听器,null 表示移除监听器

监听器接口:

interface OnMenuItemClickListener {
    boolean onMenuItemClick(PluginMenuItem item);
}

返回值:

  • true - 表示已处理该点击事件
  • false - 表示未处理

示例:

popupMenu.setOnMenuItemClickListener(item -> {
    String id = item.getItemId();
    String title = item.getTitle().toString();

    pluginUI.showToast("点击了:" + title);

    // 处理点击事件
    if ("copy".equals(id)) {
        // 复制操作
    } else if ("paste".equals(id)) {
        // 粘贴操作
    }

    return true;  // 已处理事件
});

# setOnDismissListener

void setOnDismissListener(OnDismissListener listener)

设置菜单关闭监听器。

参数:

  • listener - 关闭监听器,null 表示移除监听器

监听器接口:

interface OnDismissListener {
    void onDismiss(PluginPopupMenu menu);
}

示例:

popupMenu.setOnDismissListener(menu -> {
    pluginUI.showToast("菜单已关闭");
});

# 本地化文本支持

弹出菜单的标题支持 {key} 格式的本地化文本引用,但 ID 不会转换,始终保持原样。

示例:

// 菜单项标题使用本地化文本
menu.add("{copy}");           // ID 是 "{copy}",标题会转为本地化文本
menu.add("paste", "{paste}"); // ID 是 "paste",标题会转为本地化文本

// 子菜单标题
PluginSubMenu subMenu = menu.addSubMenu("{submenu_title}");
// 子菜单的 ID 是 "{submenu_title}",标题会转为本地化文本

assets/strings.mtl

copy: Copy
paste: Paste
submenu_title: More Options

assets/strings-zh-CN.mtl

copy: 复制
paste: 粘贴
submenu_title: 更多选项

提示:关于本地化文本的详细说明请查看 本地化文本

# 完整示例

# 基本菜单

button.setOnClickListener(view -> {
    PluginPopupMenu popupMenu = pluginUI.createPopupMenu(view);
    PluginMenu menu = popupMenu.getMenu();

    menu.add("{menu1}", "菜单1");
    menu.add("{menu2}");  // ID 是 "{menu2}",标题会转为本地化文本
    menu.add("菜单3");    // ID 和标题都是 "菜单3"

    popupMenu.setOnMenuItemClickListener(item -> {
        pluginUI.showToast("点击了:" + item.getTitle());
        return true;
    });

    popupMenu.show();
});

# 图标菜单

button.setOnClickListener(view -> {
    PluginPopupMenu popupMenu = pluginUI.createPopupMenu(view);
    PluginMenu menu = popupMenu.getMenu();

    menu.add("search", "搜索").setIcon(MaterialIcons.get("search"));
    menu.add("copy", "复制").setIcon(MaterialIcons.get("content_copy"));
    menu.add("cut", "剪切").setIcon(MaterialIcons.get("content_cut"));
    menu.add("delete", "删除").setIcon(MaterialIcons.get("delete"));

    popupMenu.setOnMenuItemClickListener(item -> {
        pluginUI.showToast("点击了:" + item.getTitle());
        return true;
    });

    popupMenu.show();
});

# 多选菜单

Set<String> checkedItems = new HashSet<>();

button.setOnClickListener(view -> {
    PluginPopupMenu popupMenu = pluginUI.createPopupMenu(view);
    PluginMenu menu = popupMenu.getMenu();

    menu.add("option1", "选项1")
        .setCheckable(true)
        .setChecked(checkedItems.contains("option1"));

    menu.add("option2", "选项2")
        .setCheckable(true)
        .setChecked(checkedItems.contains("option2"));

    menu.add("option3", "选项3")
        .setCheckable(true)
        .setChecked(checkedItems.contains("option3"));

    popupMenu.setOnMenuItemClickListener(item -> {
        // 切换选中状态
        if (item.isChecked()) {
            checkedItems.remove(item.getItemId());
            item.setChecked(false);
        } else {
            checkedItems.add(item.getItemId());
            item.setChecked(true);
        }
        return true;
    });

    popupMenu.show();
});

提示:多选菜单不需要使用 setGroupCheckable(),直接为每个菜单项设置 setCheckable(true) 即可。

# 单选菜单

String[] currentSelection = {"option1"};  // 当前选中的选项

button.setOnClickListener(view -> {
    PluginPopupMenu popupMenu = pluginUI.createPopupMenu(view);
    PluginMenu menu = popupMenu.getMenu();

    menu.add("option1", "选项1", "group1")
        .setChecked(currentSelection[0].equals("option1"));

    menu.add("option2", "选项2", "group1")
        .setChecked(currentSelection[0].equals("option2"));

    menu.add("option3", "选项3", "group1")
        .setChecked(currentSelection[0].equals("option3"));

    // 设置为单选模式(互斥选择)
    menu.setGroupCheckable("group1", true, true);

    popupMenu.setOnMenuItemClickListener(item -> {
        item.setChecked(true);
        currentSelection[0] = item.getItemId();
        pluginUI.showToast("选中了:" + item.getTitle());
        return true;
    });

    popupMenu.show();
});

# 多级菜单

button.setOnClickListener(view -> {
    PluginPopupMenu popupMenu = pluginUI.createPopupMenu(view);
    PluginMenu menu = popupMenu.getMenu();

    // 创建第一个子菜单
    PluginSubMenu subMenu1 = menu.addSubMenu("submenu1", "子菜单1");
    subMenu1.add("sub1_0", "子项0");
    subMenu1.add("sub1_1", "子项1");
    subMenu1.add("sub1_2", "子项2");

    // 创建第二个子菜单
    PluginSubMenu subMenu2 = menu.addSubMenu("submenu2", "子菜单2");
    subMenu2.add("sub2_0", "子项0");
    subMenu2.add("sub2_1", "子项1");
    subMenu2.add("sub2_2", "子项2");

    // 添加普通菜单项
    menu.add("menu3", "菜单项3");
    menu.add("menu4", "菜单项4");

    popupMenu.setOnMenuItemClickListener(item -> {
        if (!item.hasSubMenu()) {  // 避免展开子菜单时也弹出提示
            pluginUI.showToast("点击了:" + item.getTitle());
        }
        return true;
    });

    popupMenu.show();
});

# 分割线

button.setOnClickListener(view -> {
    PluginPopupMenu popupMenu = pluginUI.createPopupMenu(view);
    PluginMenu menu = popupMenu.getMenu();

    // 未分组的菜单项
    menu.add("menu1", "菜单项1");
    menu.add("menu2", "菜单项2");

    // 分组的菜单项
    menu.add("menu3", "菜单项3", "group1");
    menu.add("menu4", "菜单项4", "group1");

    // 启用分组分割线
    menu.setGroupDividerEnabled(true);

    popupMenu.setOnMenuItemClickListener(item -> {
        pluginUI.showToast("点击了:" + item.getTitle());
        return true;
    });

    popupMenu.show();
});

# 相关接口