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

时间:2019-10-21 12:18:44

标签: flutter dart flutter-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,因为我需要知道页脚是否对其他功能可见。

问题

  • widget.child必须包装其内容,否则: Vertical viewport was given unbounded height。我尝试用widget.childWrap包装IntrinsicHeight,但是不起作用。如果assert没有widget.child,我唯一能做的就是制作一个shrinkWrap: true
  • widget.child不必滚动,否则自定义窗口小部件将不会滚动,因为widget.childListView包装。我尝试用widget.childIgnorePointer包装GestureDetector,但无法点击这些项目。如果assert widget.child不是phyisics,我唯一能做的就是制作一个NeverScrollableScrollPhysics
  • 如果widget.child有一个controller,则由于widget.child不应该滚动,因此我必须将该controller传递给父级ListView,但我会得到:ScrollController attached to multiple scroll views。我唯一能做的就是允许自定义窗口小部件接收controller并在assert有一个widget.child的情况下制作一个controller

问题

  • 我该如何解决这些问题?
  • 这是个好方法吗?
  • 我还能采取什么其他方法?

1 个答案:

答案 0 :(得分:0)

看着ScrollView类,我找到了解决方案:

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

  • Scrollable将有NeverScrollableScrollPhysics
  • Scrollable不会有controller
  • Scrollable将返回一个ShrinkWrappingViewport,其中包含 axisDirection中的sliverswidget.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')),
)

让我知道是否有人认为这不好并且有更好的方法。