如何在EDT外正确运行多任务,解决Codename One UI交互阻塞问题
问题描述
我创建了一个继承自Container的ImagenesButton类,在Button组件中依次显示图片。该容器显示在窗体底部,但当我尝试与其他窗体元素交互时,必须等待容器动画执行完毕。代码如下:
public class ImagenesButton extends Container { public ImagenesButton(Resources res) { this.res = res; this.setUIID("ContainerPromocion"); muestraImagenes(); } private void muestraImagenes() { this.setLayout(new BoxLayout(BoxLayout.X_AXIS)); Button btPromo = new Button("Cargando .....", "ContainerPromocion"); btPromo.getStyle().setAlignment(Button.CENTER); this.addComponent(btPromo); btPromo.addActionListener((e) -> { Display.getInstance().execute("https://www.youtube.com/watch?v=xLGwKBmqfzI&t=3s"); }); Display.getInstance().callSerially(() -> { while (true) { for (int i = 0; i < 5; i++) { btPromo.setIcon(res.getImage("p" + i + ".png").scaledWidth(this.getWidth())); Component btActual = this.getComponentAt(0); int aleatorio = aleatorio(5); if (aleatorio == 1) { this.replaceAndWait(btActual, btPromo, CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, true, 5000)); } if (aleatorio == 2) { this.replaceAndWait(btActual, btPromo, CommonTransitions.createCover(CommonTransitions.SLIDE_HORIZONTAL, true, 5000)); } if (aleatorio == 3) { this.replaceAndWait(btActual, btPromo, CommonTransitions.createFade(7000)); } if (aleatorio == 4) { this.replaceAndWait(btActual, btPromo, CommonTransitions.createCover(CommonTransitions.SLIDE_VERTICAL, true, 5000)); } if (aleatorio == 5) { this.replaceAndWait(btActual, btPromo, CommonTransitions.createSlide(CommonTransitions.SLIDE_HORIZONTAL, true, 5000)); } } } }); } private int aleatorio(int x) { int num = 0; Random r = new Random(); num = r.nextInt(x) + 1; return num; } }
请问如何让UI交互与动画同时运行?
解决方案
问题核心在于replaceAndWait方法和无限循环的使用:
replaceAndWait是阻塞式方法,会占用事件调度线程(EDT)直到动画完成,导致其他UI交互无法响应。- 外层的
while(true)循环在EDT中执行,彻底阻塞线程,所有UI操作都必须等待循环结束(永远不会结束)。
修改思路是使用非阻塞的replace方法,通过回调触发下一次动画,让EDT能处理其他交互事件:
public class ImagenesButton extends Container { private Resources res; private Button btPromo; private int currentImageIndex = 0; public ImagenesButton(Resources res) { this.res = res; this.setUIID("ContainerPromocion"); initComponent(); startImageCycle(); } private void initComponent() { this.setLayout(new BoxLayout(BoxLayout.X_AXIS)); btPromo = new Button("Cargando .....", "ContainerPromocion"); btPromo.getStyle().setAlignment(Button.CENTER); this.addComponent(btPromo); btPromo.addActionListener((e) -> { Display.getInstance().execute("https://www.youtube.com/watch?v=xLGwKBmqfzI&t=3s"); }); } private void startImageCycle() { Display.getInstance().callSerially(this::showNextImage); } private void showNextImage() { // 更新当前图片 btPromo.setIcon(res.getImage("p" + currentImageIndex + ".png").scaledWidth(this.getWidth())); Component btActual = this.getComponentAt(0); int aleatorio = aleatorio(5); Transition transition = switch(aleatorio) { case 1 -> CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, true, 5000); case 2 -> CommonTransitions.createCover(CommonTransitions.SLIDE_HORIZONTAL, true, 5000); case 3 -> CommonTransitions.createFade(7000); case 4 -> CommonTransitions.createCover(CommonTransitions.SLIDE_VERTICAL, true, 5000); case 5 -> CommonTransitions.createSlide(CommonTransitions.SLIDE_HORIZONTAL, true, 5000); default -> CommonTransitions.createFade(5000); }; // 使用非阻塞的replace方法,动画结束后触发下一次循环 this.replace(btActual, btPromo, transition, () -> { currentImageIndex = (currentImageIndex + 1) % 5; // 延迟一小段时间后开始下一次动画(可选,避免连续切换) Display.getInstance().scheduleTask(this::showNextImage, 1000); }); } private int aleatorio(int x) { Random r = new Random(); return r.nextInt(x) + 1; } }
关键修改点:
- 移除
while(true)和for循环,改用递归回调触发下一次动画,让EDT能在动画间隙处理其他事件。 - 将
replaceAndWait替换为replace,利用其动画完成后的回调函数调度下一次图片切换。 - 用
switch语句替代多个if,简化代码结构。 - 增加
currentImageIndex追踪当前显示图片,实现循环轮播。
修改后动画异步执行,EDT不会被阻塞,其他UI元素可正常响应交互。
内容的提问来源于stack exchange,提问作者Rubén Villarreal
相关产品推荐
相关产品推荐

