布局列表框使用指导

← 返回布局列表框组件参考 · 全部使用指导

布局列表框使用指导

可见组件,把「界面设计」里拖入的布局当作行模板:运行时按数据项克隆模板里的组件生成每一行, 滚动时只创建可见行并回收复用,适合显示结构复杂、数量大的列表。

与列表显示框的区别:列表显示框只能在固定布局里显示文本和图像; 布局列表框的行内容完全由你自己的布局决定,行内每个组件(标签、图像、按钮等)都能按数据设置属性。

除了纵向列表,它还内置了现代列表的常用能力:更换数据时只刷新有变化的行(带增删动画)、 网格与横向排列、行间距、数据为空时的空提示文本、滚动到底部触发加载更多、 滚动到指定行——全部开箱即用,不用写一行额外代码。

注意:该组件为新增组件,联机调试需更新到包含它的 AI 伴侣版本,打包 APK 不受影响。

实际案例:中国大学排名及特色(完整 AIA、APK 和专项测试表)

循序渐进实战:从三行商品到搜索、收藏、购物车、聊天与网络分页

快速上手

  1. 拖一个布局列表框到屏幕;
  2. 往布局列表框里拖一个竖直布局(这就是行模板),再往里面拖标签、图像、按钮等组件。 模板里组件的组件名(如 标签1)就是行内组件的标签——字典自动绑定、取组件(标签)(GetComponent)都靠它;
  3. 把字典列表设给数据(Data)属性,键与组件名相同的项会自动填进组件的 文本(或图片)属性:
when Screen1.Initialize() {
  布局列表框1.Data = list(
    { "标签1": "张三", "标签2": "13800000001" },
    { "标签1": "李四", "标签2": "13800000002" }
  )
}

每行的标签1、标签2 已按字典自动填充。需要更灵活的行逻辑时用遍历数据(索引,值)(ForEachData)事件(见使用示例)。

字典自动绑定规则:数据项是字典时,键与模板内组件名相同的项自动写入——组件有文本属性的写文本 (标签、按钮等),没有文本但有图片属性的写图片(图像);类型/type 键只用于多模板路由, 不参与绑定;其余键忽略,需要时在遍历数据事件里用取行数据(GetCurrentData)自行读取; 值会转成文本再写入,某一项写入失败不影响其余项。

实际案例:中国大学排名及特色(下拉自动加载更多)

用真实高校资料做一个可搜索、收藏和切换视图的大学目录,同时专项检查布局列表框的自动绑定、行内按钮、长按、下拉自动加载更多、回收复用与空状态。

难度 准备时间 测试平台
入门,导入工程即可开始 约 5 分钟 Android APK / 包含布局列表框的最新版 AI 伴侣

第 1 步:导入完整工程

下载 大学排名与特色 AIA,在项目菜单选择「导入项目(.aia)」。 也可以直接安装 本次工程编译的 Android APK 对照测试。 工程包含完整界面、代码块、20 所高校的数据和 20 张校徽,不需要接口密钥或运行时网络请求。 Web1 只负责解析本地 JSON。

下载文件名应为 UniversityLayoutList.aia,直接导入即可。AIA 内部是 ZIP 容器;如果此前下载成了 .zip,把后缀改回 .aia,不要解压。页面下载链接已指定 .aia 文件名,并携带当前文件版本号。

导入后可立即按这个顺序测试自动加载:

  1. 首次打开显示首批高校,普通手机一般为 10 所,大屏会多显示几所。
  2. 手指向上滑动列表,滚到当前最后一所;自动追加下一批,每批最多 5 所,直到显示 20 所并提示「已全部加载」。
  3. 切换「双列卡片」后继续滚动,自动加载方式相同,已加载进度保留;搜索「北京」时 6 所结果会一次显示全部。
准备项 数量 说明
AIA 工程 1 导入后直接打开 Screen1;不需要额外扩展
Android 手机 1 伴侣必须包含新增的布局列表框;也可安装上面的 APK
universities.json 1 已在素材中,使用文件组件读取 //universities.json
校徽 PNG 20 已在素材中,文件名为 university-01.png 至 university-20.png

中途验证:设计器中能看到两个布局列表框及各自的行模板,素材列表中能看到 JSON 和校徽。设计器只展示模板,完整 20 条数据在运行时加载。

排名、地区、类型和总分来自 2026 软科中国大学排名主榜,本例取前 20 所。清华和北大的特色文字分别参考 清华本科生教育 与 北大简介;其余学校摘选 教育部 2022 年第二轮“双一流”建设学科名单。特色方向用于展示学校特点,不是 2026 年专业排名。

采集日期为 2026-10-03。逐校信息见 数据来源表,原始资料见 universities.json。详情弹窗也保留了每所大学的来源地址。

第 2 步:了解界面和模板

AIA 已搭好以下组件。需要自己重做时,以 完整界面配置 中的名称和属性为准。

组件类型 命名 关键属性或用途
屏幕 Screen1 可滚动 = 假,尺寸调整 = 响应式
文本输入框 文本输入框_搜索 搜索大学名称、地区或特色方向
复选框 复选框_收藏 只看收藏
按钮 按钮_视图、按钮_恢复 切换图文列表/双列卡片、恢复全部
按钮 按钮_顶部、按钮_刷新、按钮_清空 回顶部、强制刷新、清空显示;加载更多由列表触底自动触发
标签 标签_状态、标签_事件 显示数量、收藏数及绑定/点击记录
布局列表框 布局列表框_列表 宽高填充父级,列数 1,方向纵向,行间距 10
布局列表框 布局列表框_网格 宽高填充父级,列数 2,方向纵向,行间距 8,初始不可见
竖直布局 竖直布局_模板_列表、竖直布局_模板_网格 分别直接放入对应列表框,宽填充父级、高自动、背景白色
文件、Web、本地数据库、对话框 文件管理器1、Web1、微数据库1、对话框1 读取素材、解析 JSON、保存收藏、显示详情

图文列表模板结构如下;网格模板把校徽和排名放在顶部,其余文字依次排列。所有网格组件的名称后缀改成 _网格,避免同屏重名。

布局列表框_列表
└─ 竖直布局_模板_列表
   ├─ 水平布局_图文_列表
   │  ├─ 竖直布局_校徽_列表
   │  │  ├─ 图像_校徽_列表(64 × 64,缩放图片适应)
   │  │  └─ 标签_排名_列表
   │  └─ 竖直布局_文字_列表
   │     ├─ 标签_名_列表
   │     ├─ 标签_地区_列表
   │     ├─ 标签_特色_列表
   │     └─ 标签_分数_列表
   └─ 水平布局_操作_列表
      ├─ 按钮_详情_列表
      └─ 按钮_收藏_列表

模板需要保持可见。运行时隐藏的是另一个列表框,不能把直接子模板隐藏。模板中的两个按钮宽度都设「填充父级」,由水平布局分配剩余宽度;网格行内不要用「宽度 50%」,百分比以屏幕宽度计算,会超出卡片。

列数是设计器属性,所以本例预先准备一列、两列的两个列表框,通过可见性切换。两者使用同一批数据、同一份收藏编号,各自拥有独立模板。

中途验证:连接伴侣或启动 APK 后,先看到清华、北大、浙大等高校,普通手机首批显示 10 所;大屏会根据屏幕高度多显示几所,确保双列卡片也能滚动。状态提示「向上滑动,自动加载更多」。点「双列卡片」后看到同一批高校的两列卡片,点「图文列表」切回。

本例采用下拉自动加载更多:手指向上滑动列表,查看下方内容;到达当前末尾后自动追加下一批。它使用「滚到底部」事件,不是从顶部向下拖动的刷新操作。普通手机首批 10 所,后续每批 5 所;大屏首批增加,筛选结果不足首批数量时直接全部显示。两种视图共享已加载数量,切换后保留进度。

第 3 步:完整代码块

下面是工程实际使用的完整逻辑,可以在预览中查看或复制积木。自行搭建时先按上表创建组件,上传 JSON 和校徽,再粘贴这组代码块;已有同名事件或全局变量时先清理,避免重复。

数据字典中,标签_名_列表、图像_校徽_列表 等键与模板组件名对应,组件自动填写文字或图片。额外的 大学 键保留完整原始记录,点击时直接读取,避免把筛选后的行号误当原排名。

global 大学数据 = []
global 筛选结果 = []
global 收藏编号 = []
global 已加载 = 0
global 绑定次数 = 0
global 正在加载 = false

procedure 更新状态() {
  标签_状态.Text = join("已显示 ", 已加载, " / ", length(筛选结果), " 所 · 收藏 ", length(收藏编号), " 所")
  if length(筛选结果) > 0 {
    标签_状态.Text = join(标签_状态.Text, choose(已加载 < length(筛选结果), " · 向上滑动,自动加载更多", " · 已全部加载"))
  }
}

function 制作行(大学) {
  return makeDict(pair("大学", 大学), pair("标签_名_列表", dictLookup(大学, "name", "")), pair("标签_名_网格", dictLookup(大学, "name", "")), pair("图像_校徽_列表", dictLookup(大学, "logo", "")), pair("图像_校徽_网格", dictLookup(大学, "logo", "")), pair("标签_排名_列表", join("#", dictLookup(大学, "rank", 0))), pair("标签_排名_网格", join("#", dictLookup(大学, "rank", 0))), pair("标签_地区_列表", join(dictLookup(大学, "region", ""), " · ", dictLookup(大学, "category", ""))), pair("标签_地区_网格", join(dictLookup(大学, "region", ""), " · ", dictLookup(大学, "category", ""))), pair("标签_特色_列表", dictLookup(大学, "feature", "")), pair("标签_特色_网格", dictLookup(大学, "feature", "")), pair("标签_分数_列表", join("2026主榜 · 总分 ", dictLookup(大学, "score", 0))), pair("标签_分数_网格", join("2026主榜 · 总分 ", dictLookup(大学, "score", 0))))
}

procedure 筛选大学() {
  筛选结果 = []
  var 关键词 = trim(文本输入框_搜索.Text)
  foreach 大学 in 大学数据 {
    var 命中 = 关键词 == "" or textContains(join(dictLookup(大学, "name", ""), " ", dictLookup(大学, "region", ""), " ", dictLookup(大学, "feature", "")), 关键词)
    var 满足收藏 = not (复选框_收藏.Checked) or contains(收藏编号, dictLookup(大学, "id", ""))
    if 命中 and 满足收藏 {
      listAdd(筛选结果, 大学)
    }
  }
  // 首批兼顾两列卡片与大屏;每张卡片约 180 高,确保有足够内容可滚动。
  var 首批数量 = max(10, 2 * ceil(Screen1.Height / 180))
  已加载 = min(首批数量, length(筛选结果))
  var 行列表 = []
  for 行号 = 1 to 已加载 {
    listAdd(行列表, 制作行(筛选结果[行号]))
  }
  布局列表框_列表.Data = 行列表
  布局列表框_网格.Data = 行列表
  更新状态()
}

procedure 加载下一页() {
  if not (正在加载) and 已加载 > 0 and 已加载 < length(筛选结果) {
    正在加载 = true
    var 下一批 = []
    var 结束位置 = min(已加载 + 5, length(筛选结果))
    for 行号 = 已加载 + 1 to 结束位置 {
      listAdd(下一批, 制作行(筛选结果[行号]))
    }
    已加载 = 结束位置
    布局列表框_列表.AppendData(下一批)
    布局列表框_网格.AppendData(下一批)
    更新状态()
    正在加载 = false
  }
}

procedure 显示详情(行值, 当前索引) {
  var 大学 = dictLookup(行值, "大学", makeDict())
  标签_事件.Text = join("点击结果第", 当前索引, "行:", dictLookup(大学, "name", ""))
  对话框1.ShowMessageDialog(join("2026软科主榜第 ", dictLookup(大学, "rank", 0), " 名,总分 ", dictLookup(大学, "score", 0), "\n地区:", dictLookup(大学, "region", ""), "\n特色方向:", dictLookup(大学, "feature", ""), "\n特色来源:", dictLookup(大学, "featureNote", ""), "\n\n排名:", dictLookup(大学, "rankSource", ""), "\n特色:", dictLookup(大学, "featureSource", "")), dictLookup(大学, "name", "大学详情"), "知道了")
}

procedure 切换收藏(行值) {
  var 大学 = dictLookup(行值, "大学", makeDict())
  var 编号 = dictLookup(大学, "id", "")
  if contains(收藏编号, 编号) {
    listRemove(收藏编号, indexOf(收藏编号, 编号))
  } else {
    listAdd(收藏编号, 编号)
  }
  微数据库1.StoreValue("university-favorites-v1", 收藏编号)
  if 复选框_收藏.Checked {
    筛选大学()
  } else {
    布局列表框_列表.Refresh()
    布局列表框_网格.Refresh()
    更新状态()
  }
}

procedure 绑定当前行(列表组件, 后缀, 行值) {
  var 大学 = dictLookup(行值, "大学", makeDict())
  var 排名组件 = any LayoutListView(列表组件).GetComponent(join("标签_排名_", 后缀))
  var 收藏组件 = any LayoutListView(列表组件).GetComponent(join("按钮_收藏_", 后缀))
  var 已收藏 = contains(收藏编号, dictLookup(大学, "id", ""))
  any Label(排名组件).TextColor = choose(dictLookup(大学, "rank", 0) <= 3, colorFromHex("#C26B1B"), colorFromHex("#36547A"))
  any Button(收藏组件).Text = choose(已收藏, "已收藏", "收藏")
  any Button(收藏组件).BackgroundColor = choose(已收藏, colorFromHex("#FFE2B4"), colorFromHex("#E8EEF8"))
  绑定次数 = 绑定次数 + 1
  标签_事件.Text = join("累计绑定 ", 绑定次数, " 次 · 当前:", dictLookup(大学, "name", ""))
}

when Screen1.Initialize() {
  收藏编号 = 微数据库1.GetValue("university-favorites-v1", [])
  文件管理器1.ReadFrom("//universities.json")
}

when 文件管理器1.GotText(文本) {
  大学数据 = Web1.JsonTextDecodeWithDictionaries(文本)
  筛选大学()
}

when 文本输入框_搜索.TextChanged() { 筛选大学() }
when 复选框_收藏.Changed() { 筛选大学() }

when 按钮_恢复.Click() {
  文本输入框_搜索.Text = ""
  复选框_收藏.Checked = false
  筛选大学()
}

when 按钮_视图.Click() {
  var 切到网格 = 布局列表框_列表.Visible
  布局列表框_列表.Visible = not (切到网格)
  布局列表框_网格.Visible = 切到网格
  按钮_视图.Text = choose(切到网格, "图文列表", "双列卡片")
}

when 按钮_顶部.Click() {
  if 已加载 > 0 {
    if 布局列表框_列表.Visible { 布局列表框_列表.ScrollToIndex(1) }
    else { 布局列表框_网格.ScrollToIndex(1) }
  }
}

when 按钮_刷新.Click() {
  布局列表框_列表.Refresh()
  布局列表框_网格.Refresh()
}

when 按钮_清空.Click() {
  已加载 = 0
  布局列表框_列表.ClearData()
  布局列表框_网格.ClearData()
  标签_状态.Text = "已清空显示;点恢复全部可重新显示,收藏保留。"
}

when 布局列表框_列表.ReachEnd() {
  if 布局列表框_列表.Visible { 加载下一页() }
}
when 布局列表框_网格.ReachEnd() {
  if 布局列表框_网格.Visible { 加载下一页() }
}
when 布局列表框_列表.ForEachData(索引, 值) { 绑定当前行(布局列表框_列表, "列表", 值) }
when 布局列表框_网格.ForEachData(索引, 值) { 绑定当前行(布局列表框_网格, "网格", 值) }
when 布局列表框_列表.ItemClick(索引, 值) { 显示详情(值, 索引) }
when 布局列表框_网格.ItemClick(索引, 值) { 显示详情(值, 索引) }
when 布局列表框_列表.ItemLongClick(索引, 值) { 切换收藏(值) }
when 布局列表框_网格.ItemLongClick(索引, 值) { 切换收藏(值) }

when 布局列表框_列表.ComponentClick(标签, 索引, 值) {
  if 标签 == "按钮_收藏_列表" { 切换收藏(值) }
  else { 显示详情(值, 索引) }
}

when 布局列表框_网格.ComponentClick(标签, 索引, 值) {
  if 标签 == "按钮_收藏_网格" { 切换收藏(值) }
  else { 显示详情(值, 索引) }
}

when Screen1.ErrorOccurred(组件, 函数名称, 错误编号, 消息) {
  标签_状态.Text = join("错误 ", 错误编号, ":", 消息)
}

中途验证:输入「口腔医学」应只剩武汉大学和四川大学;点「查看特色」能看到该校排名、总分和来源。点「收藏」后按钮变成「已收藏」;滚走再滚回,收藏样式仍对应正确的大学。

收藏使用固定学校编号保存到本地数据库,不使用列表行号。筛选改变行顺序后不会把收藏移到别的学校。行内按钮通过列表框的「组件点击」事件处理,不能只给设计器里的模板按钮写普通点击事件。每次「遍历数据」都重新设置收藏文字、背景和排名颜色,防止行回收后串色。

第 4 步:按表专项测试

建议先安装 APK 验证一遍,再用同一工程连接 AI 伴侣对照。下表是预期结果,真机与伴侣尚未代替用户完成实测。

操作 预期结果 覆盖能力
首次打开 普通手机首批 10 所,大屏会多显示几所;校徽、名称、排名、地区、特色和总分完整 读取素材、字典自动绑定
向上滑动列表,滚到当前最后一所 自动追加 5 所;普通手机数量从 10/20 到 15/20,再到 20/20,提示「已全部加载」 ReachEnd、AppendData、分页边界
恢复全部,切到双列卡片,再向上滑到末尾 自动追加,每批最多 5 所,两种视图数据同步;达到 20 所后继续滑动不重复追加 双列触底自动加载、无重复、隐藏视图不触发加载
搜索「北京」 匹配 6 所,一次显示全部,提示「已全部加载」,无需手动加载 筛选结果不足首批数量时全部显示
搜索「上海」 上海交通大学、复旦大学、同济大学,共 3 所 短列表
搜索「湖北」 武汉大学、华中科技大学,共 2 所 地区筛选
搜索「口腔医学」 武汉大学、四川大学,共 2 所,原排名分别为 8、16 特色筛选、当前行号与排名区分
搜索「不存在的大学XYZ」 显示空提示;不追加数据,恢复后数据重新出现 空列表及恢复
点行空白/名称/查看特色 显示被点大学的详情;结果索引按当前筛选计算 ItemClick、ComponentClick
点收藏按钮,或长按行空白 收藏状态切换;详情不会代替收藏操作 子组件点击、ItemLongClick
只收藏武汉和川大,勾「只看收藏」 只显示这两所;取消一个收藏后该行消失 外部状态刷新、重新筛选
取消收藏筛选,反复上下滚动 各学校的收藏文字、背景和前 3 名颜色正确,不串行 克隆行回收复用
切换图文列表/双列卡片 两种视图大学数据和收藏一致,卡片内容不越界 网格、独立模板、显隐切换
点「强制刷新」 当前可见行重新绑定,绑定计数增加,收藏不丢失 Refresh;绑定次数不等于学校数量
加载全部后滚到末尾,点「回顶部」 当前可见视图回到第一行 ScrollToIndex;索引从 1 开始
点「清空显示」,再点「恢复全部」 清空提示出现,再恢复首批数据,收藏保留 ClearData、重新设置 Data
关闭后重新打开 APK 收藏仍保留,首次数据重新加载 本地数据库持久化
分别在两个视图横竖屏切换 列表可继续滚动,内容不消失,收藏与详情仍正确 尺寸变化、克隆布局重新测量

AI 伴侣里的多个工程可能共用本地数据库,收藏测试建议以 APK 重启结果为准;需要清除本例收藏时,逐个取消收藏或清除本例 APK 的应用数据。

常见问题

现象 检查与处理
伴侣报未知组件,或者整片列表空白 更新到包含布局列表框的伴侣;用本页 APK 对照。工程导入成功不代表旧伴侣支持新组件
状态一直是「正在读取本地数据」 查看错误标签;确认素材有 universities.json,文件名大小写一致,读取路径有 // 前缀
数据条数正常,但所有行都空白 确认行布局是列表框的直接子组件且可见;不能把模板放在列表框外面
文字正常,校徽不显示 检查 JSON 中的 logo 与素材文件名一致,素材确实导入;本例使用本地图片文件名,无需下载
只有一行、列表高度不对或不能滚动 屏幕关闭可滚动,列表高度填充父级;嵌入可滚动屏幕时改为固定高度
网格的按钮超出卡片 行内按钮宽度用填充父级;不要用以屏幕宽度计算的百分比
收藏滚动后跑到别的学校 每次绑定重新设置状态,收藏依据固定 id;不要保存当前行索引或克隆组件对象
模板按钮有点击事件,运行时没反应 用列表框的组件点击事件,按标签区分收藏和详情;模板原组件不是每一行的克隆组件
短列表不能滚动 筛选结果少于首批数量时已经全部显示,这是正常现象;首批按两列卡片和屏幕高度计算,不会把少量结果留在下一页
清空后再搜索又出现大学 清空显示只清列表,本例保留原始数据;搜索和恢复会重新生成筛选结果

工程校验与下一步

本次交付的最终 AIA 已通过归档结构检查、正式编辑器 528 个积木完整加载及完整 YAIL 生成、真实后台导入、Android APK 编译。 正式编辑器首次加载及保存后重开均为 0 个错误,已逐个检查 124 个变量引用的下拉框内部值,事件参数显示中文、保存原生标识,导入后无需重新选择参数。最终生成链 10 项回归也必须全部通过,校验报告包含编译器、转换器、预览、技能、参数背景、参数内部值与文档变量绑定的逐项结果。下载 SHA-256 对应的校验记录。浏览器人工导入、AI 伴侣联机、真机运行和鸿蒙 HAP 尚未验证。

需要扩充学校时,先更新 JSON 记录和对应校徽,保持 id 唯一、图片文件名与素材一致;然后继续学习多模板和嵌套列表。修改或重新生成 AIA 后需重新完成最终归档校验。

使用示例

手动设置行组件

每行每次绑定都会触发遍历数据(索引,值)(ForEachData)。事件内用取组件(标签)(GetComponent) 按标签拿到真实克隆组件:

when 布局列表框1.ForEachData(索引, 值) {
  var 标题组件 = 布局列表框1.GetComponent("标签1")
  var 行数据 = 布局列表框1.GetCurrentData()
  any Label(标题组件).Text = join(行数据)
}

拿到组件后,用「任意组件」分类的积木(如「任意 标签 的 文本 属性」)设置属性, 把 标题组件 变量接到任意组件积木的组件参数上即可。

要点:

  • 行被回收复用时会先按模板初始值复位,再绑定新数据——所以在事件里改过的属性每次绑定都要重新设置 (字典自动绑定也是每次都执行的);
  • 多模板时只能取到当前行所用模板里的组件。

事件处理

when 布局列表框1.ItemClick(索引, 值) {
  标签_详情.Text = 值
}

when 布局列表框1.ComponentClick(标签, 索引, 值) {
  if 标签 = "按钮_删除" {
    对话框1.ShowAlert(join("删除第 ", 索引, " 行"))
  }
}

追加数据与滚动

when 按钮_加载更多.Click() {
  布局列表框1.AppendData(list({ "标签1": "新行 1" }, { "标签1": "新行 2" }))
}

when 按钮_回顶部.Click() {
  布局列表框1.ScrollToIndex(1)
}

when 按钮_看最新.Click() {
  布局列表框1.ScrollToIndex(length(布局列表框1.Data))
}

(length 是列表长度积木——数据有几条就滚到第几行;也可以直接给固定行号。)

滚到底部自动加载更多

不需要「加载更多」按钮——列表滚到底部会触发滚到底部(ReachEnd)事件, 在事件里请求下一页并追加数据(值)(AppendData)即可:

global 页码 = 1

when Screen1.Initialize() {
  布局列表框1.Data = list({ "标签1": "第 1 页数据" })
}

when 布局列表框1.ReachEnd() {
  页码 = 页码 + 1
  Web1.Url = join("https://example.com/api/list?page=", 页码)
  Web1.Get()
}

when Web1.GotText(URL网址, 响应代码, 响应类型, 响应内容) {
  var 结果 = Web1.JsonTextDecode(响应内容)
  布局列表框1.AppendData(dictLookup(结果, "rows"))
}

要点:

  • 追加后新行出现在底部,继续上滑会再次触发,翻页自动连续;
  • 数据不足一屏时不触发——第一页至少要铺满一屏,加载更多才会接力;
  • 没有更多数据时可以在事件里判断并停止请求(例如记一个「是否还有更多」全局变量)。

智能刷新与强制刷新

设置数据(Data)时自动按新旧差异刷新:只重新绑定有变化的行, 未变化的行不触发遍历数据(索引,值)(ForEachData),删除、移动带动画:

when 按钮_只看未读.Click() {
  // 重新设整个列表:与旧数据相同的行不重绑,不同的行自动增删动画
  布局列表框1.Data = list({ "标签1": "张三", "标签2": "2 条未读" })
}

两种情况要改用刷新(Refresh)强制全部行重新绑定:

  • 行内显示依赖列表之外的全局状态(如夜间模式、选中项),改了状态要立刻反映到所有行;
  • 你原地修改了某个字典的内容后,把同一个列表对象再设给数据属性。
global 夜间模式 = false

when 开关_夜间模式.Changed() {
  夜间模式 = 开关_夜间模式.On
  布局列表框1.Refresh()
}

空状态

数据为空时列表中央自动显示提示(默认「暂无数据」)。提示文字可以按场景改写, 也可以在代码块里读回:

when Screen1.Initialize() {
  布局列表框1.EmptyText = "还没有订单,下单后显示在这里"
}

when 布局列表框1.ItemClick(索引, 值) {
  标签_详情.Text = 值
}

设为空文本 "" 则不显示任何提示。搜索无结果时改提示、查到数据恢复,都不用手动管理显隐:

when 按钮_搜索.Click() {
  布局列表框1.ClearData()
  布局列表框1.EmptyText = join("没有找到「", 文本输入框_关键词.Text, "」相关内容")
}

(把你的查找结果设给数据(Data)即可:结果为空列表时提示自动显示, 有结果时自动隐藏。)

多模板

拖入布局列表框的直接子布局按拖入顺序作为类型 1、2、…、n 模板。例如先拖一个「左图右文」的 水平布局、再拖一个纯文本的竖直布局,就是类型 1 和类型 2:

when Screen1.Initialize() {
  布局列表框1.Data = list(
    { "类型": 1, "标签1": "张三", "标签2": "在吗?" },
    { "类型": 2, "标签3": "系统通知", "标签4": "10:30" }
  )
}

同一屏幕内组件名不能重复,所以每个模板里的组件要各自命名。上面的键名就是组件名: 类型 1 的水平布局里放 标签1、标签2,类型 2 的竖直布局里放 标签3、标签4, 数据里各写各的键——路由到哪个模板,就按哪个模板的组件名绑定。

路由规则:

  • 数据项为字典时,用 类型(优先)或 type 键选模板;值接受数字或可转数字的文本;
  • 没有该键、值不是数字时都用类型 1;超出模板数时夹取到第一个/最后一个模板;
  • 非字典数据项(文本、数字)一律用类型 1;
  • 只有拖进布局列表框本身的直接子布局才算模板;拖进模板布局里的子组件属于模板内容。

网格、横向与行间距

列数(Columns)、方向(Orientation)、 行间距(Spacing)、从底部开始(StackFromEnd)是四个 只在设计器属性面板里设置的布局属性(没有对应代码块),常用的组合:

场景 设置
商品墙 / 相册 列数 = 2 或 3,行间距 = 8
横滑卡片(如头像栏) 排列方向 = 横向,行间距 = 8
聊天窗口 从底部开始 = 真,行间距 = 4,配合追加数据(值)(AppendData)

网格模式下每列宽度自动均分;行间距只在行与行、列与列之间生效,列表四周与屏幕对齐不留边。 滚到底部(ReachEnd)在横向列表里对应滚到最右侧。

聊天窗口的典型写法(设计器勾选「从底部开始」后):

when 按钮_发送.Click() {
  布局列表框1.AppendData(list({ "类型": 2, "标签2": 文本输入框_消息.Text }))
  文本输入框_消息.Text = ""
}

when Screen1.Initialize() {
  布局列表框1.Data = list({ "类型": 1, "标签1": "你好,请问有什么可以帮你?" })
}

(配合多模板:对方气泡模板里放 标签1、自己气泡模板里放 标签2(两个模板组件不能同名),数据里按类型各写各的键; 新消息追加后视图自动停在最底部。)

嵌套:列表套列表

布局列表框可以拖进另一个布局列表框当模板组件。外层克隆行时会把内层连同它自己的模板一起 完整克隆——内层是一个真正能显示数据的列表,典型用法是「纵向首页 + 行内横滑栏目」。

嵌套时的规则:

  • 内层的列数(Columns)、方向(Orientation)、 行间距(Spacing)等设计器属性随克隆一并带过去;内层设计时设好的数据也会 按克隆那一刻的值带过去(与限制第 3 条的快照语义相同);
  • 要每行的内层显示不同数据:在外层的遍历数据(索引,值)(ForEachData)事件里用 取组件(标签)(GetComponent)拿到内层克隆,再用 「任意组件」分类的积木(如「任意 布局列表框 的 数据」)设置—— 行被回收时会先复位到快照数据,所以每次绑定都要重新设置:
when 布局列表框1.ForEachData(索引, 值) {
  var 内层列表 = 布局列表框1.GetComponent("布局列表框2")
  any LayoutListView(内层列表).Data = 值
}

(拿到 内层列表 变量后,把它接到「任意 布局列表框」的属性/方法积木上即可,写法与 手动设置行组件相同。)

  • 内层的事件接不到同名处理块:克隆出来的内层不是设计器里那个组件,when 布局列表框2.行点击 这样的块不会触发,只能用「任意组件」分类的 「任意 布局列表框 的 … 事件」接收(此时每一行的内层都会触发,注意区分来源)—— 相关逻辑尽量写在外层事件里(外层是真实组件,事件正常);
  • 方向:纵套横(外层纵向、内层方向(Orientation))方向不同、互不干扰, 是首页横滑栏目的标准做法;纵套纵时手势会先滚动内层、滚到内层边缘才带动外层, 若内层只是展示少量数据,给它固定高度、数据控制在一屏内即可;
  • 内层高度必须设固定值(不要用自动/填充父级,嵌套布局里测量结果不可预期);
  • 别套太多层:外层每建一行都要克隆整棵内层列表(含内层的全部模板),行数多时开销明显。

完整案例:校园资讯流

朋友圈 / 资讯类 app 的完整骨架:置顶公告与图文帖混排(多模板)、点赞只刷新被点的那一行 (智能刷新)、滚到底部自动加载下一页。照着搭完,得到的是一个能交互、能继续扩展的真实页面, 而不是孤立的积木演示。

第 1 步:搭界面

  1. 拖一个布局列表框到屏幕,在属性面板把高度设为固定值(如 500)—— 可滚动的屏幕上不要用填充父级(见限制);
  2. 拖一个水平布局进布局列表框内部(这就是类型 1「图文帖」模板):
    • 图像改名为 图像_头像,宽、高各 48;
    • 竖直布局放进水平布局,里面放标签 标签_昵称(初始文本「昵称」)和 标签 标签_内容(初始文本「内容预览」,让它有高度);
    • 按钮改名为 赞,初始文本 0;
  3. 再拖一个竖直布局进布局列表框(类型 2「置顶公告」模板):标签 标签_置顶 (初始文本「置顶」)、标签 标签_公告内容;
  4. 屏幕底部放标签 标签_状态,文本清空(用来显示交互结果)。

模板里的初始文本只是设计时能看清行的大致样子,运行时会被数据自动覆盖; 图像_头像 找不到同名素材时只是显示空白,不影响其它部分。

第 2 步:初始数据,先看到列表

global 资讯数据 = list(
  { "类型": 2, "标签_置顶": "置顶", "标签_公告内容": "本周六 14:00 操场「百团大战」,60 个社团现场招新" },
  { "类型": 1, "图像_头像": "avatar1.png", "标签_昵称": "篮球社",
    "标签_内容": "今晚 7 点体育馆三对三,缺一个人,速来!", "赞": 12 },
  { "类型": 1, "图像_头像": "avatar2.png", "标签_昵称": "学生会",
    "标签_内容": "宿舍文化节开始报名,优秀宿舍奖励 500 度电", "赞": 45 }
)

when Screen1.Initialize() {
  布局列表框1.Data = 资讯数据
}

跑起来:第一条走类型 2 模板(置顶公告),后两条走类型 1(头像 + 昵称 + 内容), 「赞」按钮直接显示 12 和 45——绑定代码一行都没写,全是字典键名对上组件名的 自动绑定。

第 3 步:点赞——只刷新被点的那一行

when 布局列表框1.ComponentClick(标签, 索引, 值) {
  if 标签 = "赞" {
    var 新数据 = dictCopy(值)
    dictSet(新数据, "赞", dictLookup(值, "赞") + 1)
    资讯数据[索引] = 新数据
    布局列表框1.Data = 资讯数据
    标签_状态.Text = join("第 ", 索引, " 行被点赞,只有这一行在刷新")
  }
}

when 布局列表框1.ItemClick(索引, 值) {
  标签_状态.Text = join("点击了第 ", 索引, " 行")
}

跑起来:点一下「12」变「13」,带动画,其它行纹丝不动——改一条数据就只重绑这一行 (见智能刷新);点行的空白处则显示点的是第几行。

注意这里的写法是复制一份、改副本、再把副本换回该行,而不是直接改 值: 字典设值(dictSet)是原地修改,原地改完不换回列表,新旧列表内容完全相同, 智能刷新会认为什么都没变而整表全刷。先 dictCopy 复制、改完把新字典赋给 资讯数据[索引],该行才会真正「有变化」,从而只重绑这一行。

第 4 步:滚到底部自动加载

global 页码 = 1

when 布局列表框1.ReachEnd() {
  if 页码 < 3 {
    页码 = 页码 + 1
    布局列表框1.AppendData(list(
      { "类型": 1, "图像_头像": "avatar1.png", "标签_昵称": join("校园号 ", 页码),
        "标签_内容": join("第 ", 页码, " 页加载的资讯内容……"), "赞": 0 }
    ))
  } else {
    标签_状态.Text = "没有更多了"
  }
}

跑起来:列表滚到底自动多出一条,连续滚连续加载,加载到第 3 页后提示「没有更多了」。 真实项目里把 AppendData 换成 Web1.Get() 请求接口、在 Web1.GotText 事件里解析并追加即可 (写法见上文滚到底部(ReachEnd))。

常见问题

现象 检查
列表空白、行不显示 数据项的键名与模板组件名一个字都不能差;类型 值要在模板数量范围内
点赞没反应 组件点击(标签,索引,值)(ComponentClick)里的标签是组件名——按钮必须真的叫 赞;每行数据都要带 赞 键
滚到底不触发 首屏数据要铺满一屏(内容不足一屏时不触发);高度别用填充父级
点赞数字变成 01 这类 该行数据缺 赞 键,dictLookup 返回空文本,+1 变成了文本拼接——补上键就好

限制与注意事项

  1. 模板里不要放重型/独占资源组件:地图、相机、视频播放器、Web浏览器、各类选择器 (拨号、联系人、文件等)——克隆会产生多份实例,资源独占与结果回调都无法对应。 推荐用标签、图像、按钮、复选框、开关和各种布局搭模板。
  2. 滚动屏幕 + 高度=填充父级会显示异常(与列表显示框相同): 可滚动屏幕上请给固定高度。
  3. 运行期改模板组件的属性不会同步到已建行:模板快照在首次克隆时建立;改了模板后, 清空数据再重新设置即可。
  4. 取组件(标签)(GetComponent)、取行数据(GetCurrentData)只在 遍历数据与三类点击事件内有效,返回的组件不要长期保存(见方法说明)。
  5. 性能:只创建可见行 + 少量回收行,行数与数据总量无关;万条数据滚动内存平稳; 遍历数据事件在滚动中高频触发,事件体尽量轻(属性设置没问题,网络请求别放这里)。
  6. 行内逻辑依赖外部状态时要主动刷新:智能刷新对内容相同的行不重新绑定(这是性能优势), 也就不会重新执行这些行的遍历数据事件——改了全局状态后调一次 刷新(Refresh)即可(见智能刷新与强制刷新)。
  7. 列数 / 排列方向 / 从底部开始 / 行间距是设计期属性:在设计器里改,代码块里没有对应 积木;运行中不可切换。

完整 .aia 源码与操作指导

下载 .aia 源码

沿用中国大学排名案例的完整业务流程:真实大学资料、搜索、收藏、详情和自动加载更多。显示参数收在“列表显示设置”中,默认主界面直接查看大学。

大学列表收藏与分页完整案例

导入方法:项目 → 导入项目(.aia)→ 选择下载的 .aia 源码文件。 浏览真实大学资料,搜索、收藏、查看详情并触底自动加载更多;空关键词显示全部大学。尺寸和颜色设置默认收起。 覆盖检查 38/38,正式编辑器首次载入和保存重开均零错误,后台导入与安装包编译通过。真机/伴侣尚未实测。

先运行这个实际案例

  1. 打开即显示大学排名,不输入关键词也能浏览。
  2. 搜索、收藏、点击大学查看详情,滑到底自动加载下一批。
  3. 需要调整尺寸和颜色时,再打开“列表显示设置”。

高级面板默认收起,原完整功能入口仍可打开使用。源码为中文app.json和Screen1.ai-blocks,AIA包含全部素材和完整积木。正式编辑器、后台导入及安装包检查以更新后的最终归档报告为准;真机/伴侣尚未实测。

API / 功能 操作入口 预期结果
Data读取与设置、AppendData、ReachEnd 初始真实资料读取;搜索;列表向上滑到底;横向卡片滑到末端 实际首批数据与后续批次;无网络请求,不伪造加载成功;到底事件驱动还有数据才追加
GetComponent、ForEachData、GetCurrentData 实际行绑定过程 按真实模板标签取排名和收藏按钮,依据真实行值染色;仅绑定回调读当前行上下文
ItemClick、ItemLongClick、ComponentClick 点击大学行、长按及收藏按钮 详情使用真实大学数据;收藏切换并存独立TinyDB;模板按钮标签正确
Refresh、ScrollToIndex、ClearData 刷新、顶部、清空、恢复全部 真实刷新绑定与回到顶部;清空仅显示数据,原资料和收藏保留;恢复重新筛选
BackgroundColor、EmptyText 完整主题按钮后搜索无匹配,再恢复 真实背景与空提示读回;恢复后大学继续显示
Visible、Left、Top、Width、Height、WidthPercent、HeightPercent 视图切换、列表像素、百分比及数据与属性 列表真实位置尺寸和可见性;控制区独立可用
Columns、Orientation、Spacing、StackFromEnd设计器属性 单列竖向列表和横向双行卡片切换 设计器中各项真实配置;这些属性只有设计器入口,不编造运行时设置块
搜索、仅收藏、空数据、错误恢复 不输入关键词、输入不存在词、取消筛选、查看错误 空关键词显示真实大学;无匹配显示空提示;底部实际错误信息,不用预置结果伪造绑定

横向卡片采用Orientation=1、Columns=2与StackFromEnd=True,与原大学案例的竖向双列网格不同。组件设计器方向与行列数需在导入工程中修改后重载,界面不提供不存在的属性设置块。排名及特色素材与原大学案例完全一致,引用方式和适用范围见资料来源。

文档反馈