Flutter - 如何制作一个自定义小部件,将页脚添加到接收到的可滚动小部件中?

Flutter - How to make a custom widget that adds a footer to a received scrollable widget?

我正在创建一个自定义小部件,它将页脚作为最后一项添加到收到的任何可滚动小部件中。

理想的用法是这样的:

ScrollableWithFooter(
    child: ListView.builder(...),
    footer: Text('Footer'),
)

ScrollableWithFooter(
    child: GridView.builder(...),
    footer: Text('Footer'),
)

ScrollableWithFooter(
    child: CustomListView.builder(...),
    footer: Text('Footer'),
)

ScrollableWithFooter(
    child: SingleChildScrollView.builder(...),
    footer: Text('Footer'),
)

ScrollableWithFooter(
    child: StaggeredGridView.builder(...),
    footer: Text('Footer'),
)

这是我正在采用的方法:

class ScrollableWithFooter extends StatefulWidget {
  final ScrollView child;
  final Widget footer;

  ScrollableWithFooter({Key key, this.child, this.footer}) : super(key: key);

  @override
  State<StatefulWidget> createState() => ScrollableWithFooterState();
}

class ScrollableWithFooterState extends State<ScrollableWithFooter> {
  final _scrollController = ScrollController();

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      scrollDirection: widget.child.scrollDirection,
      itemCount: 2,
      itemBuilder: (context, index) {
        if (index == 0) {
          return widget.child;
        }
        return widget.footer;
      },
    );
  }
}

请注意它有一个 ScrollController,因为我需要知道页脚是否对其他功能可见。

问题

问题

查看 ScrollView class 我找到了一个解决方案:

在自定义小部件中,我必须创建一个新的 Scrollable 小部件,而不是使用收到的 widget.child,这样做:

  • Scrollable会有NeverScrollableScrollPhysics
  • Scrollable 不会有 controller
  • Scrollable 将 return 与 ShrinkWrappingViewport axisDirectionslivers 来自 widget.child

自定义小部件的实现如下:

var _scrollController = ScrollController();

@override
void initState() {
  super.initState();
  _scrollController = widget.child.controller ?? _scrollController;
}

@override
Widget build(BuildContext context) {
  return ListView.builder(
    controller: widget.child.controller,
    scrollDirection: widget.child.scrollDirection,
    itemCount: 2,
    itemBuilder: (context, index) {
      if (index == 0) {
        return Scrollable(
          physics: NeverScrollableScrollPhysics(),
          axisDirection: widget.child.getDirection(context),
          viewportBuilder: (context, offset) {
            return ShrinkWrappingViewport(
              axisDirection: widget.child.getDirection(context),
              offset: offset,
              slivers: widget.child.buildSlivers(context),
            );
          },
        );
      }
      return widget.footer;
    },
  );
}

编辑: 这是一个简化的替代方法:

@override
Widget build(BuildContext context) {
  return Scrollable(
    controller: widget.child.controller,
    axisDirection: widget.child.getDirection(context),
    viewportBuilder: (context, offset) {
      return ShrinkWrappingViewport(
        axisDirection: widget.child.getDirection(context),
        offset: offset,
        slivers: widget.child.buildSlivers(context)
          ..add(SliverList(delegate: SliverChildListDelegate([widget.footer]))),
      );
    },
  );
}

下面是一些如何使用它的例子:

final _scrollController = ScrollController();

// ListView
ScrollableWithFooter(
  child: ListView.builder(
    controller: _scrollController,
    itemCount: 50,
    itemBuilder: (context, index) => Text('Item $index'),
  ),
  footer: Center(child: Text('Footer')),
)

// GridView
ScrollableWithFooter(
  child: GridView.builder(
    controller: _scrollController,
    itemCount: 50,
    itemBuilder: (context, index) => Text('Item $index'),
    gridDelegate:
        SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
  ),
  footer: Center(child: Text('Footer')),
)

如果有人认为这不好并且有更好的方法,请告诉我。