在带有 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.
);
}
}
在我的 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.
);
}
}