前一阵子我尝试复制那个确切的页面/转换,虽然我没有让它看起来很像,但是我确实非常接近。请记住,这是快速组合在一起的,实际上并没有遵循最佳做法或其他任何规定。
重要的部分是Hero窗口小部件,尤其是与它们一起使用的标签-如果它们不匹配,则不会这样做。
import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState();}class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( backgroundColor: Colors.deepPurple, ), body: ListView.builder( itemBuilder: (context, index) { return TileItem(num: index); }, ), ), ); }}class TileItem extends StatelessWidget { final int num; const TileItem({Key key, this.num}) : super(key: key); @override Widget build(BuildContext context) { return Hero( tag: "card$num", child: Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all( Radius.circular(8.0), ), ), clipBehavior: Clip.antiAliasWithSaveLayer, child: Stack( children: <Widget>[ Column( children: <Widget>[ AspectRatio( aspectRatio: 485.0 / 384.0, child: Image.network("https://picsum.photos/485/384?image=$num"), ), Material( child: ListTile( title: Text("Item $num"), subtitle: Text("This is item #$num"), ), ) ], ), Positioned( left: 0.0, top: 0.0, bottom: 0.0, right: 0.0, child: Material( type: MaterialType.transparency, child: InkWell( onTap: () async { await Future.delayed(Duration(milliseconds: 200)); Navigator.push(context,MaterialPageRoute( builder: (context) { return new PageItem(num: num); }, fullscreenDialog: true,), ); }, ), ), ), ], ), ), ); }}class PageItem extends StatelessWidget { final int num; const PageItem({Key key, this.num}) : super(key: key); @override Widget build(BuildContext context) { AppBar appBar = new AppBar( primary: false, leading: IconTheme(data: IconThemeData(color: Colors.white), child: CloseButton()), flexibleSpace: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withOpacity(0.4), Colors.black.withOpacity(0.1), ], ), ), ), backgroundColor: Colors.transparent, ); final MediaQueryData mediaQuery = MediaQuery.of(context); return Stack(children: <Widget>[ Hero( tag: "card$num", child: Material( child: Column( children: <Widget>[ AspectRatio( aspectRatio: 485.0 / 384.0, child: Image.network("https://picsum.photos/485/384?image=$num"), ), Material( child: ListTile( title: Text("Item $num"), subtitle: Text("This is item #$num"), ), ), Expanded( child: Center(child: Text("Some more content goes here!")), ) ], ), ), ), Column( children: <Widget>[ Container( height: mediaQuery.padding.top, ), ConstrainedBox( constraints: BoxConstraints(maxHeight: appBar.preferredSize.height), child: appBar, ) ], ), ]); }}编辑:为了回应评论,我将写一篇关于Hero如何工作的解释(或者至少我认为它如何工作= D)。
基本上,当页面之间开始过渡时,执行过渡的基础机制(或多或少是Navigator的一部分)会在当前页面和新页面中查找任何“英雄”窗口小部件。如果找到英雄,则针对每个页面计算其大小和位置。
在执行页面之间的过渡时,新页面中的英雄将被移到与旧英雄相同的位置上的叠加层,然后将其大小和位置朝其最终大小和位置进行动画处理。(请注意,如果您需要做一些工作,可以更改-
请参阅此博客以获取更多信息)。
这是OP试图实现的目标:
当您点击卡片时,其背景颜色会扩展并变为带有Appbar的脚手架的背景颜色。
最简单的方法是将脚手架本身放入英雄中。在过渡期间,其他任何东西都会使AppBar变得模糊,因为在进行英雄过渡时,它处于覆盖中。请参见下面的代码。请注意,我已经添加了一个类,以使转换过程变慢,因此您可以看到发生了什么,因此要以正常速度查看它,请更改将SlowMaterialPageRoute推回MaterialPageRoute的部分。
That looks something like this:import 'dart:math';import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState();}class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( backgroundColor: Colors.deepPurple, ), body: ListView.builder( itemBuilder: (context, index) { return TileItem(num: index); }, ), ), ); }}Color colorFromNum(int num) { var random = Random(num); var r = random.nextInt(256); var g = random.nextInt(256); var b = random.nextInt(256); return Color.fromARGB(255, r, g, b);}class TileItem extends StatelessWidget { final int num; const TileItem({Key key, this.num}) : super(key: key); @override Widget build(BuildContext context) { return Hero( tag: "card$num", child: Card( color: colorFromNum(num), shape: RoundedRectangleBorder( borderRadius: BorderRadius.all( Radius.circular(8.0), ), ), clipBehavior: Clip.antiAliasWithSaveLayer, child: Stack( children: <Widget>[ Column( children: <Widget>[ AspectRatio( aspectRatio: 485.0 / 384.0, child: Image.network("https://picsum.photos/485/384?image=$num"), ), Material( type: MaterialType.transparency, child: ListTile( title: Text("Item $num"), subtitle: Text("This is item #$num"), ), ) ], ), Positioned( left: 0.0, top: 0.0, bottom: 0.0, right: 0.0, child: Material( type: MaterialType.transparency, child: InkWell( onTap: () async { await Future.delayed(Duration(milliseconds: 200)); Navigator.push(context,SlowMaterialPageRoute( builder: (context) { return new PageItem(num: num); }, fullscreenDialog: true,), ); }, ), ), ), ], ), ), ); }}class PageItem extends StatelessWidget { final int num; const PageItem({Key key, this.num}) : super(key: key); @override Widget build(BuildContext context) { return Hero( tag: "card$num", child: Scaffold( backgroundColor: colorFromNum(num), appBar: AppBar( backgroundColor: Colors.white.withOpacity(0.2), ), ), ); }}class SlowMaterialPageRoute<T> extends MaterialPageRoute<T> { SlowMaterialPageRoute({ WidgetBuilder builder, RouteSettings settings, bool maintainState = true, bool fullscreenDialog = false, }) : super(builder: builder, settings: settings, fullscreenDialog: fullscreenDialog); @override Duration get transitionDuration => const Duration(seconds: 3);}但是,在某些情况下,让整个支架进行过渡可能不是最佳选择-
可能有很多数据,或者被设计为适合特定数量的空间。在这种情况下,您可以选择制作您想要进行的英雄过渡的版本,本质上是一个“伪造”-即具有两层的堆栈,一层是英雄,具有背景色,脚手架等否则,您想在过渡期间显示,而顶层的另一层完全遮盖了底层(即,背景具有100%的不透明度),并且还具有应用程序栏和您想要的其他任何东西。



