You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在CN1应用中使用Glass Pane,仅开放特定UI组件供用户输入?

在Codename One中实现带交互白名单的Glass Pane引导教程

完全可以实现这个需求——通过自定义GlassPane的事件拦截逻辑,就能精准控制哪些组件可以接收用户输入,同时用GlassPane绘制引导元素。以下是具体实现方案:

1. 自定义GlassPane拦截非目标组件的交互

默认GlassPane会拦截所有触摸/点击事件,我们需要重写它的事件处理方法,只把事件传递给指定的组件:

public class TutorialGlassPane extends Container {
    private final List<Component> allowedComponents = new ArrayList<>();

    public TutorialGlassPane() {
        setUIID("GlassPane");
        setLayout(new LayeredLayout());
        setVisible(false);
        // 设置半透明背景,突出引导层
        getAllStyles().setBgColor(0x80000000);
        getAllStyles().setBgTransparency(128);
    }

    // 添加允许交互的组件
    public void addAllowedComponent(Component cmp) {
        allowedComponents.add(cmp);
    }

    // 清空允许列表
    public void clearAllowedComponents() {
        allowedComponents.clear();
    }

    @Override
    public void pointerPressed(int x, int y) {
        Container parent = getParent();
        // 转换坐标到父容器(表单)的坐标系
        int parentX = x - getAbsoluteX() + parent.getAbsoluteX();
        int parentY = y - getAbsoluteY() + parent.getAbsoluteY();
        Component target = parent.getComponentAt(parentX, parentY);
        
        // 仅当目标组件在允许列表中时,传递事件
        if (allowedComponents.contains(target)) {
            target.pointerPressed(parentX - target.getAbsoluteX(), parentY - target.getAbsoluteY());
        } else {
            // 拦截非目标组件的点击事件
            super.pointerPressed(x, y);
        }
    }

    // 同理重写释放和拖拽事件,保证交互完整
    @Override
    public void pointerReleased(int x, int y) {
        Container parent = getParent();
        int parentX = x - getAbsoluteX() + parent.getAbsoluteX();
        int parentY = y - getAbsoluteY() + parent.getAbsoluteY();
        Component target = parent.getComponentAt(parentX, parentY);
        
        if (allowedComponents.contains(target)) {
            target.pointerReleased(parentX - target.getAbsoluteX(), parentY - target.getAbsoluteY());
        } else {
            super.pointerReleased(x, y);
        }
    }

    @Override
    public void pointerDragged(int x, int y) {
        Container parent = getParent();
        int parentX = x - getAbsoluteX() + parent.getAbsoluteX();
        int parentY = y - getAbsoluteY() + parent.getAbsoluteY();
        Component target = parent.getComponentAt(parentX, parentY);
        
        if (allowedComponents.contains(target)) {
            target.pointerDragged(parentX - target.getAbsoluteX(), parentY - target.getAbsoluteY());
        } else {
            super.pointerDragged(x, y);
        }
    }
}

2. 集成到表单并构建引导流程

在目标表单中初始化自定义GlassPane,添加引导元素(如高亮框、提示文本),并逐步切换允许交互的组件:

Form tutorialForm = new Form("必填信息填写", new BoxLayout(BoxLayout.Y_AXIS));

// 创建表单组件
TextField nameField = new TextField("", "姓名(必填)");
TextField emailField = new TextField("", "邮箱(必填)");
Button submitBtn = new Button("提交");

tutorialForm.addAll(nameField, emailField, submitBtn);

// 初始化引导用GlassPane
TutorialGlassPane tutorialPane = new TutorialGlassPane();
tutorialForm.setGlassPane(tutorialPane);

// 第一步:引导用户填写姓名,仅允许姓名输入框交互
startNameStep(tutorialPane, nameField, emailField);

tutorialForm.show();

分步引导的具体实现

private void startNameStep(TutorialGlassPane pane, TextField nameField, TextField emailField) {
    pane.clearAllowedComponents();
    pane.addAllowedComponent(nameField);
    pane.removeAll();

    // 添加姓名框高亮引导
    Label nameHighlight = new Label();
    nameHighlight.getAllStyles().setBorder(Border.createLineBorder(3, 0x00CCFF));
    nameHighlight.setSize(nameField.getWidth(), nameField.getHeight());
    pane.add(nameHighlight);

    // 添加提示文本
    Label tip = new Label("请先填写您的姓名");
    tip.getAllStyles().setFgColor(0xFFFFFF);
    tip.getAllStyles().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
    pane.add(tip);

    // 定位引导元素
    pane.addShowListener(e -> {
        nameHighlight.setX(nameField.getAbsoluteX() - pane.getAbsoluteX());
        nameHighlight.setY(nameField.getAbsoluteY() - pane.getAbsoluteY());
        tip.setX(nameHighlight.getX() + 10);
        tip.setY(nameHighlight.getY() + nameHighlight.getHeight() + 10);
    });

    pane.setVisible(true);
    pane.revalidate();

    // 姓名填写完成后切换到下一步
    nameField.addDataChangedListener((type, index) -> {
        if (!nameField.getText().trim().isEmpty()) {
            startEmailStep(pane, emailField);
        }
    });
}

private void startEmailStep(TutorialGlassPane pane, TextField emailField) {
    pane.clearAllowedComponents();
    pane.addAllowedComponent(emailField);
    pane.removeAll();

    // 邮箱框高亮引导
    Label emailHighlight = new Label();
    emailHighlight.getAllStyles().setBorder(Border.createLineBorder(3, 0x00CCFF));
    emailHighlight.setSize(emailField.getWidth(), emailField.getHeight());
    pane.add(emailHighlight);

    Label tip = new Label("请填写您的邮箱");
    tip.getAllStyles().setFgColor(0xFFFFFF);
    tip.getAllStyles().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
    pane.add(tip);

    pane.addShowListener(e -> {
        emailHighlight.setX(emailField.getAbsoluteX() - pane.getAbsoluteX());
        emailHighlight.setY(emailField.getAbsoluteY() - pane.getAbsoluteY());
        tip.setX(emailHighlight.getX() + 10);
        tip.setY(emailHighlight.getY() + emailHighlight.getHeight() + 10);
    });

    pane.revalidate();

    // 邮箱填写完成后隐藏引导层
    emailField.addDataChangedListener((type, index) -> {
        if (!emailField.getText().trim().isEmpty()) {
            pane.setVisible(false);
        }
    });
}

3. 额外注意事项

  • 对于键盘输入(如TextField),只要组件处于enabled状态,GlassPane不会拦截键盘事件,所以无需额外处理。
  • 引导结束后直接隐藏GlassPane即可恢复所有组件的交互,不需要修改原有组件的enabled状态。
  • 可以用SVG、自定义绘制等方式替换简单的高亮框,让引导元素更直观。

内容的提问来源于stack exchange,提问作者Daniel Tsai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 03:32:47