未保留 AnimatedList 中小部件的颤动状态

时间:2021-04-17 14:30:51

标签: android flutter dart

我的 AnimatedList 在颤动时遇到问题,下面是 AnimatedList 的以下实现。该列表有效,动画正常,但是当我在点击列表中的一张卡片后调用 clearEverythingButMe() 方法时,当 AnimatedList 删除所有其他项目时,不会保留该小部件的状态并将剩余的项目移回顶部。我不明白为什么会发生这种情况,因为键都被保留了,甚至模型中小部件的哈希码在 clearEverythingButMe() 之后也保持不变。

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gym_notes/exercisesList/exercisesDB.dart';

class NiceList extends StatefulWidget {
  const NiceList({required Key key}) : super(key: key);

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

class NiceListState extends State<NiceList> {
  GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
  late ListModel _list;

  @override
  void initState() {
    super.initState();
    _list = ListModel(
      listKey: _listKey,
      initialItems: <Widget>[],
      removedItemBuilder: _buildRemovedItem,
    );
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      _addCategories();
    });
  }

  void testAdd() {}

  Tween<Offset> _offset = Tween(begin: Offset(0, 3), end: Offset(0, 0));
  Widget _buildItem(
      BuildContext context, int index, Animation<double> animation) {
    return SlideTransition(
        position: animation.drive(_offset), child: _list[index]);
  }

  var shrinkTween = Tween<double>(begin: 0.0, end: 1.0);
  var _tween =
      Tween<Offset>(begin: const Offset(1, 0), end: const Offset(0, 0));
  Widget _buildRemovedItem(
      Widget item, BuildContext context, Animation<double> animation) {
    return SizeTransition(
      axisAlignment: 0.0,
      sizeFactor: shrinkTween
          .animate(new CurvedAnimation(parent: animation, curve: Curves.ease)),
      child: SlideTransition(
          position: _tween.animate(
              new CurvedAnimation(parent: animation, curve: Curves.easeOut)),
          child: item),
    );
  }

  void _addCategories() {
    Future ft = Future(() {});
    ExercisesDB.exercises.forEach((String exercise) {
      ft = ft.then((data) {
        return Future.delayed(const Duration(milliseconds: 30), () {
          _list.insertCategory(_list.length, exercise);
        });
      });
    });
  }

  void removeEverything() {
    _list.clearList();
  }

  @override
  Widget build(BuildContext context) {
    return Scrollbar(
      child: AnimatedList(
        physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
        key: _listKey,
        itemBuilder: _buildItem,
      ),
    );
  }
}

class ListModel {
  ListModel({
    required this.listKey,
    required this.removedItemBuilder,
    Iterable? initialItems,
  }) : _items = List<Widget>.from(initialItems ?? <Widget>[]);

  final GlobalKey<AnimatedListState> listKey;
  final dynamic removedItemBuilder;
  final List<Widget> _items;

  AnimatedListState? get _animatedList => listKey.currentState;

  void insertCategory(int index, String category) {
    _items.insert(index, CategoryCard(Key(category), category, this));
    _animatedList!.insertItem(index);
  }

  void insertExercise(int index, String exercise) {
    _items.insert(index, CategoryCard(Key(exercise), exercise, this));
    _animatedList!.insertItem(index);
  }

  Widget removeAtByKey(Key key) {
    var index =
        _items.indexOf(_items.firstWhere((element) => element.key == key));
    final Widget removedItem = _items.removeAt(index);
    _animatedList!.removeItem(
      index,
      (BuildContext context, Animation<double> animation) {
        return removedItemBuilder(removedItem, context, animation);
      },
    );

    return removedItem;
  }

  Future<void> clearAndExpand(String category) async {
    //first remove everything but
    Future ft = Future(() {});
    _items.forEach((var exercise) {
      ft = ft.then((data) {
        return Future.delayed(const Duration(milliseconds: 10), () {
          if (exercise.key != Key(category)) {
            removeAtByKey(exercise.key!);
          }
        });
      });
    });
    await Future.delayed(Duration(milliseconds: 100));
    //then add the exercises
    var exercises = ExercisesDB.exercisesSub[category];
    exercises!.forEach((element) {
      //for every category in the databse, check to see if the key belongs to them
      ft = ft.then((data) {
        return Future.delayed(const Duration(milliseconds: 10), () {
          insertExercise(length, element);
        });
      });
    });
  }

  void clearEverythingButMe(Key key) {
    Future ft = Future(() {});
    _items.forEach((var exercise) {
      ft = ft.then((data) {
        return Future.delayed(const Duration(milliseconds: 50), () {
          if (exercise.key != key) {
            removeAtByKey(exercise.key!);
          }
        });
      });
    });
  }

  void addExercises(String category) {
    var exercises = ExercisesDB.exercisesSub[category];
    Future ft = Future(() {});
    exercises!.forEach((element) {
      //for every category in the databse, check to see if the key belongs to them
      ft = ft.then((data) {
        return Future.delayed(const Duration(milliseconds: 10), () {
          insertExercise(length - 1, element);
        });
      });
    });
  }

  void addEverythingBackButMe(Key key, String cat_name) {
    Future ft = Future(() {});
    ExercisesDB.exercises.forEach((element) {
      //for every category in the databse, check to see if the key belongs to them
      ft = ft.then((data) {
        return Future.delayed(const Duration(milliseconds: 10), () {
          if (Key(element) != key) {
            var index = ExercisesDB.exercises.indexOf(element);
            insertCategory(index, element);
          }
        });
      });
    });
  }

  void clearList() {
    Future ft = Future(() {});
    _items.forEach((var exercise) {
      ft = ft.then((data) {
        return Future.delayed(const Duration(milliseconds: 10), () {
          removeAt(0);
        });
      });
    });
  }

  Widget removeAt(int index) {
    final Widget removedItem = _items.removeAt(index);
    _animatedList!.removeItem(index,
        (BuildContext context, Animation<double> animation) {
      return removedItemBuilder(removedItem, context, animation);
    }, duration: Duration(milliseconds: 10));

    return removedItem;
  }

  int get length => _items.length;

  Widget operator [](int index) => _items[index];

  int indexOf(Widget item) => _items.indexOf(item);
}


class CategoryCard extends StatefulWidget {
  CategoryCard(Key key, this.categoryName, this.listModel) : super(key: key);
  final String categoryName;
  final ListModel listModel;
  @override
  _CategoryCardState createState() => _CategoryCardState();
}

class _CategoryCardState extends State<CategoryCard>
    with AutomaticKeepAliveClientMixin {
  var count = 0;

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

  @override
  void dispose() {
    super.dispose();
  }

  Future<void> toggle() async {
    count += 1;
    widget.listModel.clearEverythingButMe(widget.key!);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          toggle();
        });
      },
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 4),
        child: Card(
          shadowColor: Colors.black,
          child: Center(
            child: Text(
              "$count ${widget.categoryName}",
              textAlign: TextAlign.center,
              style: TextStyle(fontSize: 34, fontWeight: FontWeight.w900),
            ),
          ),
        ),
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

点击列表中的第一个项目时,在顶部,删除其他所有内容时会保留状态,因为列表不必移动它,但对于其他所有内容,状态中的计数器重置为 0 . 希望有人能解释为什么尽管有键和我在我的有状态小部件中使用了 AutomaticKeepAliveClientMixin ,它的行为仍然如此。

0 个答案:

没有答案