这主要是为了讨论,但真的.

我还没有在互联网上找到类似的东西,所以我想也许有一些性能上的冲击,或者可能没有人没有try 过.但如果不是这样,这个构造函数将非常有助于减少ListView的样板代码.

通常情况下,当我们使用ListView.builder()构造函数时,它看起来如下所示:

@override
Widget build(BuildContext context) {
  final items = <ListItem>[
    // item 1,
    // item 2,
    // ... et cetera
  ];
  
  // ... code
  child: ListView.builder(
    itemBuilder: (context, index) => SomeFancyListTile(
      leading: Image.network(items[index].imageUrl),
      title: Text(items[index].title),
    ),
    itemCount: items.length,
  ),
  // ... more code
}

这完全没问题,但最近我需要用LinkedList来修改基于相邻元素的Widget,它没有方便的[]运算符,而只有.elementAt(index)个方法.然而,知道LinkedList是如何构造的,对于大型列表,代码将非常不优化(呈现整个列表需要O(n^2)).

但后来我想,并不需要这样,因为ListView会一个接一个地呈现项目,这正是迭代器的工作方式.

所以,我实现了一个定制的SliverChildDelegate,它正好做到了这一点-结合了迭代器的功能和ListView‘S渲染.它还带来了一些语法改进,减少了每次需要访问列表项时的样板代码和items[index]的使用.

import 'package:flutter/material.dart';

typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value);

class SliverIterableBuilderDelegate<T> extends SliverChildDelegate {
  SliverIterableBuilderDelegate(
    this.iterable, {
    required this.builder,
  }) : iterator = iterable.iterator;

  final Iterable<T> iterable;
  final ValueWidgetBuilder<T> builder;
  final Iterator<T> iterator;

  @override
  Widget? build(BuildContext context, int index) {
    if (index < 0 || index >= iterable.length) {
      return null;
    }

    if (iterator.moveNext()) {
      return builder(context, iterator.current);
    }

    return null;
  }

  @override
  bool shouldRebuild(covariant SliverChildDelegate oldDelegate) => true;
}

@override
Widget build(BuildContext context) {
  final items = <ListItem>[
    // item 1,
    // item 2,
    // ... et cetera
  ];
  
  // ... code
  child: ListView.custom(
    childrenDelegate: SliverIterableBuilderDelegate(
      builder: (context, value) => SomeFancyListTile(
        leading: Image.network(value.imageUrl),
        title: Text(value.title),
      ),
  ),
  // ... more code
}

所以,我想知道这种方法是否有一些缺点?此外,还建议添加一个ListView.iterable()构造函数以进一步减少代码.

推荐答案

这是一个很好的 idea ,但这个实现不起作用.不管滚动的方向如何,你都要拨打Iterator.moveNext().如果你有一个很长的列表,向下滚动很多次,然后向上滚动,你会开始看到列表以相反的方式继续.我举了一个例子来说明这个行为,它可以直接复制粘贴到DartPad中:

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  @override
Widget build(BuildContext context) {
  final items = List.generate(1000, (val) => val, growable: false);
  
  // ... code
  return ListView.custom(
    childrenDelegate: SliverIterableBuilderDelegate(
      items,
      builder: (context, value) => Text(
        value.toString()
      ),
  )
);}
}

typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value);

class SliverIterableBuilderDelegate<T> extends SliverChildDelegate {
  SliverIterableBuilderDelegate(
    this.iterable, {
    required this.builder,
  }) : iterator = iterable.iterator;

  final Iterable<T> iterable;
  final ValueWidgetBuilder<T> builder;
  final Iterator<T> iterator;

  @override
  Widget? build(BuildContext context, int index) {
    if (index < 0 || index >= iterable.length) {
      return null;
    }

    if (iterator.moveNext()) {
      return builder(context, iterator.current);
    }

    return null;
  }

  @override
  bool shouldRebuild(covariant SliverChildDelegate oldDelegate) => true;
}

最简单的解决方案是只使用普通列表,而不是链接列表,这样您就可以进行快速的索引操作.

Flutter相关问答推荐

Android Emulator冻结,但在调整窗口大小时解冻

在LinkedIn上分享Fighter和Web的链接会产生不同的结果

在Flutter 中创建自定义形状

Flutter-Riverpod:如何只从提供者读取一次值

Riverpod 2.0-如何使用FutureOr和AutoDisposeSyncNotificationer处理api状态代码?

如何使 dart 函数变得通用?

如何手动将 FirebaseAuth 用户邮箱验证状态设置为 true

带有命名参数的回调函数不起作用

Flutter 使用扩展的 TextField 定位小部件

点击按钮后重新启动定时器

Flutter屏幕适配 - 在模拟器和真实手机上文本尺寸不同

Riverpod 2.3.6:AutoDisposeNotifier和ref.onDispose自动处理

为什么 ref.watch(otherProvider.stream) 在 Riverpod 2.x 中被弃用并且将在 3.x 中被删除?

Flutter 中出现错误空判断运算符用于空值

我正在try 使用 Firebase 在 Flutter 中使用 Google 注销,但它不起作用

Future.wait() 中的 futures 可以顺序调用吗?

更新未知文档 ID firebase\flutter 中的字段

Flutter Web Responsiveness 效率不高

在 Flutter 应用程序中全局使用 assets 文件夹

RangeError(索引):无效值:有效值范围为空:点击文章时为0