Flutter - Как создать собственный виджет, который добавляет нижний колонтитул к полученному виджету с возможностью прокрутки?

Я создаю настраиваемый виджет, который добавляет нижний колонтитул в качестве последнего элемента к любому полученному виджету с возможностью прокрутки.

Идеальное использование было бы таким:

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.child Wrap или IntrinsicHeight, но это не сработало. Единственное, что я мог сделать, это сделать assert, если widget.child не имеет shrinkWrap: true.
  • widget.child не нужно прокручивать, иначе пользовательский виджет не будет прокручиваться, потому что widget.child заключен в ListView. Я пробовал обернуть widget.child IgnorePointer или GestureDetector, но не могу нажимать на элементы. Единственное, что я мог сделать, это сделать assert, если widget.child phyisics не NeverScrollableScrollPhysics.
  • Если widget.child имеет controller, поскольку widget.child не должен прокручиваться, я должен передать это controller родительскому ListView, но я бы получил: ScrollController attached to multiple scroll views. Единственное, что я мог сделать, это позволить настраиваемому виджету получать controller и делать assert, если widget.child имеет controller.

Вопросы

  • Как я могу решить эти проблемы?
  • Это хороший подход?
  • Какие еще подходы я мог бы использовать?

person Pablo Barrera    schedule 21.10.2019    source источник
comment
вы хотите добавить нижний колонтитул, чтобы он выглядел как последний элемент списка или это должен быть настоящий нижний колонтитул в нижней части контейнера?   -  person    schedule 21.10.2019
comment
Это должен быть последний предмет   -  person Pablo Barrera    schedule 21.10.2019
comment
посмотрите на этот github.com/showang/flutter_easy_listview, я думаю, вы можете покопаться в его коде на GitHub, здесь не так много кода   -  person    schedule 21.10.2019
comment
Спасибо, но это работает только как ListView, мне нужно создать настраиваемый виджет, который работает для любого типа ScrollView, например GridView, StaggeredGridView и т. Д. Если я воспользуюсь подходом, аналогичным этой ссылке, мне нужно будет сделать много настраиваемых виджеты по одному для каждого типа ScrollView и передают все свойства каждого типа.   -  person Pablo Barrera    schedule 21.10.2019


Ответы (1)


Посмотрев на класс ScrollView, я нашел решение для этого:

В настраиваемом виджете вместо использования полученного widget.child мне нужно создать новый виджет Scrollable, выполнив следующие действия:

  • Scrollable будет иметь NeverScrollableScrollPhysics
  • У Scrollable не будет controller
  • Scrollable вернет ShrinkWrappingViewport с axisDirection и slivers из 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')),
)

Дайте мне знать, если кто-то считает, что это нехорошо, и у него есть подход получше.

person Pablo Barrera    schedule 21.10.2019