跳到内容
CrossFly 是 100% 开源在 GitHub 关注我们
CrossFlyCrossFly
开始使用

本页面由葡萄牙语机器翻译,可能存在错误。葡萄牙语版本为准。

你的第一个应用:任务清单

我们将构建一个能打开、添加、导航并持久化的应用——一步一步,按需引入每一个 组成部分。

完整示例(复制、粘贴、运行)

一个精简、自包含的核心版本:一个 TextField + Button,向虚拟列表添加项目。将其 粘贴到一个 MainWindow 类中,并用 gradlew :lwjgl3:run 运行。

package com.exemplo;

import java.util.ArrayList;
import java.util.List;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

public class MainWindow extends Window
{
   private final List<String> tarefas = new ArrayList<>();
   private TextField campo;
   private VirtualListView lista;

   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // 输入行:字段(吸收宽度)+ 按钮(自然大小)
      Component entrada = new Component();
      entrada.setLayout(new HorizontalLayout());
      campo = new TextField().caption("Nova tarefa");
      Button add = new Button("Adicionar");
      entrada.add(LAY_FILL, campo);
      entrada.add(LAY_AUTO, add);
      add(LAY_AUTO, entrada);

      // 虚拟列表:每个单元格为字体高度的 3 倍
      lista = new VirtualListView(Relative.ofFont(3f), new DataAdapter()
      {
         @Override public int getCount() { return tarefas.size(); }
         @Override public Component createView() { return new Label(""); }
         @Override public void bindView(Component view, int index)   // 先 view,再 index
         {
            ((Label) view).setText(tarefas.get(index));   // 直接强制转换
         }
      });
      add(LAY_FILL, lista);

      add.onAction(c -> adicionar());
   }

   private void adicionar()
   {
      String texto = campo.getText().trim();
      if (texto.isEmpty())
      {
         Hint.show("Digite uma tarefa", 1500);   // 始终 2 个参数
         return;
      }
      tarefas.add(texto);
      campo.clearText();
      lista.notifyDataChanged();   // 重新测量并重绘列表
   }
}

下面的片段只展示核心内容(放进 onShow() 里的部分)。要运行的话,请把它们放入上面的 MainWindow 骨架中。

1. 界面骨架(VerticalLayout)

public class TarefasScreen extends Window {
   private final List<Tarefa> tarefas = new ArrayList<>();   // 状态保存在 Window 的字段中
   private Component listaBox;
   private TextField campo;

   @Override public void onShow() throws Exception {
      super.onShow();
      setLayout(new VerticalLayout());

      // 输入行(字段 + 按钮)
      Component entrada = new Component();
      entrada.setLayout(new HorizontalLayout());
      campo = new TextField().caption("Nova tarefa");
      Button add = new Button("Adicionar");
      entrada.add(LAY_FILL, campo);       // 字段吸收宽度
      entrada.add(LAY_AUTO, add);         // 按钮为内容大小
      add(LAY_AUTO, entrada);

      // 列表区域,吸收剩余高度
      listaBox = new Component();
      listaBox.setLayout(new VerticalLayout());
      add(LAY_FILL, listaBox);

      add.onAction(c -> adicionar());
      carregar();                          // 从数据库填充
   }
}

2. 添加并反映到 UI(无响应式绑定)

private void adicionar() {
   String texto = campo.getText().trim();
   if (texto.isEmpty()) return;
   Tarefa t = new Tarefa(texto);
   tarefas.add(t);
   salvar(t);              // 持久化(步骤 4)
   campo.clearText();
   renderizarLista();      // 你手动更新 UI
}

private void renderizarLista() {
   listaBox.removeAll();
   for (Tarefa t : tarefas) {
      Button item = new Button(t.titulo);
      item.onAction(c -> new DetalheScreen(t).show());   // 导航(步骤 3)
      listaBox.add(LAY_AUTO, item);
   }
   listaBox.setValidLayout(false);   // 重新测量布局
   Component.repaint();              // 请求新的一帧
}

3. push/pop 导航

public class DetalheScreen extends Window {
   private final Tarefa tarefa;
   public DetalheScreen(Tarefa t) { this.tarefa = t; }   // 通过构造函数传入数据
   @Override public void onShow() throws Exception {
      super.onShow();
      setLayout(new VerticalLayout());
      add(LAY_AUTO, new Label(tarefa.titulo));
      Button voltar = new Button("Voltar");
      voltar.onAction(c -> hide());     // pop
      add(LAY_AUTO, voltar);
   }
}

4. SQLite 持久化(在 paint thread 之外)

private void carregar() {
   new Thread(() -> {
      List<Tarefa> carregadas = new ArrayList<>();
      try (SqliteDb db = SqliteDb.open("tarefas.db")) {
         db.exec("CREATE TABLE IF NOT EXISTS tarefa(id INTEGER PRIMARY KEY, titulo TEXT)");
         try (SqliteCursor c = db.query("SELECT id, titulo FROM tarefa ORDER BY id")) {
            while (c.next())                                   // getter 从 0 开始
               carregadas.add(new Tarefa(c.getInt(0), c.getString(1)));
         }
      } catch (Exception e) { Debug.handle(e); }
      Component.onUI(() -> {                                   // 返回 paint thread
         tarefas.clear(); tarefas.addAll(carregadas);
         renderizarLista();
      });
   }).start();
}

private void salvar(Tarefa t) {
   new Thread(() -> {
      try (SqliteDb db = SqliteDb.open("tarefas.db")) {
         db.execute("INSERT INTO tarefa(titulo) VALUES(?)", t.titulo);   // bind 从 1 开始
      } catch (Exception e) { Debug.handle(e); }
   }).start();
}

SQLite 的两条黄金规则: 参数绑定是 1-based,游标的 getter 是 0-based;并且数据库运行在 paint thread 之外,结果通过 Component.onUI(...) 返回。

你构建了什么

一个能打开、添加、导航并持久化的应用——你也已经见到了 learn/ 课程将深入讲解的那些支柱。

下一步

→ 项目剖析

另请参阅

这个页面有帮助吗?