在 Flutter 中使用 Provider 更新 AnimatedList 并为其设置动画

Bru*_*Ely 5 flutter flutter-provider

当列表数据存储在拥有列表小部件的同一组件中时,我能够成功地AnimatedList在 Flutter 中对 的内容进行动画处理(即,当列表数据发生更改时不会发生重建)。当我尝试从ChangeNotifierusingProvider和获取列表中的项目时,我遇到了问题Consumer

拥有 的组件AnimatedList(我们称之为ListPage)是用 构建的Consumer<ListItemService>。我的理解是,ListPage每当服务更新列表数据并调用时就会重建notifyListeners()。当发生这种情况时,我不确定ListPage可以调用其中的哪个位置AnimatedListState.insertItem来对列表进行动画处理,因为在build列表状态期间仍然是null。结果是一个列表,其内容没有动画效果。

我认为我的问题归结为“如果实时获取和更新内容,我如何管理此列表的状态?”,理想情况下,我想了解正在发生的事情,但我愿意接受有关如何我的建议如果这不是完成任务的最佳方式,则应该更改此设置。

下面是一些说明问题的代码:

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

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<AuthService>(
          create: (_) => AuthService(),
        ),
        ChangeNotifierProxyProvider<AuthService, ListItemService>(
          create: (_) => ListItemService(),
          update: (_, authService, listItemService) =>
              listItemService!..update(authService),
        ),
      ],
      child: MaterialApp(
        home: HomePage(),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Consumer<ListItemService>(
      builder: (context, listItemService, _) =>
          ListPage(items: listItemService.items),
    );
  }
}

// Implementation details aren't really relevant, but
// this only updates if the user logs in or out.
class AuthService extends ChangeNotifier {}

class ListItemService extends ChangeNotifier {
  List<Item> _items = [];
  List<Item> get items => _items;

  Future<void> update(AuthService authService) async {
    // Method that subscribes to a Firestore snapshot
    // and calls notifyListeners() after updating _items.
  }
}

class Item {
  Item({required this.needsUpdate, required this.content});

  final String content;
  bool needsUpdate;
}

class ListPage extends StatefulWidget {
  const ListPage({Key? key, required this.items}) : super(key: key);

  final List<Item> items;

  @override
  _ListPageState createState() => _ListPageState();
}

class _ListPageState extends State<ListPage> {
  final GlobalKey<AnimatedListState> _listKey = GlobalKey();
  late int _initialItemCount;

  @override
  void initState() {
    _initialItemCount = widget.items.length;
    super.initState();
  }

  void _updateList() {
    for (int i = 0; i < widget.items.length; i++) {
      final item = widget.items[i];
      if (item.needsUpdate) {
        // _listKey.currentState is null here if called
        // from the build method.
        _listKey.currentState?.insertItem(i);
        item.needsUpdate = false;
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    _updateList();
    return AnimatedList(
      key: _listKey,
      initialItemCount: _initialItemCount,
      itemBuilder: (context, index, animation) => SizeTransition(
        sizeFactor: animation,
        child: Text(widget.items[index].content),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

Lul*_*ntu 5

您可以使用didUpdateWidget并检查新旧列表之间的差异。“检查差异”意味着查看删除的内容和添加的内容。在你的情况下,Item小部件应该有一些可以识别的东西。Equatable例如,您可以使用Items 之间的相等性来表示它们的属性之间的相等性。

另一个重要的方面是您正在处理一个列表,它是可变的,但 Widgets 应该是不可变的。因此,每当您修改列表时,实际上都会创建一个新列表,这一点至关重要。

以下是实现细节,最有趣的部分当然是注释(尽管渲染也很有趣;)):

import 'dart:async';
import 'dart:math';

import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<AuthService>(
          create: (_) => AuthService(),
        ),
        ChangeNotifierProxyProvider<AuthService, ListItemService>(
          create: (_) => ListItemService(),
          update: (_, authService, listItemService) => listItemService!..update(authService),
        ),
      ],
      child: MaterialApp(
        home: HomePage(),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Material(
      child: SafeArea(
        child: Consumer<ListItemService>(
          builder: (context, listItemService, _) => ListPage(
            // List.from is very important because it creates a new list instead of
            // giving the old one mutated
            items: List.from(listItemService.items),
          ),
        ),
      ),
    );
  }
}

// Implementation details aren't really relevant, but
// this only updates if the user logs in or out.
class AuthService extends ChangeNotifier {}

class ListItemService extends ChangeNotifier {
  List<Item> _items = [];

  List<Item> get items => _items;

  Future<void> update(AuthService authService) async {
    // Every 5 seconds
    Timer.periodic(Duration(seconds: 5), (timer) {
      // Either create or delete an item randomly
      if (Random().nextDouble() > 0.5 && _items.isNotEmpty) {
        _items.removeAt(Random().nextInt(_items.length));
      } else {
        _items.add(
          Item(
            needsUpdate: true,
            content: 'This is item with random number ${Random().nextInt(10000)}',
          ),
        );
      }
      notifyListeners();
    });
  }
}

class Item extends Equatable {
  Item({required this.needsUpdate, required this.content});

  final String content;
  bool needsUpdate;

  @override
  List<Object?> get props => [content]; // Not sure you want to include needsUpdate?
}

class ListPage extends StatefulWidget {
  const ListPage({Key? key, required this.items}) : super(key: key);

  final List<Item> items;

  @override
  _ListPageState createState() => _ListPageState();
}

class _ListPageState extends State<ListPage> {
  final _listKey = GlobalKey<AnimatedListState>();

  // You can use widget if you use late
  late int _initialItemCount = widget.items.length;

  /// Handles any removal of [Item]
  _handleRemovedItems({
    required List<Item> oldItems,
    required List<Item> newItems,
  }) {
    // If an [Item] was in the old but is not in the new, it has
    // been removed
    for (var i = 0; i < oldItems.length; i++) {
      final _oldItem = oldItems[i];
      // Here the equality checks use [content] thanks to Equatable
      if (!newItems.contains(_oldItem)) {
        _listKey.currentState?.removeItem(
          i,
          (context, animation) => SizeTransition(
            sizeFactor: animation,
            child: Text(oldItems[i].content),
          ),
        );
      }
    }
  }

  /// Handles any added [Item]
  _handleAddedItems({
    required List<Item> oldItems,
    required List<Item> newItems,
  }) {
    // If an [Item] is in the new but was not in the old, it has
    // been added
    for (var i = 0; i < newItems.length; i++) {
      // Here the equality checks use [content] thanks to Equatable
      if (!oldItems.contains(newItems[i])) {
        _listKey.currentState?.insertItem(i);
      }
    }
  }

  // Here you can check any update
  @override
  void didUpdateWidget(covariant ListPage oldWidget) {
    super.didUpdateWidget(oldWidget);
    _handleAddedItems(oldItems: oldWidget.items, newItems: widget.items);
    _handleRemovedItems(oldItems: oldWidget.items, newItems: widget.items);
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedList(
      key: _listKey,
      initialItemCount: _initialItemCount,
      itemBuilder: (context, index, animation) => SizeTransition(
        sizeFactor: animation,
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text(widget.items[index].content),
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)