如何在Flutter中实现ListView平滑滚动

How to achieve ListView smooth scrolling in Flutter

我创建了一个包含网络图像的列表视图,当我尝试滚动列表视图时,它的滚动并不平滑,感觉像是在抽搐。对于缓存,我使用了 cached_network_image: any,这个库本身工作正常,但列表视图滚动不平滑。

我知道我们可以使用 Future 小部件实现此目的,但不知道如何 return 使用 future 缓存图像。

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';

void main() {
   runApp(
      MaterialApp(
        title: 'List view with network images',
        home: ListViewController(),

     )
  );
}


class ListViewController extends StatelessWidget {

var imagesArray = [
  "http://iastro.shangcarts.com/pub/media/notice/File-1550484786.jpeg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1550218043.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1550217808.jpeg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1550111913.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1550108862.jpeg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1550024618.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1550022739.jpeg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1549935759.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1549935073.jpeg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1549850545.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1549849978.jpeg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1549008412.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1548985261.jpeg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1548821873.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1548731040.jpeg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1548641672.jpeg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1548410089.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547774905.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547701178.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547625318.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547624883.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547619153.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547606341.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547606200.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547603338.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547602464.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547454842.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547192524.jpg",
  "http://iastro.shangcarts.com/pub/media/notice/File-1547191374.jpg"
];




Widget _imageCell(String imageUrl) {

return ListTile(
  leading: CachedNetworkImage(imageUrl: imageUrl, placeholder: CircularProgressIndicator(), errorWidget: Icon(Icons.error),),
);

}


@override
Widget build(BuildContext context) {
// TODO: implement build
return Material(
  child: ListView.separated(
      itemBuilder: (BuildContext context, int index) {
        return _imageCell(imagesArray[index]);
      },
      separatorBuilder: (context, index) => Divider(
        color: Colors.black,
      ),
      itemCount: imagesArray.length),
);
}



}

编辑:将构建配置更改为 Release 后仍然相同,抽搐。

除了减小源图像的大小,我认为目前没有好的解决方案。我看了你的图片列表,有些图片很大。

https://github.com/renefloor/flutter_cached_network_image/issues/90

https://github.com/pejalo/flutter_image_performance

https://github.com/flutter/flutter/issues/25469

https://github.com/flutter/flutter/issues/27625#issuecomment-461677587

https://github.com/flutter/flutter/issues/2848

https://github.com/flutter/flutter/issues/26194

Flutter 降尺度非常慢。 Flutter 团队必须对其进行性能优化。

另一件事,你的代码没有 运行 on flutter stable

Flutter 1.0.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 5391447fae (3 months ago) • 2018-11-29 19:41:26 -0800
Engine • revision 7375a0f414
Tools • Dart 2.1.0 (build 2.1.0-dev.9.4 f9ebf21297)

Compiler message:
lib/main.dart:56:64: Error: The argument type '#lib1::CircularProgressIndicator'
can't be assigned to the parameter type '(#lib2::BuildContext, dart.core::String)
→ #lib2::Widget'.
Try changing the type of the parameter, or casting the argument to
'(#lib2::BuildContext, dart.core::String) → #lib2::Widget'.
  leading: CachedNetworkImage(imageUrl: imageUrl, placeholder:
  CircularProgressIndicator(), errorWidget: Icon(Icons.error),),
                                                               ^
lib/main.dart:56:106: Error: The argument type '#lib1::Icon' can't be assigned to
the parameter type '(#lib2::BuildContext, dart.core::String, dart.core::Exception)
→ #lib2::Widget'.
Try changing the type of the parameter, or casting the argument to
'(#lib2::BuildContext, dart.core::String, dart.core::Exception) → #lib2::Widget'.
  leading: CachedNetworkImage(imageUrl: imageUrl, placeholder:
  CircularProgressIndicator(), errorWidget: Icon(Icons.error),),
                                                                                                         ^
Compiler failed on /Users/blah/Whosebug/issue_54786567/lib/main.dart
Error launching application on iPhone XR.


flutter run
Launching lib/main.dart on iPhone XR in debug mode...
Starting Xcode build...                                          
 ├─Assembling Flutter resources...                    3.9s

 └─Compiling, linking and signing...                  3.4s

Xcode build done.                                            9.1s
 5.8s
Syncing files to device iPhone XR...                             
flutter: ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
flutter: The following assertion was thrown during performLayout():
flutter: BoxConstraints forces an infinite height.
flutter: These invalid constraints were provided to RenderSemanticsAnnotations's layout() function by the
flutter: following function, which probably computed the invalid constraints in question:
flutter:   RenderConstrainedBox.performLayout (package:flutter/src/rendering/proxy_box.dart:258:13)
flutter: The offending constraints were:
flutter:   BoxConstraints(w=382.0, h=Infinity)
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0      BoxConstraints.debugAssertIsValid.<anonymous closure>.throwError (package:flutter/src/rendering/box.dart:504:9)
flutter: #1      BoxConstraints.debugAssertIsValid.<anonymous closure> (package:flutter/src/rendering/box.dart:547:21)
flutter: #2      BoxConstraints.debugAssertIsValid (package:flutter/src/rendering/box.dart:551:6)
flutter: #3      RenderObject.layout (package:flutter/src/rendering/object.dart:1549:24)
flutter: #4      RenderConstrainedBox.performLayout (package:flutter/src/rendering/proxy_box.dart:258:13)
flutter: #5      RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #6      _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:13)
flutter: #7      RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #8      RenderStack.performLayout (package:flutter/src/rendering/stack.dart:510:15)
flutter: #9      RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #10     _RenderListTile._layoutBox (package:flutter/src/material/list_tile.dart:892:9)
flutter: #11     _RenderListTile.performLayout (package:flutter/src/material/list_tile.dart:913:30)
flutter: #12     RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #13     RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:199:11)
flutter: #14     RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #15     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:13)
flutter: #16     RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #17     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:13)
flutter: #18     RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #19     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:13)
flutter: #20     RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #21     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:13)
flutter: #22     RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #23     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:13)
flutter: #24     RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #25     RenderSliverList.performLayout (package:flutter/src/rendering/sliver_list.dart:164:27)
flutter: #26     RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #27     RenderSliverPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:182:11)
flutter: #28     RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)
flutter: #29     RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:405:13)
flutter: #30     RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1316:12)
flutter: #31     RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1234:20)
flutter: #32     RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1509:7)
flutter: #33     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:768:18)
flutter: #34     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:281:19)
flutter: #35     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:677:13)
flutter: #36     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:219:5)
flutter: #37     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
flutter: #38     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930:9)
flutter: #39     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:842:5)
flutter: #40     _invoke (dart:ui/hooks.dart:154:13)
flutter: #41     _drawFrame (dart:ui/hooks.dart:143:3)
flutter:
flutter: The following RenderObject was being processed when the exception was fired:
flutter:   RenderConstrainedBox#57507 relayoutBoundary=up12 NEEDS-LAYOUT NEEDS-PAINT
flutter:   creator: ConstrainedBox ← Container ← FadeTransition ← Stack ← StreamBuilder<FileInfo>-[#fb1e3] ←
flutter:   CachedNetworkImage ← IconTheme ← Builder ← _ListTile ← MediaQuery ← Padding ← SafeArea ← ⋯
flutter:   parentData: <none> (can use size)
flutter:   constraints: BoxConstraints(0.0<=w<=382.0, 0.0<=h<=Infinity)
flutter:   size: MISSING
flutter:   additionalConstraints: BoxConstraints(biggest)
flutter: This RenderObject had the following descendants (showing up to depth 5):
flutter:   RenderSemanticsAnnotations#494da NEEDS-LAYOUT NEEDS-PAINT
flutter:     RenderImage#98201 NEEDS-LAYOUT NEEDS-PAINT
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════
flutter: Another exception was thrown: RenderBox was not laid out: RenderConstrainedBox#57507 relayoutBoundary=up12 NEEDS-PAINT
flutter: Another exception was thrown: BoxConstraints forces an infinite height.
flutter: Another exception was thrown: RenderBox was not laid out: RenderConstrainedBox#ac73c relayoutBoundary=up12 NEEDS-PAINT
flutter: Another exception was thrown: BoxConstraints forces an infinite height.
flutter: Another exception was thrown: RenderBox was not laid out: RenderConstrainedBox#95164 relayoutBoundary=up12 NEEDS-PAINT
flutter: Another exception was thrown: BoxConstraints forces an infinite height.
flutter: Another exception was thrown: RenderBox was not laid out: RenderConstrainedBox#9efb3 relayoutBoundary=up12 NEEDS-PAINT
flutter: Another exception was thrown: BoxConstraints forces an infinite height.
flutter: Another exception was thrown: RenderBox was not laid out: RenderConstrainedBox#16fcc relayoutBoundary=up12 NEEDS-PAINT
flutter: Another exception was thrown: BoxConstraints forces an infinite height.

我必须做一些修改。

尝试将此添加到您的列表视图:

 physics: const AlwaysScrollableScrollPhysics(),

如果您在模拟器上进行测试,我建议构建发布 APK,将 APK 安装到您的 Android phone 上,然后检查它是否仍然不稳定。模拟器占用资源,所以这可能是一个原因。

最后,您可以尝试减少图片数量、图片类型或尺寸,看看是否仍然卡顿。

否则请在 github 报告此问题,以便 flutter 团队了解。

2 件事

  • 考虑使用 FadeInImage.memoryNetwork 而不是 cached_network_image、and/or
  • 考虑使用 ListView(children: List<Widget> ) 而不是 ListView.separated(itemBuilder: )

FadeInImage.memoryNetwork

参考 https://flutter.dev/docs/cookbook/images/fading-in-images

当 运行 你的示例代码时,我不禁注意到 cached_network_image 做了一些图像 rescaling/sampling 影响主 UI 线程,很可能这个包正在主线程上执行计算量大的任务。使用官方烹饪书对我来说效果更好(Android 模拟器),完整的示例代码(您可能想将 kTransparentImage 更改为其他加载图标)

import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';

void main() {
  runApp(MaterialApp(
    title: 'List view with network images',
    home: ListViewController(),
  ));
}

class ListViewController extends StatelessWidget {
  var imagesArray = [
    "http://iastro.shangcarts.com/pub/media/notice/File-1550484786.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550218043.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550217808.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550111913.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550108862.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550024618.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550022739.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549935759.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549935073.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549850545.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549849978.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549008412.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548985261.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548821873.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548731040.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548641672.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548410089.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547774905.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547701178.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547625318.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547624883.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547619153.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547606341.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547606200.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547603338.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547602464.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547454842.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547192524.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547191374.jpg"
  ];

  Widget _imageCell(String imageUrl) {
    return ListTile(
        leading: FadeInImage.memoryNetwork(
      placeholder: kTransparentImage,
      image: imageUrl,
    ));
  }

  @override
  Widget build(BuildContext context) {
// TODO: implement build
    return Material(
      child: 
      ListView.separated(
          itemBuilder: (BuildContext context, int index) {
            return _imageCell(imagesArray[index]);
          },
          separatorBuilder: (context, index) => Divider(
                color: Colors.black,
              ),
          itemCount: imagesArray.length),
    );
  }
}

ListView(children: List<Widget> )

参考 https://docs.flutter.io/flutter/widgets/ListView-class.html

其次,如果您事先知道您将拥有这个有限的不那么长的列表,也许您想使用 ListView(children: List<Widget> ) 而不是 ListView.separated(itemBuilder: ),因为 itemBuilder 将 invoke/call 更频繁地使用这些功能,并且现在仅针对完整内容缓存图像(通过 FadeInImage.memoryNetworkcached_network_image), 不适用于缩略图 ,@user1462442 提到源图像大小,我同意该评估。我们能做的就是尽可能减少调用次数。

例如代码:

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';

void main() {
  runApp(MaterialApp(
    title: 'List view with network images',
    home: ListViewController(),
  ));
}

class ListViewController extends StatelessWidget {
  var imagesArray = [
    "http://iastro.shangcarts.com/pub/media/notice/File-1550484786.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550218043.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550217808.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550111913.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550108862.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550024618.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1550022739.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549935759.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549935073.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549850545.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549849978.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1549008412.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548985261.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548821873.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548731040.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548641672.jpeg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1548410089.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547774905.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547701178.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547625318.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547624883.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547619153.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547606341.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547606200.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547603338.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547602464.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547454842.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547192524.jpg",
    "http://iastro.shangcarts.com/pub/media/notice/File-1547191374.jpg"
  ];

  Widget _imageCell(String imageUrl) {
    return ListTile(
      leading: CachedNetworkImage(
        imageUrl: imageUrl,
        placeholder: CircularProgressIndicator(),
        errorWidget: Icon(Icons.error),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
// TODO: implement build
    return Material(
      child: ListView(
        children: imagesArray.map((imageUrl) => _imageCell(imageUrl)).toList(),
      ),
    );
  }
}

如上所述,您也可以应用这两个建议。

我也看了一个月,经过大量研究,我发现了一行代码,使列表视图或任何列表都非常平滑滚动

只需使用 物理 属性 作为 physics: BouncingScrollPhysics(), 并感受差异

另外,如果你想将它用于 Column,你可以使用 Singlechildscrollview 并且在其中,你可以使用物理 属性.