App Inventor 2 ListView自定义Item教程 - 打造精美列表

« 返回首页

概述

ListView是App Inventor 2中最常用的列表展示组件。内置的ListView支持简单的文本列表,但自定义能力有限。本文介绍从基础到高级的ListView自定义方案:

  1. 内置ListView — 简单文本/详情列表
  2. ListView扩展 — AdvancedListView等第三方扩展
  3. WebView方案 — 使用HTML/CSS打造完全自定义的列表

内置ListView组件

组件位置

组件面板 → User Interface → ListView

核心属性

属性 类型 说明
ElementsFromString text 逗号分隔的列表项
Selection text 当前选中项
SelectionIndex number 当前选中项索引
ShowFilterBar boolean 是否显示搜索过滤栏
TextSize number 文字大小
TextColor color 文字颜色
BackgroundColor color 背景颜色
Height number 列表高度

基本用法

when Screen1.Initialize() {
  ListView1.ElementsFromString = "苹果,香蕉,橙子,葡萄,西瓜"
  // 或使用列表
  ListView1.Elements = createList("苹果", "香蕉", "橙子", "葡萄", "西瓜")
}
when ListView1.AfterPicking() {
  Label1.Text = join("你选择了:", ListView1.Selection)
}

带过滤的列表

设置 ShowFilterBar = true,用户可以在顶部搜索框中输入关键字过滤列表项。

动态添加/删除列表项

// 添加
when Button1.Click() {
  var currentList = ListView1.Elements
  addItemsToList(currentList, TextBox1.Text)
  ListView1.Elements = currentList
}

// 删除
when Button2.Click() {
  var currentList = ListView1.Elements
  var selectedIndex = ListView1.SelectionIndex
  if selectedIndex > 0 {
    removeListItem(currentList, selectedIndex)
    ListView1.Elements = currentList
  }
}

内置ListView的局限性

内置ListView只能显示:

  • 单行文本
  • 统一的颜色和字体大小
  • 无法显示图标/图片
  • 无法自定义每项布局

如果需要更丰富的列表效果,需要使用扩展或WebView方案。

方案一:使用ListView扩展

常用扩展

扩展名 来源 功能
AdvancedListView Kodular社区 多列、图标、自定义布局
RecyclerView DeepHost 高性能、自定义Item
ListViewPlus AppyBuilder 图片+文本+副文本

示例:AdvancedListView

AdvancedListView支持在列表项中显示图片、主文本、副文本。

添加扩展

  1. 下载AdvancedListView扩展(.aix文件)
  2. 在App Inventor中:Designer → Extension → Import Extension
  3. 选择下载的.aix文件

使用方法

when Screen1.Initialize() {
  global dataList = createList()
  
  // 创建列表项(每项是一个字典)
  addItemsToList(global_dataList, createDictionary(createPair("标题", "张三"), createPair("副标题", "18888888888"), createPair("图片", "contact_icon.png")))
  addItemsToList(global_dataList, createDictionary(createPair("标题", "李四"), createPair("副标题", "19999999999"), createPair("图片", "contact_icon.png")))
  addItemsToList(global_dataList, createDictionary(createPair("标题", "王五"), createPair("副标题", "17777777777"), createPair("图片", "contact_icon.png")))
  
  AdvancedListView1.SetData(global_dataList)
}

when AdvancedListView1.ItemClick(position, itemData) {
  var name = getDictionaryValue(itemData, "标题")
  var phone = getDictionaryValue(itemData, "副标题")
  Label1.Text = join(name, ":", phone)
}

方案二:Spinner下拉列表

Spinner是另一种列表选择组件,显示为下拉菜单:

when Screen1.Initialize() {
  Spinner1.Elements = createList("选项1", "选项2", "选项3")
}
when Spinner1.AfterSelecting(selection) {
  Label1.Text = join("选择了:", selection)
}

方案三:WebView + HTML自定义列表

最灵活的方案,可以完全控制列表的外观。

HTML文件(custom_list.html)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, sans-serif; background: #f5f5f5; }
.list-item {
  display: flex; align-items: center; padding: 12px 16px;
  background: white; border-bottom: 1px solid #eee;
  cursor: pointer;
}
.list-item:active { background: #e8e8e8; }
.avatar {
  width: 40px; height: 40px; border-radius: 50%;
  background: #4CAF50; color: white; display: flex;
  align-items: center; justify-content: center;
  font-size: 18px; font-weight: bold; margin-right: 12px;
  flex-shrink: 0;
}
.info { flex: 1; }
.title { font-size: 16px; color: #333; }
.subtitle { font-size: 13px; color: #999; margin-top: 2px; }
.badge {
  background: #ff4444; color: white; border-radius: 50%;
  min-width: 20px; height: 20px; display: flex;
  align-items: center; justify-content: center;
  font-size: 12px; padding: 0 6px;
}
.search-bar {
  padding: 8px 16px; background: white; border-bottom: 1px solid #eee;
}
.search-bar input {
  width: 100%; padding: 8px 12px; border: 1px solid #ddd;
  border-radius: 20px; font-size: 14px; outline: none;
}
</style>
</head>
<body>
<div class="search-bar">
  <input type="text" id="search" placeholder="搜索...">
</div>
<div id="list"></div>
<script>
var items = [];

function renderList(data) {
  var html = '';
  data.forEach(function(item, index) {
    html += '<div class="list-item" onclick="onItemClick(' + index + ')">';
    html += '<div class="avatar">' + item.title.charAt(0) + '</div>';
    html += '<div class="info">';
    html += '<div class="title">' + item.title + '</div>';
    html += '<div class="subtitle">' + item.subtitle + '</div>';
    html += '</div>';
    if (item.badge) {
      html += '<div class="badge">' + item.badge + '</div>';
    }
    html += '</div>';
  });
  document.getElementById('list').innerHTML = html;
}

function onItemClick(index) {
  window.AppInventor.setWebViewString("CLICK:" + index);
}

// 搜索过滤
document.getElementById('search').oninput = function() {
  var keyword = this.value.toLowerCase();
  var filtered = items.filter(function(item) {
    return item.title.toLowerCase().includes(keyword) || 
           item.subtitle.toLowerCase().includes(keyword);
  });
  renderList(filtered);
};

// 监听App端数据
setInterval(function() {
  var data = window.AppInventor.getWebViewString();
  if (data && data !== "") {
    try {
      items = JSON.parse(data);
      renderList(items);
    } catch(e) {}
    window.AppInventor.setWebViewString("");
  }
}, 200);
</script>
</body>
</html>

App Inventor积木块

when Screen1.Initialize() {
  WebViewer1.HomeUrl = "file:///android_asset/custom_list.html"
  global contactData = "["
  global contactData = join(global_contactData, "{\"title\":\"张三\",\"subtitle\":\"18888888888\",\"badge\":\"3\"},")
  global contactData = join(global_contactData, "{\"title\":\"李四\",\"subtitle\":\"19999999999\",\"badge\":\"\"},")
  global contactData = join(global_contactData, "{\"title\":\"王五\",\"subtitle\":\"17777777777\",\"badge\":\"1\"}")
  global contactData = join(global_contactData, "]")
}

when WebViewer1.PageLoaded(url) {
  WebViewer1.WebViewString = global_contactData
}

when WebViewer1.WebViewStringChange(value) {
  if containsText(value, "CLICK:") {
    var selectedIndex = replaceAll(value, "CLICK:", "")
    Label1.Text = join("点击了第", selectedIndex + 1, "项")
  }
}

方案四:动态创建组件

使用Dynamic Components扩展,可以在HorizontalArrangement中动态创建Label、Image等组件,模拟自定义列表。

when Screen1.Initialize() {
  var dataList = createList("项目1", "项目2", "项目3")
  var i = 1
  while i <= lengthOfList(dataList) {
    // 创建水平布局容器
    DynamicComponents1.Create(VerticalArrangement1, "HorizontalArrangement", join("row_", i))
    // 在容器中创建标签
    DynamicComponents1.Create(join("row_", i), "Label", join("label_", i))
    DynamicComponents1.SetProperty(join("label_", i), "Text", selectListItem(dataList, i))
    DynamicComponents1.SetProperty(join("label_", i), "FontSize", 18)
    i = i + 1
  }
}

实战案例:联系人列表

功能需求

  • 显示联系人头像、姓名、电话
  • 点击拨打电话
  • 搜索过滤
  • 添加/删除联系人

使用WebView方案

global contactList = createList()

when Screen1.Initialize() {
  // 初始化示例数据
  addItemsToList(global_contactList, createDictionary(createPair("name", "张三"), createPair("phone", "13800138001"), createPair("dept", "技术部")))
  addItemsToList(global_contactList, createDictionary(createPair("name", "李四"), createPair("phone", "13900139002"), createPair("dept", "市场部")))
  addItemsToList(global_contactList, createDictionary(createPair("name", "王五"), createPair("phone", "13700137003"), createPair("dept", "财务部")))
  WebViewer2.HomeUrl = "file:///android_asset/custom_list.html"
}

when WebViewer2.PageLoaded(url) {
  var jsonData = listToJson(global_contactList)
  WebViewer2.WebViewString = jsonData
}

when WebViewer2.WebViewStringChange(value) {
  if containsText(value, "CLICK:") {
    var index = replaceAll(value, "CLICK:", "") + 1
    var contact = selectListItem(global_contactList, index)
    var phone = getDictionaryValue(contact, "phone")
    PhoneCall1.MakePhoneCall(phone)
  }
}

when Button3.Click() {
  var newContact = createDictionary(createPair("name", TextBox2.Text), createPair("phone", TextBox3.Text), createPair("dept", TextBox4.Text))
  addItemsToList(global_contactList, newContact)
  WebViewer2.WebViewString = listToJson(global_contactList)
}

方案对比

方案 灵活度 性能 难度 适用场景
内置ListView ⭐ 高 ⭐ 简单文本列表
ListView扩展 ⭐⭐⭐ 高 ⭐⭐ 图文列表、多列
WebView+HTML ⭐⭐⭐⭐⭐ 中 ⭐⭐⭐ 完全自定义UI
动态组件 ⭐⭐⭐ 中低 ⭐⭐⭐ 需要原生组件

推荐:

  • 简单列表 → 内置ListView
  • 需要图片/多行 → ListView扩展
  • 完全自定义UI → WebView + HTML

常见问题

Q1: ListView数据量很大时卡顿?

  • 内置 ListView 的 Android 实现已经使用 RecyclerView,普通的大列表不需要另装 RecyclerView 扩展
  • 数据量特别大时仍建议分页或分批更新,避免每次只改一项却反复重建整个列表
  • 如果需要完全自定义的懒加载布局,再考虑专用扩展或 WebView 方案

Q2: 如何实现列表项滑动删除?

内置组件不支持滑动操作。解决方案:

  • 使用支持滑动的扩展(如SwipeListView)
  • 或使用WebView + touch事件实现

Q3: 如何在列表项中显示不同颜色的标签?

内置ListView不支持。使用WebView方案:

<span class="tag" style="background: #4CAF50">已完成</span>
<span class="tag" style="background: #FF9800">进行中</span>

总结

ListView自定义是App Inventor 2进阶开发的重要技能。根据需求复杂度选择合适方案:

  • 简单需求 → 内置组件
  • 中等需求 → 扩展组件
  • 高级需求 → WebView + HTML
文档反馈