我试图在不使用依赖项的情况下从前到后和从后到前翻转卡片,但无法实现。我见过人们使用预定义的翻转卡包但没有依赖性我遇到了麻烦。请帮帮我。
我希望卡在单击“图标按钮”时立即翻转到背面,并在单击“返回”按钮时立即返回。我在不使用动画的情况下尝试了这个想法,并且工作得很好,但是如何实现翻转动画是我觉得困难的地方。
class NotificationItemCard extends StatefulWidget {
const NotificationItemCard({
Key? key,
}) : super(key: key);
@override
State<NotificationItemCard> createState() => _NotificationItemCardState();
}
class _NotificationItemCardState extends State<NotificationItemCard> {
late bool showCardFrontSide;
@override
void initState() {
showCardFrontSide = true;
super.initState();
}
void onChangeView() {
setState(() {
showCardFrontSide = !showCardFrontSide;
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: 140.h,
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
borderRadius: BorderRadius.circular(8),
),
child: showCardFrontSide
? const NotificationCardFrontSide()
: NotificationCardBackSide(
onChangeView: onChangeView,
),
),
showCardFrontSide
? Align(
alignment: const Alignment(0.95, -1),
child: IconButton(
key: const ValueKey("IconButton"),
onPressed: onChangeView,
icon: const Icon(Icons.info_outline),
),
)
: const SizedBox.shrink()
],
);
}
}
class NotificationCardFrontSide extends StatelessWidget {
const NotificationCardFrontSide({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
SizedBox(
key: const ValueKey("FrontSideSizedBox"),
width: 126.w,
child: Center(
child: CircleAvatar(
radius: 50.r,
),
),
),
SizedBox(
key: const ValueKey("FrontSideSizedTextBox"),
width: 222.w,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Premium Private LOBBY",
style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.ellipsis),
key: const ValueKey("FrontSideSizedTextBox1"),
),
Text(
"Prediction Deadline",
// "Prediction Deadline - ${DateConverterUtil.convert(lobby.match.start)}",
style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.ellipsis),
key: const ValueKey("FrontSideSizedTextBox2"),
),
Text(
"Premium Private LOBBY",
style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.ellipsis),
key: const ValueKey("FrontSideSizedTextBox3"),
),
SizedBox(
key: const ValueKey("FrontSideSizedButtonBox"),
width: 150.w,
height: 45.h,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
SizedBox(
key: const ValueKey("FrontSideButtonSizedBox"),
width: 70.w,
child: TextButton(
onPressed: () {},
child: Text(
"deny",
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
SizedBox(
width: 70.w,
child: TextButton(
onPressed: () {},
child: Text(
"deny",
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
],
),
),
],
),
),
],
);
}
}
class NotificationCardBackSide extends StatelessWidget {
final VoidCallback onChangeView;
const NotificationCardBackSide({
Key? key,
required this.onChangeView,
}) : super(key: key);
Widget getTeamLogo(String image) {
return CircleAvatar(
backgroundColor: const Color(0xFFD9D9D9),
radius: 30.r,
child: Image.network(
image,
errorBuilder: (context, error, stackTrace) {
return Text(
"Error",
style: Theme.of(context).textTheme.displayMedium?.copyWith(
color: Colors.red,
),
);
},
height: 65.h,
width: 65.w,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: Text(
"Loading...",
style: Theme.of(context).textTheme.displayMedium,
),
);
},
),
);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
height: 62.h,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
getTeamLogo(""),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Premium Private LOBBY",
style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.clip),
key: const ValueKey("BackSideSizedText1"),
),
Text(
"Prediction Deadline",
// "Prediction Deadline - ${DateConverterUtil.convert(lobby.match.start)}",
style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.clip),
key: const ValueKey("BackSideSizedText2"),
),
],
),
getTeamLogo(""),
],
),
),
SizedBox(
key: const ValueKey("BackSideButtonBox"),
height: 30.h,
width: 100.w,
child: OutlinedButton(
onPressed: onChangeView,
child: const Text("Go Back"),
key: const ValueKey("BackSideButtonText"),
style: ButtonStyle(
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
8.r,
),
),
),
),
),
)
],
);
}
}
您可以使用
AnimatedBuilder
和 Transform
来实现此功能,使用下面的示例:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: Scaffold(body: MyApp())));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late AnimationController _controller;
Widget _front = Card1();
Widget _back = Card2();
late Widget _card = _front;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 600));
_controller.addListener(() {
if (_controller.value >= .5 && _card != _back) {
setState(() => _card = _back);
} else if (_controller.value < .5 && _card != _front) {
setState(() => _card = _front);
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () {
if (_controller.value == 1)
_controller.reverse(from: 1);
else
_controller.forward(from: 0);
},
child: AnimatedBuilder(
animation: _controller,
builder: (c, anim) => Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.0025)
..rotateY(_controller.value * pi),
alignment: FractionalOffset.center,
child: _card,
),
),
),
);
}
}
class Card1 extends StatelessWidget {
const Card1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: 150,
height: 300,
color: Colors.red,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('This is Card1'),
Text('I\'m front of the card'),
],
),
);
}
}
class Card2 extends StatelessWidget {
const Card2({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Transform.scale(
scaleX: -1,
child: Container(
width: 150,
height: 300,
color: Colors.blue,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('This is Card2'),
Text('I\'m back of the card'),
],
),
),
);
}
}
结果:
(感谢这个答案的转换代码)
感谢amir_a14的回答。我修改了你的代码,使卡片可以垂直翻转而不是水平翻转。我还需要支持通过任意数量的边而不是仅两个边进行循环。我希望能够向任一方向翻转,具体取决于您点击卡片的上半部分还是下半部分。
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(debugShowCheckedModeBanner: false, home: Scaffold(body: MyApp())));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late AnimationController _controller;
final sides = const [
Card(color: Colors.red, text: "One"),
Card(color: Colors.blue, text: "Two"),
Card(color: Colors.green, text: "Three"),
];
late Widget _card = sides[0];
int _currentSide = 0;
bool _flippingForward = true;
bool _flipWhenGreater = false;
void startFlipping(bool forward) {
setState(() {
_flipWhenGreater = forward;
_flippingForward = forward;
});
if (forward) {
_controller.forward(from: 0);
_card = sides[_currentSide];
} else {
_controller.reverse(from: 1);
_card = Transform.scale(
scaleY: -1,
child: sides[_currentSide],
);
}
}
void flip() {
_flipWhenGreater = !_flipWhenGreater;
setState(() {
_currentSide = (_currentSide + (_flippingForward ? 1 : -1)) % sides.length;
if (_flippingForward) {
_card = Transform.scale(
scaleY: -1,
child: sides[_currentSide],
);
} else {
_card = sides[_currentSide];
}
});
}
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 600));
_controller.addListener(() {
if (_controller.value >= .5 == _flipWhenGreater) {
flip();
} else if (_controller.value < .5 != _flipWhenGreater) {
flip();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final midY = MediaQuery.sizeOf(context).height / 2;
return Center(
child: GestureDetector(
onTapDown: (tapDownDetails) {
startFlipping(tapDownDetails.globalPosition.dy > midY);
},
child: AnimatedBuilder(
animation: _controller,
builder: (c, anim) => Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.0025)
..rotateX(_controller.value * pi),
alignment: FractionalOffset.center,
child: _card,
),
),
),
);
}
}
class Card extends StatelessWidget {
const Card({required this.color, required this.text, Key? key}) : super(key: key);
final Color color;
final String text;
@override
Widget build(BuildContext context) {
return Container(
width: 300,
height: 300,
color: color,
child: Center(child: Text(text)),
);
}
}