Flutter异步加载列表插件async_list_view的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter 异步加载列表插件 async_list_view 的使用

异步加载列表插件 async_list_view 可以帮助你从异步数据源中懒加载一个滚动列表。async_list_view 是基于 ListView.builderStreamSummaryBuilder 的轻量级封装。

由于项目项只在用户可见时才被加载,因此 async_list_view 减少了潜在的昂贵数据库读取操作。

示例用例

  • 显示从 Firestore 获取的用户聊天记录。
  • 显示在线市场上的搜索结果。
  • 显示从大文件中读取的日志行。

任何贡献、错误报告或功能请求都受到欢迎。

完整示例

以下是一个完整的示例代码,展示了如何使用 async_list_view 插件来实现异步加载列表。

import 'package:async_list_view/async_list_view.dart';
import 'package:flutter/material.dart';
import 'mock_database.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 2,
        child: SafeArea(
          child: Scaffold(
            appBar: AppBar(
              title: const Text('AsyncListView demo!'),
              bottom: const TabBar(
                tabs: [
                  Tab(icon: Icon(Icons.menu_book)),
                  Tab(icon: Icon(Icons.add)),
                ],
              ),
            ),
            body: const TabBarView(
              children: [
                LazyFruitList(),
                Center(
                  child: SelectableText(
                    'To get a fruit added to the fruit list, please file a bug '
                    'report:\n\n'
                    'https://github.com/caseycrogers/async_list_view/issues/new?assignees=caseycrogers&labels=high-priority&template=fruit-request-template.md&title=%5BFruit%5D+Add+%3Cinsert-fruit-name-here%3E+to+the+Fruit+List',
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                      color: Colors.black38,
                    ),
                    textAlign: TextAlign.center,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class LazyFruitList extends StatefulWidget {
  const LazyFruitList({Key? key}) : super(key: key);

  [@override](/user/override)
  _LazyFruitListState createState() => _LazyFruitListState();
}

class _LazyFruitListState extends State<LazyFruitList> {
  // 当前已加载的水果数量
  int _loadedFruits = 0;

  // 满足搜索条件的水果总数
  int _totalFruits = countMatchingFruits('');

  String _searchString = '';
  late Stream<String> _fruitStream;

  List<String> initialFruits = [];

  void _initializeFruitStream() {
    _loadedFruits = 0;
    _totalFruits = countMatchingFruits(_searchString);
    _fruitStream = getFruits(_searchString).map((fruit) {
      // 增加计数,因为我们不能在 `itemBuilder` 中调用 `setState`
      // 使用 `map` 而不是 `listen`,因为 `listen` 需要广播流,而广播流不能暂停流的底层源。
      // 提示:广播流不是你的朋友。尽量避免使用它。
      setState(() {
        _loadedFruits += 1;
      });
      return fruit;
    });
  }

  [@override](/user/override)
  void initState() {
    _initializeFruitStream();
    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      children: [
        IconButton(
          icon: const Icon(Icons.add),
          onPressed: () {
            setState(() {
              initialFruits = [
                initialFruits.length.toString(),
                ...initialFruits,
              ];
            });
          },
        ),
        TextField(
          onChanged: _onTextChanged,
          decoration: const InputDecoration(
            hintText: 'Search Fruits...',
          ),
        ),
        Expanded(
          child: AsyncListView<String>(
            // 如果相同的流重复传递给 AsyncListView,则 AsyncListView 将保持其状态并不会错误地两次监听同一个流。
            stream: _fruitStream,
            itemBuilder: _buildFruitTile,
            initialData: initialFruits,
            // 如果用户滚动到当前已加载的水果之外,则显示 'loading...' 文本,以告知他们需要等待更多结果。
            loadingWidget: const Padding(
              padding: EdgeInsets.all(8),
              child: Text(
                'loading...',
                style: TextStyle(fontSize: 20, color: Colors.black54),
              ),
            ),
            keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
            noResultsWidgetBuilder: (context) {
              return SelectableText(
                'No fruits found for search term \'$_searchString\'. '
                'If you feel a fruit has excluded in error, please file '
                'a bug report:'
                '\n\nhttps://github.com/caseycrogers/async_list_view/issues/new?assignees=caseycrogers&labels=high-priority&template=fruit-request-template.md&title=%5BFruit%5D+Add+%3Cinsert-fruit-name-here%3E+to+the+Fruit+List',
                style: const TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  color: Colors.black38,
                ),
                textAlign: TextAlign.center,
              );
            },
          ),
        ),
        Container(
          color: Colors.blueAccent.shade100,
          child: Center(
            child: Text(
              '$_loadedFruits/$_totalFruits fruits loaded!',
              style: const TextStyle(fontSize: 18),
            ),
          ),
        ),
      ],
    );
  }

  // 接收加载的水果快照和 ListView 索引,并构建相应的项目。
  Widget _buildFruitTile(
      BuildContext context, AsyncSnapshot<List<String>> snapshot, int index) {
    _loadedFruits = snapshot.data?.length ?? 0;
    return ListTile(
      title: Text(
        snapshot.data?[index] ?? 'Something went wrong!!!',
        style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
      ),
    );
  }

  void _onTextChanged(String newSearchString) {
    // 如果搜索文本发生变化,才更新流,以避免昂贵的重复数据库查询。
    if (_searchString == newSearchString) {
      return;
    }
    setState(() {
      _searchString = newSearchString;
      _initializeFruitStream();
    });
  }
}

更多关于Flutter异步加载列表插件async_list_view的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter异步加载列表插件async_list_view的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,async_list_view 是一个用于在 Flutter 中异步加载列表项的插件。虽然 Flutter 本身没有直接提供名为 async_list_view 的标准库插件,但你可以通过结合 FutureBuilderStreamBuilder 以及分页逻辑来实现类似的功能。

以下是一个使用 FutureBuilder 和分页逻辑来实现异步加载列表的示例代码。在这个例子中,我们假设你有一个 API 可以分页获取数据。

示例代码

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: AsyncListViewDemo(),
    );
  }
}

class AsyncListViewDemo extends StatefulWidget {
  @override
  _AsyncListViewDemoState createState() => _AsyncListViewDemoState();
}

class _AsyncListViewDemoState extends State<AsyncListViewDemo> {
  List<Map<String, dynamic>> items = [];
  int currentPage = 1;
  bool hasMore = true;
  final String apiUrl = "https://api.example.com/items"; // 替换为你的API URL

  @override
  void initState() {
    super.initState();
    loadMoreItems();
  }

  Future<void> loadMoreItems() async {
    if (!hasMore) return;

    try {
      final response = await http.get(Uri.parse("$apiUrl?page=$currentPage"));
      if (response.statusCode == 200) {
        final data = jsonDecode(response.body) as List<dynamic>;
        if (data.isEmpty) {
          hasMore = false;
        } else {
          setState(() {
            items.addAll(data.map((item) => Map.from(item)).toList());
            currentPage += 1;
          });
        }
      }
    } catch (e) {
      print("Error loading items: $e");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Async ListView Demo'),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: items.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(items[index]['title']), // 假设API返回的数据中有title字段
                );
              },
            ),
          ),
          if (hasMore)
            Padding(
              padding: const EdgeInsets.only(bottom: 16.0),
              child: Center(
                child: ElevatedButton(
                  onPressed: loadMoreItems,
                  child: Text('Load More'),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

解释

  1. 状态管理

    • items 存储已加载的列表项。
    • currentPage 存储当前加载的页码。
    • hasMore 标记是否还有更多数据可以加载。
  2. 数据加载

    • loadMoreItems 方法使用 http.get 异步加载数据。
    • 如果响应状态码为 200,并且返回的数据不为空,则将数据添加到 items 列表中,并递增 currentPage
    • 如果返回的数据为空,则将 hasMore 设置为 false
  3. UI 构建

    • 使用 ListView.builder 构建列表。
    • 如果 hasMoretrue,则显示一个按钮用于加载更多数据。

这种方法结合了 Flutter 的基础组件和异步编程模式,实现了类似于 async_list_view 的功能。如果你有一个更具体的 async_list_view 插件或库,请提供更多信息,以便给出更准确的示例代码。

回到顶部