JakesMD
JakesMD

Reputation: 2166

Flutter issue: listview rebuilding items when scrolled

When i scroll to the bottom of my listview, the bottom item gets rebuilt. The same when i scroll to the top, my first item gets rebuilt. The first item is a card with selectable chips that get unselected when this happens. And the "entrance" animation replays as well. How can i stop this?

Here's the basic code (it uses the simple_animations package and I can't seem to reproduce the problem with the chips, but I still have problems with the animations):

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List _chips = ['Hello', 'World'];

  List _selected = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Issue demo'),
      ),
      body: ListView(
        children: <Widget>[
          FadeIn(
            1,
            Card(
              child: Wrap(
                spacing: 10,
                children: List<Widget>.generate(
                  _chips.length,
                  (int index) => InputChip(
                      label: Text(_chips[index]),
                      selected: _selected.contains(_chips[index]),
                      onSelected: (selected) {
                        setState(() {
                          if (selected) {
                            _selected.add(_chips[index]);
                          } else {
                            _selected.remove(_chips[index]);
                          }
                        });
                      }),
                ),
              ),
            ),
          ),
          FadeIn(1.5, Text('A', style: Theme.of(context).textTheme.display4)),
          FadeIn(2, Text('Very', style: Theme.of(context).textTheme.display4)),
          FadeIn(2.5, Text('Big', style: Theme.of(context).textTheme.display4)),
          FadeIn(3, Text('Scroll', style: Theme.of(context).textTheme.display4)),
          FadeIn(3.5, Text('View', style: Theme.of(context).textTheme.display4)),
          FadeIn(4, Text('With', style: Theme.of(context).textTheme.display4)),
          FadeIn(4.5, Text('Lots', style: Theme.of(context).textTheme.display4)),
          FadeIn(5, Text('Of', style: Theme.of(context).textTheme.display4)),
          FadeIn(5.5,Text('Items', style: Theme.of(context).textTheme.display4)),
          FadeIn(
            6,
            Card(
              child: Text('Last item',
                  style: Theme.of(context).textTheme.display2),
            ),
          ),
        ],
      ),
    );
  }
}

class FadeIn extends StatelessWidget {
  final double delay;
  final Widget child;

  FadeIn(this.delay, this.child);

  @override
  Widget build(BuildContext context) {
    final tween = MultiTrackTween([
      Track("opacity")
          .add(Duration(milliseconds: 500), Tween(begin: 0.0, end: 1.0)),
      Track("translateX").add(
          Duration(milliseconds: 500), Tween(begin: 130.0, end: 0.0),
          curve: Curves.easeOut)
    ]);

    return ControlledAnimation(
      delay: Duration(milliseconds: (300 * delay).round()),
      duration: tween.duration,
      tween: tween,
      child: child,
      builderWithChild: (context, child, animation) => Opacity(
        opacity: animation["opacity"],
        child: Transform.translate(
            offset: Offset(animation["translateX"], 0), child: child),
      ),
    );
  }
}

You should run this yourself to fully understand the issue

Upvotes: 17

Views: 15948

Answers (2)

Abhay Patil
Abhay Patil

Reputation: 1

Setting cacheExtent can improve scroll performance because it helps Flutter decide how far ahead or behind the current view to load items. This reduces the need for Flutter to load elements immediately as they come into view, which can enhance performance when scrolling through large data sets.

Note: Increasing the cacheExtent too much could impact memory usage, so you may need to experiment with the value based on your app's requirements and the size of your list items.

ListView.builder(
  cacheExtent: 1000.0, // Adjust this value based on your specific use case
  itemCount: items.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(items[index]),
    );
  },
)

Upvotes: 0

Thắng Mai
Thắng Mai

Reputation: 1061

To keep elements in ListView alive (not re-render when scrolling back), you should user parameter addAutomaticKeepAlives: true . And every element in ListView have to be StatefulWidget with AutomaticKeepAliveClientMixin.

Here is the code that I has edited for you

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List _chips = ['Hello', 'World'];

  List _selected = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Issue demo'),
      ),
      body: ListView(
        addAutomaticKeepAlives: true,
        children: <Widget>[
          FadeIn(
            1,
            Card(
              child: Wrap(
                spacing: 10,
                children: List<Widget>.generate(
                  _chips.length,
                  (int index) => InputChip(
                      label: Text(_chips[index]),
                      selected: _selected.contains(_chips[index]),
                      onSelected: (selected) {
                        setState(() {
                          if (selected) {
                            _selected.add(_chips[index]);
                          } else {
                            _selected.remove(_chips[index]);
                          }
                        });
                      }),
                ),
              ),
            ),
          ),
          FadeIn(1.5, Text('A', style: Theme.of(context).textTheme.display4)),
          FadeIn(2, Text('Very', style: Theme.of(context).textTheme.display4)),
          FadeIn(2.5, Text('Big', style: Theme.of(context).textTheme.display4)),
          FadeIn(3, Text('Scroll', style: Theme.of(context).textTheme.display4)),
          FadeIn(3.5, Text('View', style: Theme.of(context).textTheme.display4)),
          FadeIn(4, Text('With', style: Theme.of(context).textTheme.display4)),
          FadeIn(4.5, Text('Lots', style: Theme.of(context).textTheme.display4)),
          FadeIn(5, Text('Of', style: Theme.of(context).textTheme.display4)),
          FadeIn(5.5,Text('Items', style: Theme.of(context).textTheme.display4)),
          FadeIn(
            6,
            Card(
              child: Text('Last item',
                  style: Theme.of(context).textTheme.display2),
            ),
          ),
        ],
      ),
    );
  }
}

class FadeIn extends StatefulWidget {
  final double delay;
  final Widget child;
  FadeIn(this.delay, this.child);
  _FadeInState createState() => _FadeInState();
}

class _FadeInState extends State<FadeIn> with AutomaticKeepAliveClientMixin {

  @override
  Widget build(BuildContext context) {
    super.build(context);//this line is needed

    final tween = MultiTrackTween([
      Track("opacity")
          .add(Duration(milliseconds: 500), Tween(begin: 0.0, end: 1.0)),
      Track("translateX").add(
          Duration(milliseconds: 500), Tween(begin: 130.0, end: 0.0),
          curve: Curves.easeOut)
    ]);

    return ControlledAnimation(
      delay: Duration(milliseconds: (300 * widget.delay).round()),
      duration: tween.duration,
      tween: tween,
      child: widget.child,
      builderWithChild: (context, child, animation) => Opacity(
        opacity: animation["opacity"],
        child: Transform.translate(
            offset: Offset(animation["translateX"], 0), child: child),
      ),
    );
  }

  @override
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => true;
}

Upvotes: 45

Related Questions