如何让 ListView 在转换到另一条路线时保持滚动?
How to make ListView preserve its scroll when transitioning to another route?
我想在我的 flutter 应用程序中完成向右滑动的过渡。
问题是路由转换有点创建我要从中转换的页面的新实例,因此 ListView
滚动重置。
这就是我创建新路线的方式
/// @oldRoute needed cause this route transition utilizes `SlideStackRightRoute`
Route createSettingsRoute(Widget oldRoute) {
return SlideStackRightRoute(exitPage: oldRoute, enterPage: SettingsRoute());
}
最后是幻灯片切换class本身
import 'package:flutter/material.dart';
/// Creates cupertino-like route transition, where new route pushes old from right to left
class SlideStackRightRoute extends PageRouteBuilder {
final Widget enterPage;
final Widget exitPage;
static var exBegin = Offset(0.0, 0.0);
static var exEnd = Offset(-0.5, 0.0);
static var entBegin = Offset(1.0, 0.0);
static var entEnd = Offset.zero;
static var curveIn = Curves.easeOutSine;
static var curveOut = Curves.easeInSine;
SlideStackRightRoute({@required this.exitPage, @required this.enterPage})
: super(
transitionDuration: Duration(milliseconds: 400),
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
enterPage,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
Stack(
children: <Widget>[
SlideTransition(
position: Tween(begin: exBegin, end: exEnd)
.chain(CurveTween(curve: curveIn))
.chain(CurveTween(curve: curveOut))
.animate(animation),
child: Container(
foregroundDecoration: BoxDecoration(
color: Colors.black.withOpacity(animation.value / 2),
),
child: exitPage),
),
SlideTransition(
position: Tween(begin: entBegin, end: entEnd)
.chain(CurveTween(curve: curveIn))
.chain(CurveTween(curve: curveOut))
.animate(animation),
child: enterPage,
)
],
),
);
}
我在问题中创建“滑出”动画的方式不正确。就框架而言,它被称为 secondaryAnimation
要制作自己的二次元动画,需要使用PageRouteBuilder
transitionBuilder属性
例子可以是下面的代码,它会产生这样的动画,ListView没有问题
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primaryColor: Colors.white),
initialRoute: '/',
onGenerateInitialRoutes: (initialRoute) => [createCustomTransition(HomeScreen())],
onGenerateRoute: (settings) {
if (settings.name == '1') {
return createCustomTransition(SomeScreen());
}
return createCustomTransition(OtherScreen());
},
debugShowCheckedModeBanner: false,
);
}
}
/// Will create a custom route transition for you.
PageRouteBuilder createCustomTransition(Widget screen) {
return PageRouteBuilder(
transitionDuration: const Duration(milliseconds: 700),
reverseTransitionDuration: const Duration(milliseconds: 700),
pageBuilder: (context, animation, secondaryAnimation) => screen,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final slideAnimation = Tween(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
curve: Curves.easeOutCubic,
reverseCurve: Curves.easeInCubic,
parent: animation,
));
final slideOutAnimation = Tween(
begin: Offset.zero,
end: const Offset(-0.3, 0.0),
).animate(CurvedAnimation(
curve: Curves.easeOutCubic,
reverseCurve: Curves.easeInCubic,
parent: secondaryAnimation,
));
return SlideTransition(
position: slideAnimation,
child: SlideTransition(
position: slideOutAnimation,
child: child,
),
);
},
);
}
class HomeScreen extends StatelessWidget {
HomeScreen({Key key}) : super(key: key);
final List<int> list = List.generate(1000, (index) => index);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) => ListTile(
title: Center(
child: Text(list[index].toString()),
)
),
)
),
ElevatedButton(
child: const Text('go to some screen'),
onPressed: () {
Navigator.of(context).pushNamed('1');
},
),
],
),
),
);
}
}
class SomeScreen extends StatelessWidget {
const SomeScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red,
appBar: AppBar(),
body: Center(
child: ElevatedButton(
child: const Text('go to other screen'),
onPressed: () {
Navigator.of(context).pushNamed('');
},
),
),
);
}
}
class OtherScreen extends StatelessWidget {
const OtherScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
appBar: AppBar(),
);
}
}
根据文档 PageRouteBuilder
是
A utility class for defining one-off page routes in terms of callbacks.
它非常适合一般用途,但如果您要构建更复杂的东西,我建议您查看一些框架页面路由动画实现以及它们之间的不同 class 关系
- widgets/page_transitions_theme.dart - 各种页面路由构建器
- material/route - android 路线实施
- cupertino/route - cupertion 路由实现
我解决这个问题的方法是将偏移量保存到页面 class 中的静态变量中,并将控制器添加到列表视图构建器中。例如
static double _previousScrollOffset = 0;
final ScrollController _controller = new ScrollController(initialScrollOffset: _previousScrollOffset);
然后,每当我导航到新路线时,我都会保存状态。例如
onTap: () {
_previousScrollOffset = _controller.offset;
Navigator.pushNamed<bool>(
context, ROUTE_ADD_HOUSING + "/" + housing.uuid);
},
我想在我的 flutter 应用程序中完成向右滑动的过渡。
问题是路由转换有点创建我要从中转换的页面的新实例,因此 ListView
滚动重置。
这就是我创建新路线的方式
/// @oldRoute needed cause this route transition utilizes `SlideStackRightRoute`
Route createSettingsRoute(Widget oldRoute) {
return SlideStackRightRoute(exitPage: oldRoute, enterPage: SettingsRoute());
}
最后是幻灯片切换class本身
import 'package:flutter/material.dart';
/// Creates cupertino-like route transition, where new route pushes old from right to left
class SlideStackRightRoute extends PageRouteBuilder {
final Widget enterPage;
final Widget exitPage;
static var exBegin = Offset(0.0, 0.0);
static var exEnd = Offset(-0.5, 0.0);
static var entBegin = Offset(1.0, 0.0);
static var entEnd = Offset.zero;
static var curveIn = Curves.easeOutSine;
static var curveOut = Curves.easeInSine;
SlideStackRightRoute({@required this.exitPage, @required this.enterPage})
: super(
transitionDuration: Duration(milliseconds: 400),
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
enterPage,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
Stack(
children: <Widget>[
SlideTransition(
position: Tween(begin: exBegin, end: exEnd)
.chain(CurveTween(curve: curveIn))
.chain(CurveTween(curve: curveOut))
.animate(animation),
child: Container(
foregroundDecoration: BoxDecoration(
color: Colors.black.withOpacity(animation.value / 2),
),
child: exitPage),
),
SlideTransition(
position: Tween(begin: entBegin, end: entEnd)
.chain(CurveTween(curve: curveIn))
.chain(CurveTween(curve: curveOut))
.animate(animation),
child: enterPage,
)
],
),
);
}
我在问题中创建“滑出”动画的方式不正确。就框架而言,它被称为 secondaryAnimation
要制作自己的二次元动画,需要使用PageRouteBuilder
transitionBuilder属性
例子可以是下面的代码,它会产生这样的动画,ListView没有问题
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primaryColor: Colors.white),
initialRoute: '/',
onGenerateInitialRoutes: (initialRoute) => [createCustomTransition(HomeScreen())],
onGenerateRoute: (settings) {
if (settings.name == '1') {
return createCustomTransition(SomeScreen());
}
return createCustomTransition(OtherScreen());
},
debugShowCheckedModeBanner: false,
);
}
}
/// Will create a custom route transition for you.
PageRouteBuilder createCustomTransition(Widget screen) {
return PageRouteBuilder(
transitionDuration: const Duration(milliseconds: 700),
reverseTransitionDuration: const Duration(milliseconds: 700),
pageBuilder: (context, animation, secondaryAnimation) => screen,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final slideAnimation = Tween(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
curve: Curves.easeOutCubic,
reverseCurve: Curves.easeInCubic,
parent: animation,
));
final slideOutAnimation = Tween(
begin: Offset.zero,
end: const Offset(-0.3, 0.0),
).animate(CurvedAnimation(
curve: Curves.easeOutCubic,
reverseCurve: Curves.easeInCubic,
parent: secondaryAnimation,
));
return SlideTransition(
position: slideAnimation,
child: SlideTransition(
position: slideOutAnimation,
child: child,
),
);
},
);
}
class HomeScreen extends StatelessWidget {
HomeScreen({Key key}) : super(key: key);
final List<int> list = List.generate(1000, (index) => index);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) => ListTile(
title: Center(
child: Text(list[index].toString()),
)
),
)
),
ElevatedButton(
child: const Text('go to some screen'),
onPressed: () {
Navigator.of(context).pushNamed('1');
},
),
],
),
),
);
}
}
class SomeScreen extends StatelessWidget {
const SomeScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red,
appBar: AppBar(),
body: Center(
child: ElevatedButton(
child: const Text('go to other screen'),
onPressed: () {
Navigator.of(context).pushNamed('');
},
),
),
);
}
}
class OtherScreen extends StatelessWidget {
const OtherScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
appBar: AppBar(),
);
}
}
根据文档 PageRouteBuilder
是
A utility class for defining one-off page routes in terms of callbacks.
它非常适合一般用途,但如果您要构建更复杂的东西,我建议您查看一些框架页面路由动画实现以及它们之间的不同 class 关系
- widgets/page_transitions_theme.dart - 各种页面路由构建器
- material/route - android 路线实施
- cupertino/route - cupertion 路由实现
我解决这个问题的方法是将偏移量保存到页面 class 中的静态变量中,并将控制器添加到列表视图构建器中。例如
static double _previousScrollOffset = 0;
final ScrollController _controller = new ScrollController(initialScrollOffset: _previousScrollOffset);
然后,每当我导航到新路线时,我都会保存状态。例如
onTap: () {
_previousScrollOffset = _controller.offset;
Navigator.pushNamed<bool>(
context, ROUTE_ADD_HOUSING + "/" + housing.uuid);
},