Flutter PageView, могу ли я анимировать удаление элементов из списка?

Я новичок в флаттере и пытаюсь создать анимацию в PageView. если быть точным, я хочу анимировать удаление элемента.

Я пробовал несколько способов оживить его, и, помимо решения, способ, которым вы, ребята, решали бы такую ​​проблему, также был бы полезен для моих навыков флаттера.

Что я пробовал до сих пор:

  • Анимация заполнения и непрозрачности
    • проблема в том, что когда я устанавливаю отступ в setState в onLongPress, он перестраивает виджет и снова переопределяет заполнение с помощью активного или неактивного CardPadding (я думаю)
  • Анимация ширины и высоты
    • Я просто не могу заставить работать обе эти ценности
  • Анимация viewportFraction в PageViewController
    • Не знал бы, как это сделать, и возможно ли это сделать только для определенной "страницы"

Ниже приведен (урезанный) код, который я написал до сих пор.

class Main extends StatefulWidget {
  @override
  _MainState createState() => _MainState();
}

class _MainState extends State<Main> {
  int activeCard = 0;

  EdgeInsets inActiveCardPadding = EdgeInsets.symmetric(vertical: 120.0, horizontal: 20.0);
  EdgeInsets activeCardPadding = EdgeInsets.symmetric(vertical: 105.0, horizontal: 10.0);

  PageController pageController = PageController(
    initialPage: 0,
    viewportFraction: 0.8,
  );

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: <Widget>[
            PageView.builder(
              itemCount: PlantCareApp.plants.length,
              controller: pageController,
              onPageChanged: (activeCardIndex) {
                setState(() {
                  this.activeCard = activeCardIndex;
                });
              },
              itemBuilder: (context, cardIndex) {
                return AnimatedContainer(
                  padding: (activeCard == cardIndex) ? activeCardPadding : inActiveCardPadding;,
                  duration: Duration(milliseconds: 250),
                  child: PlantCard(
                    PlantCareApp.plants[cardIndex],
                    onTap: () {
                      Navigator.pushNamed(context, PlantDetailScreen.route, arguments: PlantCareApp.plants[cardIndex]);
                    },
                    onLongPress: () {
                      setState(() {
                        //
                        // ANIMATE OR TRIGGER ANIMATION HERE
                        //

                        // do the actual removing
                        /*
                          PlantCareApp.plants[cardIndex].remove(); // remove from db
                          PlantCareApp.plants.removeAt(cardIndex); // remove from List
                        */
                      });
                      //PlantCareApp.plants[cardIndex].remove();
                    },
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

Любая помощь будет оценена! Как бы вы, ребята, справились с подобной проблемой или как бы вы справились с этим конкретным вариантом использования.

Я предполагаю, что на самом деле анимация viewportFraction была бы лучше всего, потому что смежные "Pages" тоже движутся навстречу друг другу?

Спасибо!

1 ответ

Я не уверен, что это то, что вы ищете, но начнем.

Один из способов сделать это - просто использовать предоставленные виджеты во Flutter. Два из них вам помогут: AnimatedList и Dismissible.

Теперь вы можете сделать что-то вроде этого:

// define somewhere
final _animatedListGK = GlobalKey<AnimatedListState>();

// put in a function somewhere
return AnimatedList(
  key: _animatedListGK,
  padding: const EdgeInsets.all(0),
  initialItemCount: PlantCareApp.plants.length,
  itemBuilder: (context, index, animation) {
    return FadeTransition(
      opacity: animation,
      child: _buildDismissibleRow(context, index, PlantCareApp.plants[index])
    );
  }
);

Примечание: вам не обязательно использовать _animatedListGK глобальный ключ как таковой, это зависит от того, можете ли вы использовать AnimatedList.of(context)или не. Хотя это более простой способ.

В _animatedListGKпросто глобальный ключ, который обеспечивает доступ кAnimatedList так что вы можете выполнять вставку / удаление с анимацией.

Ваша запрещенная строка может выглядеть примерно так:

Widget _buildDismissibleRow(BuildContext context, int index, PlantModel plantModel) {
    return Dismissible(
      key: ValueKey<String>(plantModel.someKey),
      direction: DismissDirection.startToEnd,
      background: Container(color: Colors.red),
      onDismissed: (direction) {
        // You could use:
        // AnimatedList.of(context)
        _animatedListGK.currentState.removeItem(
          index,
          (context, animation) => Container(),
          duration: Duration.zero
        );
      },
      child: _buildContent(context, index, plantModel)
    );
}

Вы также можете сделать это без запрещенной строки или даже в дочернем элементе запрещенной строки (_buildContent()например). Что-то подобное:

// You could use:
// AnimatedList.of(context)
_animatedListGK.currentState.removeItem(
  index,
  (context, animation) {
    return FadeTransition(
      opacity: CurvedAnimation(parent: animation, curve: Interval(0.5, 1.0)),
      child: SizeTransition(
        sizeFactor: CurvedAnimation(parent: animation, curve: Interval(0.0, 1.0)),
        child: _builContent(context, index, plantModel)
      )
    );
  },
  duration: const Duration(milliseconds: 300)
);

Обратите внимание, как SizeTransition просто "вызывает себя", вызывая _builContent(context, index, plantModel)? Вот как вы можете оживить саму строку (не существующую).

Обязательно посмотрите видео на вышеупомянутых страницах документации! Они помогут понять определенные конструкции.

Предварительный просмотр того, как может выглядеть отклоненное:

Предварительный просмотр того, что SizedTransition может выглядеть так:

Другие вопросы по тегам