如何在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
相关产品推荐
相关产品推荐

