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;
    }