在带有 Flutter 的对话框中的两个单独的卡片 类 之间切换动画

Toggle an animation between two separate Card classes in a Dialog with Flutter

在我的 Flutter 应用程序中,我有一个函数可以打开一个显示两个有状态卡片的对话框。我希望做到这一点,当按下一张卡片时,它会亮起并且动画会 运行。然后,另一张牌会消失。但是,在当前配置中,这两个选项可以同时 selected,这在生产设置中可能会使用户感到困惑。当对话框打开时,它应该如下所示:

然后用户应该能够 select 一个或另一个,按钮应该像这样来回切换:

但是,根据我当前的代码设置方式,可以同时切换按钮,如下所示:

我一直无法弄清楚如何更改我的代码的工作方式以适应这种情况。我尝试过使用 Flutter 的原生 ToggleButtons class,但我无法让它满足我在这个项目中的需求。这是代码:

class CustomRoomStateCard extends StatefulWidget {
  final bool isPublicCard; // true: card is green, false: card is red
  static bool
      choice; //true: user's room will be public, false: user's room will be private

  CustomRoomStateCard({this.isPublicCard});
  @override
  _CustomRoomStateCardState createState() => _CustomRoomStateCardState();
}

class _CustomRoomStateCardState extends State<CustomRoomStateCard>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation animation;

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

    controller = AnimationController(
      upperBound: 1,
      duration: Duration(milliseconds: 200),
      vsync: this,
    );
    animation = ColorTween(
            begin: (widget.isPublicCard == true
                ? Colors.green[100]
                : Colors.red[100]),
            end: (widget.isPublicCard == true ? Colors.green : Colors.red))
        .animate(controller);
    controller.addListener(() {
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onTap: () {
          setState(() {
            if (widget.isPublicCard == true) {
              CustomRoomStateCard.choice = true;
            } else {
              CustomRoomStateCard.choice = false;
            }
            if (animation.isCompleted) {
              controller.reverse();
              CustomRoomStateCard.choice = false;

              print("choice is ${CustomRoomStateCard.choice}");
            } else {
              controller.forward();
              print("choice is ${CustomRoomStateCard.choice}");
            }
          });
        },
        child: Card(
          shape:
              RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
          color: animation.value,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Padding(
                padding: EdgeInsets.all(15.0),
                child: widget.isPublicCard
                    ? Icon(Icons.radar, color: Colors.white)
                    : Icon(Icons.shield, color: Colors.white),
              ),
              Padding(
                  padding: EdgeInsets.all(15.0),
                  child: Text(
                    widget.isPublicCard ? "Public" : "Private",
                    style: kBoldText.copyWith(color: Colors.white),
                    textAlign: TextAlign.center,
                  ))
            ],
          ),
        ));
  }
}

Future<void> showPublicPrivateChoiceDialog(BuildContext context) {
  List<bool> toggledValues = [false, false]; // an idea
  return showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(20.0))),
          title: Text(
            "Set room privacy level",
            style: TextStyle(fontWeight: FontWeight.bold),
          ),
          content: Container(
            height: MediaQuery.of(context).size.height * 0.2,
            width: MediaQuery.of(context).size.height * 0.7,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Expanded(
                  child: CustomRoomStateCard(
                    isPublicCard: true,
                  ),
                ),
                Expanded(
                  child: CustomRoomStateCard(
                    isPublicCard: false,
                  ),
                )
              ],
            ),
          ),
          actions: [
            TextButton(
                onPressed: () {
                  print("the choice is ${CustomRoomStateCard.choice}");
                  isBroadcasting = CustomRoomStateCard.choice ??
                      true; // default to true in case they don't press anything
                  Navigator.pop(context);
                  return;
                },
                child: Text(
                  "Create",
                  style: TextStyle(fontWeight: FontWeight.bold),
                ))
          ],
        );
      });
}

我的第一个想法是创建一个布尔变量,如果其中一张卡片已经处于活动状态,则该变量为真。当我按下一张卡片时,它会检查这个变量,相应地改变自己,但随后也必须在另一张卡片中调用 setState() ,我现在不确定该怎么做。我怎样才能让这两张卡来回切换而不是同时激活?如有任何帮助,我们将不胜感激!

这取决于您需要对动画进行多少控制。但是如果你不需要控件,你可以使用 AnimatedOpacity(..) 来实现这个。

看这个例子:

import 'package:flutter/material.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool isPublic = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
          child: Column(
        children: [
          AnimatedOpacity(
            duration: const Duration(milliseconds: 500),
            opacity: isPublic ? 1.0 : 0.20,
            child: Card(
              child: InkWell(
                onTap: () {
                  setState(() {
                    isPublic = true;
                  });

                  print('is public = true');
                },
                child: SizedBox(
                  child: Text('Public'),
                  height: 120,
                  width: 120,
                ),
              ),
              color: Colors.green[600],
            ),
          ),
          SizedBox(height: 20),
          AnimatedOpacity(
            duration: const Duration(milliseconds: 500),
            opacity: !isPublic ? 1.0 : 0.20,
            child: Card(
              child: InkWell(
                onTap: () {
                  setState(() {
                    isPublic = false;
                  });

                  print('is public = false');
                },
                child: SizedBox(
                  child: Text('Private'),
                  height: 120,
                  width: 120,
                ),
              ),
              color: Colors.red[600],
            ),
          ),
        ],
      )), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}