{"id":125519,"date":"2020-06-22T13:17:09","date_gmt":"2020-06-22T05:17:09","guid":{"rendered":"http:\/\/4563.org\/?p=125519"},"modified":"2020-06-22T13:17:09","modified_gmt":"2020-06-22T05:17:09","slug":"%e7%94%a8-flutter-%e5%ae%9e%e7%8e%b0%e6%8e%a2%e6%8e%a2%e5%8d%a1%e7%89%87%e5%b8%83%e5%b1%80","status":"publish","type":"post","link":"http:\/\/4563.org\/?p=125519","title":{"rendered":"\u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40"},"content":{"rendered":"<div>\n<div>\n<div>\n<h1>                  \u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40               <\/h1>\n<p> <\/p>\n<div>\n<div> <span>\u8cc7\u6df1\u5927\u4f6c : xrr2016 <\/span>  <span><i><\/i> 4<\/span> <\/div>\n<div> <\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div isfirst=\"1\"> <\/p>\n<h2>\u524d\u8a00<\/h2>\n<p>\u524d\u51e0\u5929\u5199\u4e86\u4e00\u4e2a Fluter \u63d2\u4ef6 tcard\uff0c\u7528\u6765\u5b9e\u73b0\u7c7b\u4f3c\u4e8e\u63a2\u63a2\u5361\u7247\u7684\u5e03\u5c40\u3002\u6548\u679c\u5982\u4e0b\uff0c\u672c\u6587\u8bb2\u89e3\u5982\u4f55\u4f7f\u7528 <code>Stack<\/code> \u63a7\u4ef6\u5b9e\u73b0\u8fd9\u4e2a\u5e03\u5c40\u3002<\/p>\n<p> <img decoding=\"async\" src=\"http:\/\/4563.org\/wp-content\/uploads\/2020\/06\/20200628_5ef8c79abed24.gif\" alt=\"\u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40\" \/> <\/p>\n<p>\u5728\u7ebf\u67e5\u770b<\/p>\n<h2>\u521d\u8bc6 Stack<\/h2>\n<p><code>Stack<\/code> \u662f\u4e00\u4e2a\u6709\u591a\u5b50\u9879\u7684\u63a7\u4ef6\uff0c\u5b83\u4f1a\u5c06\u81ea\u5df1\u7684\u5b50\u9879\u76f8\u5bf9\u4e8e\u81ea\u8eab\u8fb9\u7f18\u8fdb\u884c\u5b9a\u4f4d\uff0c\u540e\u9762\u7684\u5b50\u9879\u4f1a\u8986\u76d6\u524d\u9762\u7684\u5b50\u9879\u3002\u901a\u5e38\u7528\u6765\u5b9e\u73b0\u5c06\u4e00\u4e2a\u63a7\u4ef6\u8986\u76d6\u4e8e\u53e6\u4e00\u4e2a\u63a7\u4ef6\u4e4b\u4e0a\u7684\u5e03\u5c40\uff0c\u6bd4\u5982\u5728\u4e00\u5f20\u56fe\u7247\u4e0a\u663e\u793a\u4e00\u4e9b\u6587\u5b57\u3002\u5b50\u9879\u7684\u9ed8\u8ba4\u4f4d\u7f6e\u5728 <code>Stack<\/code> \u5de6\u4e0a\u89d2\uff0c\u4e5f\u53ef\u4ee5\u7528 <code>Align<\/code> \u6216\u8005 <code>Positioned<\/code> \u63a7\u4ef6\u5206\u522b\u8fdb\u884c\u5b9a\u4f4d\u3002<\/p>\n<p> <img decoding=\"async\" src=\"http:\/\/4563.org\/wp-content\/uploads\/2020\/06\/20200628_5ef8c79b3b44a.png\" alt=\"\u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40\" \/> <\/p>\n<pre><code>Stack(   children: &lt;Widget&gt;[     Container(       width: 100,       height: 100,       color: Colors.red,     ),     Container(       width: 90,       height: 90,       color: Colors.green,     ),     Container(       width: 80,       height: 80,       color: Colors.blue,     ),   ], ) <\/code><\/pre>\n<p>Stack (Flutter Widget of the Week)<\/p>\n<h2>\u5e03\u5c40\u601d\u8def<\/h2>\n<p>\u8981\u4f7f\u7528 <code>Stack<\/code> \u5b9e\u73b0\u8fd9\u4e2a\u5361\u7247\u5e03\u5c40\u7684\u5927\u81f4\u601d\u8def\u5982\u4e0b<\/p>\n<ol>\n<li>\u9996\u5148\u9700\u8981\u524d\uff0c\u4e2d\uff0c\u540e\u4e09\u4e2a\u5b50\u63a7\u4ef6\uff0c\u4f7f\u7528 <code>Align<\/code> \u63a7\u4ef6\u5b9a\u4f4d\u5728\u5bb9\u5668\u4e2d\u3002<\/li>\n<li>\u9700\u8981\u4e00\u4e2a\u624b\u52bf\u76d1\u542c\u5668 <code>GestureDetector<\/code> \u76d1\u542c\u624b\u6307\u6ed1\u52a8\u3002<\/li>\n<li>\u76d1\u542c\u624b\u6307\u5728\u5c4f\u5e55\u4e0a\u6ed1\u52a8\u540c\u65f6\u66f4\u65b0\u6700\u524d\u9762\u5361\u7247\u7684\u4f4d\u7f6e\u3002<\/li>\n<li>\u5224\u65ad\u79fb\u52a8\u7684\u6a2a\u8f74\u8ddd\u79bb\u8fdb\u884c\u5361\u7247\u4f4d\u7f6e\u53d8\u6362\u52a8\u753b\u6216\u8005\u5361\u7247\u56de\u5f39\u52a8\u753b\u3002<\/li>\n<li>\u5982\u679c\u8fd0\u884c\u4e86\u5361\u7247\u4f4d\u7f6e\u53d8\u6362\u52a8\u753b\u5728\u52a8\u753b\u7ed3\u675f\u540e\u66f4\u65b0\u5361\u7247\u7684\u7d22\u5f15\u503c\u3002<\/li>\n<\/ol>\n<h2>\u5361\u7247\u5e03\u5c40<\/h2>\n<ol>\n<li>\u521b\u5efa <code>Stack<\/code> \u5bb9\u5668\u4ee5\u53ca\u524d\uff0c\u4e2d\uff0c\u540e\u4e09\u4e2a\u5b50\u63a7\u4ef6<\/li>\n<\/ol>\n<pre><code>class MyApp extends StatefulWidget {   @override   _MyAppState createState() =&gt; _MyAppState(); }  class _MyAppState extends State&lt;MyApp&gt; {   \/\/  \u524d\u9762\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _frontCard() {     return Align(       child: Container(         color: Colors.blue,       ),     );   }    \/\/ \u4e2d\u95f4\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _middleCard() {     return Align(       child: Container(         color: Colors.red,       ),     );   }    \/\/ \u540e\u9762\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _backCard() {     return Align(       child: Container(         color: Colors.green,       ),     );   }    @override   Widget build(BuildContext context) {     return MaterialApp(       title: 'TCards demo',       debugShowCheckedModeBanner: false,       home: Scaffold(         body: Center(           child: SizedBox(             width: 300,             height: 400,             child: Stack(               children: [                 \/\/ \u540e\u9762\u7684\u5b50\u9879\u4f1a\u663e\u793a\u5728\u4e0a\u9762\uff0c\u6240\u4ee5\u524d\u9762\u7684\u5361\u7247\u653e\u5728\u6700\u540e                 _backCard(),                 _middleCard(),                 _frontCard(),               ],             ),           ),         ),       ),     );   } } <\/code><\/pre>\n<p> <img decoding=\"async\" src=\"http:\/\/4563.org\/wp-content\/uploads\/2020\/06\/20200628_5ef8c7a1c47c7.png\" alt=\"\u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40\" \/> <\/p>\n<ol>\n<li>\u5bf9\u5b50\u63a7\u4ef6\u5206\u522b\u5b9a\u4f4d\u5e76\u8bbe\u7f6e\u5176\u5c3a\u5bf8<\/li>\n<\/ol>\n<p>\u5b9a\u4f4d\u9700\u8981\u8bbe\u7f6e <code>Align<\/code> \u63a7\u4ef6\u7684 alignment \u5c5e\u6027\uff0c\u4f20\u5165\u4e00\u4e2a <code>Alignment(x, y)<\/code> \u8fdb\u884c\u8bbe\u7f6e\u3002\u8bbe\u7f6e\u5c3a\u5bf8\u9700\u8981\u4f7f\u7528 <code>LayoutBuilder<\/code> \u83b7\u53d6\u5f53\u524d\u7236\u5bb9\u5668\u7684\u5c3a\u5bf8\uff0c\u7136\u540e\u6839\u636e\u5bb9\u5668\u5c3a\u5bf8\u8fdb\u884c\u8ba1\u7b97\u3002<\/p>\n<pre><code>class _MyAppState extends State&lt;MyApp&gt; {   \/\/  \u524d\u9762\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _frontCard(BoxConstraints constraints) {     return Align(       alignment: Alignment(0.0, -0.5),       \/\/ \u4f7f\u7528 SizedBox \u786e\u5b9a\u5361\u7247\u5c3a\u5bf8       child: SizedBox.fromSize(         \/\/ \u8ba1\u7b97\u5361\u7247\u5c3a\u5bf8\uff0c\u76f8\u5bf9\u4e8e\u7236\u5bb9\u5668         size: Size(constraints.maxWidth * 0.9, constraints.maxHeight * 0.9),         child: Container(           color: Colors.blue,         ),       ),     );   }    \/\/ \u4e2d\u95f4\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _middleCard(BoxConstraints constraints) {     return Align(       alignment: Alignment(0.0, 0.0),       child: SizedBox.fromSize(         \/\/ \u8ba1\u7b97\u5361\u7247\u5c3a\u5bf8\uff0c\u76f8\u5bf9\u4e8e\u7236\u5bb9\u5668         size: Size(constraints.maxWidth * 0.85, constraints.maxHeight * 0.9),         child: Container(           color: Colors.red,         ),       ),     );   }    \/\/ \u540e\u9762\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _backCard(BoxConstraints constraints) {     return Align(       alignment: Alignment(0.0, 0.5),       child: SizedBox.fromSize(         \/\/ \u8ba1\u7b97\u5361\u7247\u5c3a\u5bf8\uff0c\u76f8\u5bf9\u4e8e\u7236\u5bb9\u5668         size: Size(constraints.maxWidth * 0.8, constraints.maxHeight * .9),         child: Container(           color: Colors.green,         ),       ),     );   }    @override   Widget build(BuildContext context) {     return MaterialApp(       title: 'TCards demo',       debugShowCheckedModeBanner: false,       home: Scaffold(         body: Center(           child: SizedBox(             width: 300,             height: 400,             child: LayoutBuilder(               builder: (context, constraints) {                 \/\/ \u4f7f\u7528 LayoutBuilder \u83b7\u53d6\u5bb9\u5668\u7684\u5c3a\u5bf8\uff0c\u4f20\u4e2a\u5b50\u9879\u8ba1\u7b97\u5361\u7247\u5c3a\u5bf8                 return Stack(                   children: [                     \/\/ \u540e\u9762\u7684\u5b50\u9879\u4f1a\u663e\u793a\u5728\u4e0a\u9762\uff0c\u6240\u4ee5\u524d\u9762\u7684\u5361\u7247\u653e\u5728\u6700\u540e                     _backCard(constraints),                     _middleCard(constraints),                     _frontCard(constraints),                   ],                 );               },             ),           ),         ),       ),     );   } }  <\/code><\/pre>\n<p> <img decoding=\"async\" src=\"http:\/\/4563.org\/wp-content\/uploads\/2020\/06\/20200628_5ef8c7a31675e.png\" alt=\"\u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40\" \/> <\/p>\n<ol>\n<li>\u66f4\u65b0\u6700\u524d\u9762\u5361\u7247\u4f4d\u7f6e<\/li>\n<\/ol>\n<p>\u5411 <code>Stack<\/code> \u5bb9\u5668\u6dfb\u52a0\u4e00\u4e2a <code>GestureDetector<\/code>\uff0c\u624b\u6307\u5728\u5c4f\u5e55\u4e0a\u79fb\u52a8\u65f6\u66f4\u65b0\u6700\u524d\u9762\u5361\u7247\u7684\u4f4d\u7f6e\u3002<\/p>\n<pre><code>class _MyAppState extends State&lt;MyApp&gt; {   \/\/ \u4fdd\u5b58\u6700\u524d\u9762\u5361\u7247\u7684\u5b9a\u4f4d   Alignment _frontCardAlignment = Alignment(0.0, -0.5);   \/\/ \u4fdd\u5b58\u6700\u524d\u9762\u5361\u7247\u7684\u65cb\u8f6c\u89d2\u5ea6   double _frontCardRotation = 0.0;    \/\/  \u524d\u9762\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _frontCard(BoxConstraints constraints) {     return Align(       alignment: _frontCardAlignment,       \/\/ \u4f7f\u7528 Transform.rotate \u65cb\u8f6c\u5361\u7247       child: Transform.rotate(         angle: (pi \/ 180.0) * _frontCardRotation,         \/\/ \u4f7f\u7528 SizedBox \u786e\u5b9a\u5361\u7247\u5c3a\u5bf8         child: SizedBox.fromSize(           size: Size(constraints.maxWidth * 0.9, constraints.maxHeight * 0.9),           child: Container(             color: Colors.blue,           ),         ),       ),     );   }    \/\/ \u7701\u7565......    @override   Widget build(BuildContext context) {     return MaterialApp(       title: 'TCards demo',       debugShowCheckedModeBanner: false,       home: Scaffold(         body: Center(           child: SizedBox(             width: 300,             height: 400,             child: LayoutBuilder(               builder: (context, constraints) {                 \/\/ \u4f7f\u7528 LayoutBuilder \u83b7\u53d6\u5bb9\u5668\u7684\u5c3a\u5bf8\uff0c\u4f20\u4e2a\u5b50\u9879\u8ba1\u7b97\u5361\u7247\u5c3a\u5bf8                 Size size = MediaQuery.of(context).size;                 double speed = 10.0;                  return Stack(                   children: [                     \/\/ \u540e\u9762\u7684\u5b50\u9879\u4f1a\u663e\u793a\u5728\u4e0a\u9762\uff0c\u6240\u4ee5\u524d\u9762\u7684\u5361\u7247\u653e\u5728\u6700\u540e                     _backCard(constraints),                     _middleCard(constraints),                     _frontCard(constraints),                     \/\/ \u4f7f\u7528\u4e00\u4e2a\u5360\u6ee1\u7236\u5143\u7d20\u7684 GestureDetector \u76d1\u542c\u624b\u6307\u79fb\u52a8                     SizedBox.expand(                       child: GestureDetector(                         onPanDown: (DragDownDetails details) {},                         onPanUpdate: (DragUpdateDetails details) {                           \/\/ \u624b\u6307\u79fb\u52a8\u5c31\u66f4\u65b0\u6700\u524d\u9762\u5361\u7247\u7684 alignment \u5c5e\u6027                           _frontCardAlignment += Alignment(                             details.delta.dx \/ (size.width \/ 2) * speed,                             details.delta.dy \/ (size.height \/ 2) * speed,                           );                           \/\/ \u8bbe\u7f6e\u6700\u524d\u9762\u5361\u7247\u7684\u65cb\u8f6c\u89d2\u5ea6                           _frontCardRotation = _frontCardAlignment.x;                           \/\/ setState \u66f4\u65b0\u754c\u9762                           setState(() {});                         },                         onPanEnd: (DragEndDetails details) {},                       ),                     ),                   ],                 );               },             ),           ),         ),       ),     );   } } <\/code><\/pre>\n<p> <img decoding=\"async\" src=\"http:\/\/4563.org\/wp-content\/uploads\/2020\/06\/20200628_5ef8c7a4d16e2.gif\" alt=\"\u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40\" \/> <\/p>\n<h2>\u5361\u7247\u52a8\u753b<\/h2>\n<p>\u8fd9\u4e2a\u5e03\u5c40\u6709\u4e09\u79cd\u52a8\u753b\uff0c\u6700\u524d\u9762\u5361\u7247\u79fb\u5f00\u7684\u52a8\u753b\uff1b\u540e\u9762\u4e24\u5f20\u5361\u7247\u4f4d\u7f6e\u548c\u5c3a\u5bf8\u53d8\u5316\u7684\u52a8\u753b\uff1b\u6700\u524d\u9762\u5361\u7247\u56de\u5230\u539f\u4f4d\u7684\u52a8\u753b\u3002<\/p>\n<ol>\n<li>\u5224\u65ad\u5361\u7247\u6a2a\u8f74\u79fb\u52a8\u8ddd\u79bb<\/li>\n<\/ol>\n<p>\u5728\u624b\u6307\u79bb\u5f00\u5c4f\u5e55\u65f6\u5224\u65ad\u5361\u7247\u6a2a\u8f74\u7684\u79fb\u52a8\u8ddd\u79bb\uff0c\u5982\u679c\u6700\u524d\u9762\u7684\u5361\u7247\u6a2a\u8f74\u79fb\u52a8\u8ddd\u79bb\u8d85\u8fc7\u9650\u5236\u5c31\u8fd0\u884c\u6362\u4f4d\u52a8\u753b\uff0c\u5426\u5219\u8fd0\u884c\u56de\u5f39\u52a8\u753b\u3002<\/p>\n<pre><code>\/\/ \u6539\u53d8\u4f4d\u7f6e\u7684\u52a8\u753b void _runChangeOrderAnimation() {}  \/\/ \u5361\u7247\u56de\u5f39\u7684\u52a8\u753b void _runReboundAnimation(Offset pixelsPerSecond, Size size) {}  \/\/ \u7701\u7565...  \/\/ \u5361\u7247\u6a2a\u8f74\u8ddd\u79bb\u9650\u5236 final double limit = 10.0;  SizedBox.expand(   child: GestureDetector(     \/\/ \u7701\u7565...     onPanEnd: (DragEndDetails details) {       \/\/ \u5982\u679c\u6700\u524d\u9762\u7684\u5361\u7247\u6a2a\u8f74\u79fb\u52a8\u8ddd\u79bb\u8d85\u8fc7\u9650\u5236\u5c31\u8fd0\u884c\u6362\u4f4d\u52a8\u753b\uff0c\u5426\u5219\u8fd0\u884c\u56de\u5f39\u52a8\u753b       if (_frontCardAlignment.x &gt; limit ||           _frontCardAlignment.x &lt; -limit) {         _runChangeOrderAnimation();       } else {         _runReboundAnimation(           details.velocity.pixelsPerSecond,           size,         );       }     },   ), ), <\/code><\/pre>\n<ol>\n<li>\u5361\u7247\u56de\u5f39\u52a8\u753b<\/li>\n<\/ol>\n<p>\u9996\u5148\u5b9e\u73b0\u5361\u7247\u56de\u5f39\u7684\u52a8\u753b\uff0c\u4f7f\u7528 <code>AnimationController<\/code> \u63a7\u5236\u52a8\u753b\uff0c\u5728 <code>initState<\/code> \u521d\u59cb\u5316\u52a8\u753b\u63a7\u5236\u5668\u3002\u521b\u5efa\u4e00\u4e2a <code>AlignmentTween<\/code> \u8bbe\u7f6e\u52a8\u753b\u8fd0\u52a8\u503c\uff0c\u8d77\u59cb\u503c\u662f\u5361\u7247\u5f53\u524d\u4f4d\u7f6e\uff0c\u6700\u7ec8\u503c\u662f\u5361\u7247\u7684\u9ed8\u8ba4\u4f4d\u7f6e\u3002\u7136\u540e\u5c06\u4e00\u4e2a\u5f39\u7c27\u6a21\u62df <code>SpringSimulation<\/code> \u4f20\u9012\u7ed9\u52a8\u753b\u63a7\u5236\u5668\uff0c\u8ba9\u52a8\u753b\u6a21\u62df\u8fd0\u884c\u3002<\/p>\n<pre><code>class _MyAppState extends State&lt;MyApp&gt; with TickerProviderStateMixin {   \/\/ \u7701\u7565...   \/\/ \u5361\u7247\u56de\u5f39\u52a8\u753b   Animation&lt;Alignment&gt; _reboundAnimation;   \/\/ \u5361\u7247\u56de\u5f39\u52a8\u753b\u63a7\u5236\u5668   AnimationController _reboundController;    \/\/ \u7701\u7565...    \/\/ \u5361\u7247\u56de\u5f39\u7684\u52a8\u753b   void _runReboundAnimation(Offset pixelsPerSecond, Size size) {     \/\/ \u521b\u5efa\u52a8\u753b\u503c     _reboundAnimation = _reboundController.drive(       AlignmentTween(         \/\/ \u8d77\u59cb\u503c\u662f\u5361\u7247\u5f53\u524d\u4f4d\u7f6e\uff0c\u6700\u7ec8\u503c\u662f\u5361\u7247\u7684\u9ed8\u8ba4\u4f4d\u7f6e         begin: _frontCardAlignment,         end: Alignment(0.0, -0.5),       ),     );     \/\/ \u8ba1\u7b97\u5361\u7247\u8fd0\u52a8\u901f\u5ea6     final double unitsPerSecondX = pixelsPerSecond.dx \/ size.width;     final double unitsPerSecondY = pixelsPerSecond.dy \/ size.height;     final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);     final unitVelocity = unitsPerSecond.distance;     \/\/ \u521b\u5efa\u5f39\u7c27\u6a21\u62df\u7684\u5b9a\u4e49     const spring = SpringDescription(mass: 30, stiffness: 1, damping: 1);     \/\/ \u521b\u5efa\u5f39\u7c27\u6a21\u62df     final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);     \/\/ \u6839\u636e\u7ed9\u5b9a\u7684\u6a21\u62df\u8fd0\u884c\u52a8\u753b     _reboundController.animateWith(simulation);     \/\/ \u91cd\u7f6e\u65cb\u8f6c\u503c     _frontCardRotation = 0.0;     setState(() {});   }    @override   void initState() {     super.initState();     \/\/ \u521d\u59cb\u5316\u56de\u5f39\u7684\u52a8\u753b\u63a7\u5236\u5668     _reboundController = AnimationController(vsync: this)       ..addListener(() {         setState(() {           \/\/ \u52a8\u753b\u8fd0\u884c\u65f6\u66f4\u65b0\u6700\u524d\u9762\u5361\u7247\u7684 alignment \u5c5e\u6027           _frontCardAlignment = _reboundAnimation.value;         });       });   }   \/\/ \u7701\u7565... } <\/code><\/pre>\n<p> <img decoding=\"async\" src=\"http:\/\/4563.org\/wp-content\/uploads\/2020\/06\/20200628_5ef8c7a6cfed4.gif\" alt=\"\u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40\" \/> <\/p>\n<ol>\n<li>\u5361\u7247\u6362\u4f4d\u52a8\u753b<\/li>\n<\/ol>\n<p>\u5361\u7247\u6362\u4f4d\u52a8\u753b\u5c31\u662f\u5c06\u6700\u524d\u9762\u7684\u5361\u7247\u79fb\u9664\u53ef\u89c6\u533a\uff0c\u5c06\u4e2d\u95f4\u7684\u5361\u7247\u79fb\u52a8\u5230\u6700\u524d\u9762\uff0c\u5c06\u6700\u540e\u7684\u5361\u7247\u79fb\u52a8\u5230\u4e2d\u95f4\uff0c\u7136\u540e\u65b0\u5efa\u4e00\u4e2a\u6700\u540e\u9762\u7684\u5361\u7247\u3002\u5728\u5361\u7247\u66f4\u6362\u4f4d\u7f6e\u7684\u540c\u65f6\u9700\u8981\u6539\u53d8\u5361\u7247\u7684\u5c3a\u5bf8\uff0c\u4f4d\u7f6e\u52a8\u753b\u548c\u5c3a\u5bf8\u52a8\u753b\u540c\u65f6\u8fdb\u884c\u3002\u9996\u5148\u5b9a\u4e49\u6bcf\u4e2a\u5361\u7247\u8fd0\u52a8\u65f6\u7684\u52a8\u753b\u503c<\/p>\n<pre><code>\/\/\/ \u5361\u7247\u5c3a\u5bf8 class CardSizes {   static Size front(BoxConstraints constraints) {     return Size(constraints.maxWidth * 0.9, constraints.maxHeight * 0.9);   }    static Size middle(BoxConstraints constraints) {     return Size(constraints.maxWidth * 0.85, constraints.maxHeight * 0.9);   }    static Size back(BoxConstraints constraints) {     return Size(constraints.maxWidth * 0.8, constraints.maxHeight * .9);   } }  \/\/\/ \u5361\u7247\u4f4d\u7f6e class CardAlignments {   static Alignment front = Alignment(0.0, -0.5);   static Alignment middle = Alignment(0.0, 0.0);   static Alignment back = Alignment(0.0, 0.5); }  \/\/\/ \u5361\u7247\u8fd0\u52a8\u52a8\u753b class CardAnimations {   \/\/\/ \u6700\u524d\u9762\u5361\u7247\u7684\u6d88\u5931\u52a8\u753b\u503c   static Animation&lt;Alignment&gt; frontCardDisappearAnimation(     AnimationController parent,     Alignment beginAlignment,   ) {     return AlignmentTween(       begin: beginAlignment,       end: Alignment(         beginAlignment.x &gt; 0             ? beginAlignment.x + 30.0             : beginAlignment.x - 30.0,         0.0,       ),     ).animate(       CurvedAnimation(         parent: parent,         curve: Interval(0.0, 0.5, curve: Curves.easeIn),       ),     );   }    \/\/\/ \u4e2d\u95f4\u5361\u7247\u4f4d\u7f6e\u53d8\u6362\u52a8\u753b\u503c   static Animation&lt;Alignment&gt; middleCardAlignmentAnimation(     AnimationController parent,   ) {     return AlignmentTween(       begin: CardAlignments.middle,       end: CardAlignments.front,     ).animate(       CurvedAnimation(         parent: parent,         curve: Interval(0.2, 0.5, curve: Curves.easeIn),       ),     );   }    \/\/\/ \u4e2d\u95f4\u5361\u7247\u5c3a\u5bf8\u53d8\u6362\u52a8\u753b\u503c   static Animation&lt;Size&gt; middleCardSizeAnimation(     AnimationController parent,     BoxConstraints constraints,   ) {     return SizeTween(       begin: CardSizes.middle(constraints),       end: CardSizes.front(constraints),     ).animate(       CurvedAnimation(         parent: parent,         curve: Interval(0.2, 0.5, curve: Curves.easeIn),       ),     );   }    \/\/\/ \u6700\u540e\u9762\u5361\u7247\u4f4d\u7f6e\u53d8\u6362\u52a8\u753b\u503c   static Animation&lt;Alignment&gt; backCardAlignmentAnimation(     AnimationController parent,   ) {     return AlignmentTween(       begin: CardAlignments.back,       end: CardAlignments.middle,     ).animate(       CurvedAnimation(         parent: parent,         curve: Interval(0.4, 0.7, curve: Curves.easeIn),       ),     );   }    \/\/\/ \u6700\u540e\u9762\u5361\u7247\u5c3a\u5bf8\u53d8\u6362\u52a8\u753b\u503c   static Animation&lt;Size&gt; backCardSizeAnimation(     AnimationController parent,     BoxConstraints constraints,   ) {     return SizeTween(       begin: CardSizes.back(constraints),       end: CardSizes.middle(constraints),     ).animate(       CurvedAnimation(         parent: parent,         curve: Interval(0.4, 0.7, curve: Curves.easeIn),       ),     );   } } <\/code><\/pre>\n<p>\u4f7f\u7528\u4e00\u4e2a <code>AnimationController<\/code> \u63a7\u5236\u52a8\u753b\u8fd0\u884c\uff0c\u52a8\u753b\u8fd0\u884c\u65f6\u5728\u5361\u7247\u4e0a\u5e94\u7528\u4ee5\u4e0a\u7684\u52a8\u753b\u503c\uff0c\u5426\u5219\u4f7f\u7528\u5361\u7247\u9ed8\u8ba4\u7684\u4f4d\u7f6e\u548c\u5c3a\u5bf8\u3002<\/p>\n<pre><code>class _MyAppState extends State&lt;MyApp&gt; with TickerProviderStateMixin {    \/\/ \u7701\u7565...    \/\/ \u5361\u7247\u4f4d\u7f6e\u53d8\u6362\u52a8\u753b\u63a7\u5236\u5668   AnimationController _cardChangeController;    \/\/  \u524d\u9762\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _frontCard(BoxConstraints constraints) {     \/\/ \u5224\u65ad\u52a8\u753b\u662f\u5426\u5728\u8fd0\u884c     bool forward = _cardChangeController.status == AnimationStatus.forward;      \/\/ \u4f7f\u7528 Transform.rotate \u65cb\u8f6c\u5361\u7247     Widget rotate = Transform.rotate(       angle: (pi \/ 180.0) * _frontCardRotation,       \/\/ \u4f7f\u7528 SizedBox \u786e\u5b9a\u5361\u7247\u5c3a\u5bf8       child: SizedBox.fromSize(         size: CardSizes.front(constraints),         child: Container(           color: Colors.blue,         ),       ),     );      \/\/ \u5728\u52a8\u753b\u8fd0\u884c\u65f6\u4f7f\u7528\u52a8\u753b\u503c     if (forward) {       return Align(         alignment: CardAnimations.frontCardDisappearAnimation(           _cardChangeController,           _frontCardAlignment,         ).value,         child: rotate,       );     }      \/\/ \u5426\u5219\u4f7f\u7528\u9ed8\u8ba4\u503c     return Align(       alignment: _frontCardAlignment,       child: rotate,     );   }    \/\/ \u4e2d\u95f4\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _middleCard(BoxConstraints constraints) {     \/\/ \u5224\u65ad\u52a8\u753b\u662f\u5426\u5728\u8fd0\u884c     bool forward = _cardChangeController.status == AnimationStatus.forward;     Widget child = Container(color: Colors.red);      \/\/ \u5728\u52a8\u753b\u8fd0\u884c\u65f6\u4f7f\u7528\u52a8\u753b\u503c     if (forward) {       return Align(         alignment: CardAnimations.middleCardAlignmentAnimation(           _cardChangeController,         ).value,         child: SizedBox.fromSize(           size: CardAnimations.middleCardSizeAnimation(             _cardChangeController,             constraints,           ).value,           child: child,         ),       );     }      \/\/ \u5426\u5219\u4f7f\u7528\u9ed8\u8ba4\u503c     return Align(       alignment: CardAlignments.middle,       child: SizedBox.fromSize(         size: CardSizes.middle(constraints),         child: child,       ),     );   }    \/\/ \u540e\u9762\u7684\u5361\u7247\uff0c\u4f7f\u7528 Align \u5b9a\u4f4d   Widget _backCard(BoxConstraints constraints) {     \/\/ \u5224\u65ad\u52a8\u753b\u662f\u5426\u5728\u8fd0\u884c     bool forward = _cardChangeController.status == AnimationStatus.forward;     Widget child = Container(color: Colors.green);      \/\/ \u5728\u52a8\u753b\u8fd0\u884c\u65f6\u4f7f\u7528\u52a8\u753b\u503c     if (forward) {       return Align(         alignment: CardAnimations.backCardAlignmentAnimation(           _cardChangeController,         ).value,         child: SizedBox.fromSize(           size: CardAnimations.backCardSizeAnimation(             _cardChangeController,             constraints,           ).value,           child: child,         ),       );     }      \/\/ \u5426\u5219\u4f7f\u7528\u9ed8\u8ba4\u503c     return Align(       alignment: CardAlignments.back,       child: SizedBox.fromSize(         size: CardSizes.back(constraints),         child: child,       ),     );   }    \/\/ \u6539\u53d8\u4f4d\u7f6e\u7684\u52a8\u753b   void _runChangeOrderAnimation() {     _cardChangeController.reset();     _cardChangeController.forward();   }    \/\/ \u7701\u7565...    @override   void initState() {     super.initState();     \/\/ \u7701\u7565...      \/\/ \u521d\u59cb\u5316\u5361\u7247\u6362\u4f4d\u52a8\u753b\u63a7\u5236\u5668     _cardChangeController = AnimationController(       duration: Duration(milliseconds: 1000),       vsync: this,     )       ..addListener(() =&gt; setState(() {}))       ..addStatusListener((status) {         if (status == AnimationStatus.completed) {           \/\/ \u52a8\u753b\u8fd0\u884c\u7ed3\u675f\u540e\u91cd\u7f6e\u4f4d\u7f6e\u548c\u65cb\u8f6c           _frontCardRotation = 0.0;           _frontCardAlignment = CardAlignments.front;           setState(() {});         }       });   }   \/\/ \u7701\u7565... } <\/code><\/pre>\n<p> <img decoding=\"async\" src=\"http:\/\/4563.org\/wp-content\/uploads\/2020\/06\/20200628_5ef8c7aa85369.gif\" alt=\"\u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40\" \/> <\/p>\n<h2>\u6570\u636e\u66f4\u65b0<\/h2>\n<p>\u4e3b\u9898\u5185\u5bb9\u957f\u5ea6\u4e0d\u80fd\u8d85\u8fc7 20000 \u4e2a\u5b57\u7b26\u3002\u3002\u3002\u8d85\u957f\u9650\u5236\uff0c\u5168\u6587\u5730\u5740\u5728\u6b64 \u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03\u5c40<\/p>\n<\/p><\/div>\n<div> <b>\u5927\u4f6c\u6709\u8a71\u8aaa<\/b> (<span>4<\/span>)        <\/div>\n<div> <\/div>\n<\/p><\/div>\n<\/p><\/div>\n<ul>\n<li data-pid=\"2152857\" data-uid=\"2\">\n<div>\n<div>\n<div> <span>\u8cc7\u6df1\u5927\u4f6c : suzic <\/span>  <\/div>\n<div> <i title=\"\u5f15\u7528\"><\/i>  <span>          <\/span> <\/div>\n<\/p><\/div>\n<div>                                                             \u611f\u8c22\u5206\u4eab\uff0c\u6536\u85cf\u4e86                                                            <\/div>\n<\/p><\/div>\n<\/li>\n<li data-pid=\"2152858\" data-uid=\"2\">\n<div>\n<div>\n<div> <span>\u8cc7\u6df1\u5927\u4f6c : Roung <\/span>  <\/div>\n<div> <i title=\"\u5f15\u7528\"><\/i>  <span>          <\/span> <\/div>\n<\/p><\/div>\n<div>                                                             \u76f4\u63a5\u611f\u8c22\u4e86\uff0c\u628a\u4e1c\u897f\u7801\u5230 v2 \u4e5f\u4e0d\u5bb9\u6613\u3002                                                            <\/div>\n<\/p><\/div>\n<\/li>\n<li data-pid=\"2152859\" data-uid=\"2\">\n<div>\n<div>\n<div> <span>\u8cc7\u6df1\u5927\u4f6c : notEnough <\/span>  <\/div>\n<div> <i title=\"\u5f15\u7528\"><\/i>  <span>          <\/span> <\/div>\n<\/p><\/div>\n<div>                                                             \u5b66\u4e60\u4e86\uff0c\u5927\u4f6c\u725b\u903c                                                            <\/div>\n<\/p><\/div>\n<\/li>\n<li data-pid=\"2152860\" data-uid=\"2\">\n<div>\n<div>\n<div> <span>\u4e3b<\/span> <span>\u8cc7\u6df1\u5927\u4f6c : xrr2016 <\/span>  <\/div>\n<div> <i title=\"\u5f15\u7528\"><\/i>  <span>          <\/span> <\/div>\n<\/p><\/div>\n<div>                                                             @notEnough \u54c8\u54c8\uff0c\u53d6\u7b11\u4e86\uff0c\u4e0d\u662f\u5927\u4f6c                                                            <\/div>\n<\/p><\/div>\n<\/li>\n<li>\n","protected":false},"excerpt":{"rendered":"<p>\u7528 Flutter \u5b9e\u73b0\u63a2\u63a2\u5361\u7247\u5e03&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[],"tags":[],"_links":{"self":[{"href":"http:\/\/4563.org\/index.php?rest_route=\/wp\/v2\/posts\/125519"}],"collection":[{"href":"http:\/\/4563.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/4563.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/4563.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/4563.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=125519"}],"version-history":[{"count":0,"href":"http:\/\/4563.org\/index.php?rest_route=\/wp\/v2\/posts\/125519\/revisions"}],"wp:attachment":[{"href":"http:\/\/4563.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=125519"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/4563.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=125519"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/4563.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=125519"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}