【问题标题】:How do I create an exit animation for items in a list that were not the selected item?如何为列表中不是所选项目的项目创建退出动画?
【发布时间】:2019-08-11 03:00:34
【问题描述】:

我正在使用 ListView.builder 构建按钮的 ListView。当其中一个按钮被点击时,我需要所有其他按钮淡出视图,然后重建 ListView 以仅包含被点击的按钮。

我已经设置了这个来重建 ListView 以仅包含被点击的按钮,但所有其他按钮都会消失。我怎样才能让它们全部淡出?

如果我将每个按钮都包装在 FadeTransition 中,然后调用 _animationController.forward(),它将淡出所有按钮,包括被点击的按钮。我也考虑过为每个按钮创建一个单独的控制器,并且只在那些未被点击的按钮上调用.forward(),但从简单性和性能方面来看,这是正确的方法吗?

到目前为止,这是我的代码:

import 'package:baseball_bluebook_mobile/search_page/nav_button_model.dart';
import 'package:baseball_bluebook_mobile/search_page/recent_button_model.dart';
import 'package:baseball_bluebook_mobile/search_page/search_page_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
import 'package:provider/provider.dart';

import '../colors.dart';
import 'index_picker_button_model.dart';
import 'link_button_model.dart';
import 'nav_button_type.dart';

class Navigation extends StatefulWidget {
  @override
  _NavigationState createState() => _NavigationState();
}

class _NavigationState extends State<Navigation>
    with SingleTickerProviderStateMixin {
  AnimationController _animationController;
  Animation _animation;
  List<NavButtonModel> previousNavButtons = [];

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

    _animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 1000),
    );

    _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
  }

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

  @override
  Widget build(BuildContext context) {
    return Consumer<SearchPageModel>(
      builder: (context, model, child) {
        return Card(
          margin: EdgeInsets.zero,
          shape: ContinuousRectangleBorder(),
          elevation: 4,
          // If we don't have the navButtons from the database yet, show a loading indicator
          child: model.navButtons.isEmpty
              ? _getNavigationLoadingIndicator(model)
              : _getNavigationContent(model),
        );
      },
    );
  }

  Widget _getNavigationLoadingIndicator(SearchPageModel model) {
    return Container(
      height: 48,
      padding: EdgeInsets.only(top: 46),
      child: LinearProgressIndicator(
        semanticsLabel: 'Search navigation loading',
        valueColor: AlwaysStoppedAnimation(CustomColors.blue),
      ),
    );
  }

  Widget _getNavigationContent(SearchPageModel model) {
    _animationController.forward();
    return Container(
      height: 48,
      padding: EdgeInsets.symmetric(vertical: 8, horizontal: 0),
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: model.navButtons.length,
        itemBuilder: (context, index) {
          Widget navButtonWidget;
          // If this is the first button in the list, add left and right padding, otherwise, only add right padding
          if (index == 0) {
            navButtonWidget = Padding(
              padding: EdgeInsets.only(left: 8, right: 8),
              child: _getNavButton(index, model),
            );
          } else {
            navButtonWidget = Padding(
              padding: EdgeInsets.only(right: 8),
              child: _getNavButton(index, model),
            );
          }

          return navButtonWidget;
        },
      ),
    );
  }

  Widget _getNavButton(int index, SearchPageModel model) {
    Widget navButton;

    switch (model.navButtons[index].type) {
      case NavButtonType.recent:
        navButton = _getRecentButton(index, model);
        break;
      case NavButtonType.indexPicker:
        navButton = _getIndexPickerButton(index, model);
        break;
      case NavButtonType.link:
        navButton = _getLinkButton(index, model);
        break;
    }

    if (navButton != null) {
      return FadeTransition(
        opacity: _animationController,
        child: navButton,
      );
    } else {
      return Container();
    }
  }

  Widget _getRecentButton(int index, SearchPageModel model) {
    final recentButtonModel = model.navButtons[index];
    return FlatButton(
      padding: EdgeInsets.symmetric(horizontal: 8),
      color: recentButtonModel.isActive
          ? CustomColors.blue
          : CustomColors.lightGray,
      textColor: Colors.white,
      onPressed: () async {},
      child: Text(recentButtonModel.label),
    );
  }

  Widget _getIndexPickerButton(int index, SearchPageModel model) {
    IndexPickerButtonModel indexPickerButtonModel = model.navButtons[index];
    return FlatButton(
      padding: EdgeInsets.symmetric(horizontal: 8),
      color: indexPickerButtonModel.isActive
          ? CustomColors.blue
          : CustomColors.lightGray,
      textColor: Colors.white,
      onPressed: () async {
        model.setNavigationButtonActiveStatus(
          index,
          !indexPickerButtonModel.isActive,
        );
      },
      child: Text(indexPickerButtonModel.label),
    );
  }

  Widget _getLinkButton(int index, SearchPageModel model) {
    LinkButtonModel linkButtonModel = model.navButtons[index];
    return FlatButton(
      padding: EdgeInsets.symmetric(horizontal: 8),
      color:
          linkButtonModel.isActive ? CustomColors.blue : CustomColors.lightGray,
      textColor: Colors.white,
      onPressed: () async {
        await launch(
          linkButtonModel.url,
          option: new CustomTabsOption(
            toolbarColor: CustomColors.blue,
            enableUrlBarHiding: true,
            showPageTitle: true,
            animation: new CustomTabsAnimation.slideIn(),
            extraCustomTabs: <String>[
              'org.mozilla.firefox',
              'com.microsoft.emmx'
            ],
          ),
        );
      },
      child: Text(linkButtonModel.label),
    );
  }
}

【问题讨论】:

    标签: flutter flutter-animation


    【解决方案1】:

    我没有使用FadeTransition,而是使用纯Animation来实现它。

    首先,每个按钮需要知道它代表什么索引。

         onPressed: () async {
            _clickedBtn = index;
            await launch(
              linkButtonModel.url,
              option: new CustomTabsOption(
                toolbarColor: CustomColors.blue,
                enableUrlBarHiding: true,
                showPageTitle: true,
                animation: new CustomTabsAnimation.slideIn(),
                extraCustomTabs: <String>[
                  'org.mozilla.firefox',
                  'com.microsoft.emmx'
                ],
              ),
            );
          },
    

    其次,使用Animation控制渐变动画。

      Widget _getNavButton(int index, SearchPageModel model) {
        Widget navButton;
    
        switch (model.navButtons[index].type) {
          case NavButtonType.recent:
            navButton = _getRecentButton(index, model);
            break;
          case NavButtonType.indexPicker:
            navButton = _getIndexPickerButton(index, model);
            break;
          case NavButtonType.link:
            navButton = _getLinkButton(index, model);
            break;
        }
    
        if (navButton != null) {
          return AnimatedBuilder(
                animation: _animation,
                builder: (context, child) => Opacity(
                      opacity: _clickedBtn == index ? 1.0 : _animation.value, // 1 to 0
                      child: navButton ,
                    ),
                child: navButton ),
        } else {
          return Container();
        }
      }
    

    就是这样。我没有测试过上面的代码,因为这些代码具有相当的依赖性和复杂性。希望对您有所帮助。

    【讨论】:

    • 看起来这个答案需要使用setState 来更新_clickedBtn,这似乎导致这不起作用,因为一旦setState 被重建,小部件就会被重建叫
    • 如果你想更新你的 UI 的一部分,你可以看看StreamController,并在动画结束时更新你的逻辑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-04
    • 1970-01-01
    • 1970-01-01
    • 2019-01-24
    • 1970-01-01
    相关资源
    最近更新 更多