首页 > 解决方案 > AnimatedList 中小部件的颤振状态未保留

问题描述

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 在有状态小部件中的使用,但可以解释为什么它的行为如此。

标签: androidflutterdart

解决方案


推荐阅读