Parallax-style header flutter 中的滚动性能
Parallax-style header scrolling performance in flutter
我正在我的 flutter 应用程序中开发一个 parallax-style header/background 块,它以前景内容速度的 1/3 左右向上滚动。前景中的所有部分都在同一个 customScrollView 中,背景 header 位于堆栈顶部的定位容器中。
我在 customscrollview 上使用侦听器更新 y-offset 整数,然后使用该整数更新堆栈中元素的顶部位置。
虽然这按预期工作,但我面临的问题是大量重绘发生在滚动上,这在未来可能会影响性能。我确信可能有一种更有效的方法来实现这一点——比如将整个背景放在一个单独的 child 小部件中,并将控制器从 parent 小部件传递给它——但是我正在努力找到有关这样做的任何信息,或者这是否是正确的方法。
有人可以指出正确的方向来重构它,以便将滚动背景与前景断开连接,从而使前景不会不断重绘吗?
class ScrollingWidgetList extends StatefulWidget {
ScrollingWidgetList();
@override
State<StatefulWidget> createState() {
return _ScrollingWidgetList();
}
}
class _ScrollingWidgetList extends State<ScrollingWidgetList> {
ScrollController _controller;
double _offsetY = 0.0;
_scrollListener() {
setState(() {
_offsetY = _controller.offset;
});
}
@override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: -(_offsetY / 3),
child: ConstrainedBox(
constraints: new BoxConstraints(
maxHeight: 300.0,
minHeight: MediaQuery.of(context).size.width * 0.35),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Theme.of(context).primaryColorDark,
Colors.blueGrey[900].withOpacity(0.8)
],
)),
height: MediaQuery.of(context).size.width * 0.35)),
width: MediaQuery.of(context).size.width,
),
CustomScrollView(controller: _controller, slivers: [
SliverList(
delegate: SliverChildListDelegate([
Padding(
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
child: ListTile(
title: Padding(
padding: const EdgeInsets.only(top: 6.0),
child: Text('Header text',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white)),
),
subtitle: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('Subtitle text',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white)),
),
))
])),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return FakeItem(
executing: false,
delay: index.isOdd,
complete: false,
cancelled: false);
},
childCount: 30,
)),
])
],
);
}
}
@pskink 在评论中添加了一个很好的解决方案,但他们似乎已将其删除。对于任何寻找优雅解决方案的人来说,这是解决问题的基础。
您可以在下面的代码中看到 CustomMultiChildLayout
正在处理两个布局。希望这可以帮助任何寻找类似解决方案的人
class ScrollList extends StatelessWidget {
final ScrollController _controller = ScrollController();
@override
Widget build(BuildContext context) {
return CustomMultiChildLayout(
delegate: ScrollingChildComponentDelegate(_controller),
children: <Widget>[
// background element layout
LayoutId(
id: 'background',
child: DecoratedBox(
decoration: BoxDecoration(
// box decoration
),
),
),
// foreground element layout
LayoutId(
id: 'scrollview',
child: CustomScrollView(
controller: _controller,
physics: AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: ListTile(
title: Text('TitleText'),
),
subtitle: Text('SubtitleText'),
)),
),
SliverList(
delegate: SliverChildBuilderDelegate(itemBuilder,
childCount: 100),
),
],
)),
],
);
}
}
// itembuilder for child components
Widget itemBuilder(BuildContext context, int index) {
return Card(
margin: EdgeInsets.all(6),
child: ClipPath(
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10))),
child: Container(
// child element content
)));
}
// controller for the animation
class ScrollingChildComponentDelegate extends MultiChildLayoutDelegate {
final ScrollController _controller;
ScrollingChildComponentDelegate(this._controller) : super(relayout: _controller);
@override
void performLayout(Size size) {
positionChild('background', Offset(0, -_controller.offset / 3));
layoutChild('background',
BoxConstraints.tightFor(width: size.width, height: size.height * 0.2));
positionChild('scrollview', Offset.zero);
layoutChild('scrollview', BoxConstraints.tight(size));
}
@override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => true;
}
我正在我的 flutter 应用程序中开发一个 parallax-style header/background 块,它以前景内容速度的 1/3 左右向上滚动。前景中的所有部分都在同一个 customScrollView 中,背景 header 位于堆栈顶部的定位容器中。
我在 customscrollview 上使用侦听器更新 y-offset 整数,然后使用该整数更新堆栈中元素的顶部位置。
虽然这按预期工作,但我面临的问题是大量重绘发生在滚动上,这在未来可能会影响性能。我确信可能有一种更有效的方法来实现这一点——比如将整个背景放在一个单独的 child 小部件中,并将控制器从 parent 小部件传递给它——但是我正在努力找到有关这样做的任何信息,或者这是否是正确的方法。
有人可以指出正确的方向来重构它,以便将滚动背景与前景断开连接,从而使前景不会不断重绘吗?
class ScrollingWidgetList extends StatefulWidget {
ScrollingWidgetList();
@override
State<StatefulWidget> createState() {
return _ScrollingWidgetList();
}
}
class _ScrollingWidgetList extends State<ScrollingWidgetList> {
ScrollController _controller;
double _offsetY = 0.0;
_scrollListener() {
setState(() {
_offsetY = _controller.offset;
});
}
@override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: -(_offsetY / 3),
child: ConstrainedBox(
constraints: new BoxConstraints(
maxHeight: 300.0,
minHeight: MediaQuery.of(context).size.width * 0.35),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Theme.of(context).primaryColorDark,
Colors.blueGrey[900].withOpacity(0.8)
],
)),
height: MediaQuery.of(context).size.width * 0.35)),
width: MediaQuery.of(context).size.width,
),
CustomScrollView(controller: _controller, slivers: [
SliverList(
delegate: SliverChildListDelegate([
Padding(
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
child: ListTile(
title: Padding(
padding: const EdgeInsets.only(top: 6.0),
child: Text('Header text',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white)),
),
subtitle: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('Subtitle text',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white)),
),
))
])),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return FakeItem(
executing: false,
delay: index.isOdd,
complete: false,
cancelled: false);
},
childCount: 30,
)),
])
],
);
}
}
@pskink 在评论中添加了一个很好的解决方案,但他们似乎已将其删除。对于任何寻找优雅解决方案的人来说,这是解决问题的基础。
您可以在下面的代码中看到 CustomMultiChildLayout
正在处理两个布局。希望这可以帮助任何寻找类似解决方案的人
class ScrollList extends StatelessWidget {
final ScrollController _controller = ScrollController();
@override
Widget build(BuildContext context) {
return CustomMultiChildLayout(
delegate: ScrollingChildComponentDelegate(_controller),
children: <Widget>[
// background element layout
LayoutId(
id: 'background',
child: DecoratedBox(
decoration: BoxDecoration(
// box decoration
),
),
),
// foreground element layout
LayoutId(
id: 'scrollview',
child: CustomScrollView(
controller: _controller,
physics: AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: ListTile(
title: Text('TitleText'),
),
subtitle: Text('SubtitleText'),
)),
),
SliverList(
delegate: SliverChildBuilderDelegate(itemBuilder,
childCount: 100),
),
],
)),
],
);
}
}
// itembuilder for child components
Widget itemBuilder(BuildContext context, int index) {
return Card(
margin: EdgeInsets.all(6),
child: ClipPath(
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10))),
child: Container(
// child element content
)));
}
// controller for the animation
class ScrollingChildComponentDelegate extends MultiChildLayoutDelegate {
final ScrollController _controller;
ScrollingChildComponentDelegate(this._controller) : super(relayout: _controller);
@override
void performLayout(Size size) {
positionChild('background', Offset(0, -_controller.offset / 3));
layoutChild('background',
BoxConstraints.tightFor(width: size.width, height: size.height * 0.2));
positionChild('scrollview', Offset.zero);
layoutChild('scrollview', BoxConstraints.tight(size));
}
@override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => true;
}