动态构建Flutter PageView

时间:2018-12-24 18:25:14

标签: flutter

我需要一些有关如何使用PageView的建议。我有一个在线项目,该项目允许用户构建自定义表单,并根据其他问题的答案来隐藏或显示问题。移动项目允许用户填写这些表格。我一直在使用适用于此功能的PageView,但我一直在努力找出如何指示PageView的结尾。现在,它将允许滚动一直持续下去。

return PageView.builder(
  controller: _controller,
  onPageChanged: (int index) {
    FocusScope.of(context).requestFocus(FocusNode());
  },
  itemBuilder: (BuildContext context, int index) {
    if (index >= _form.controls.length) {
      print("Returning null");
      return null;
    }

    return FormControlFactory.createFormControl(
        _form.controls[index], null);
  },
);

由于我不确定直到表格末尾有多少个元素如何结束滚动?

更新:在我的示例中,我尝试返回null,但它仍会滚动到末尾。

更新:这是我目前所在的位置:

class _FormViewerControllerState extends State<FormViewerController> {
  int _currentIndex = 0;
  List<FormGroupController> _groups = List();
  List<StreamSubscription> _subscriptions = List();
  Map<int, FormControlController> _controllerMap = Map();
  bool _hasVisibilityChanges = false;

  @override
  void initState() {
    super.initState();
    for (var i = 0; i < widget.form.controls.length; i++) {
      var control = widget.form.controls[i];
      if (control.component == ControlType.header) {
        _groups.add(FormGroupController(
            form: widget.form,
            formResponses: widget.responses,
            headerIndex: i));
      }
    }
    _controllerMap[_currentIndex] = _getControl(_currentIndex);
    _subscriptions.add(FormsEventBus()
        .on<FormControlVisibilityChanging>()
        .listen(_onControlVisibilityChanging));
    _subscriptions.add(FormsEventBus()
        .on<FormControlVisibilityChanged>()
        .listen(_onControlVisibilityChanged));
  }

  @override
  Widget build(BuildContext context) {
    print("Building pageview, current index: $_currentIndex");
    return PageView.builder(
      controller: PageController(
        initialPage: _currentIndex,
        keepPage: true,
      ),
      onPageChanged: (int index) {
        print("Page changed: $index");
        _currentIndex = index;
        FocusScope.of(context).requestFocus(FocusNode());
      },
      itemBuilder: (BuildContext context, int index) {
        print("Building $index");
        _controllerMap[index] = _getControl(index);
        return _controllerMap[index].widget;
      },
      itemCount: _groups
          .map((g) => g.visibleControls)
          .reduce((curr, next) => curr + next),
    );
  }

  @override
  void dispose() {
    _subscriptions.forEach((sub) => sub.cancel());
    _groups.forEach((g) => g.dispose());
    super.dispose();
  }

  FormControlController _getControl(int index) {
    for (var group in _groups) {
      // We want to reduce the index so it can be local to group
      if (index >= group.visibleControls) {
        index -= group.visibleControls;
        continue;
      }

      for (var instance in group.instances) {
        // We want to reduce the index so it can be local to the instance
        if (index >= instance.visibleControls) {
          index -= instance.visibleControls;
          continue;
        }

        return instance.controls.where((c) => c.visible).elementAt(index);
      }
    }

    throw StateError("Weird, the current control doesn't exist");
  }

  int _getControlIndex(FormControlController control) {
    var index = 0;
    for (var group in _groups) {
      if (control.groupInstance.group.groupId != group.groupId) {
        index += group.visibleControls;
        continue;
      }

      for (var instance in group.instances) {
        if (control.groupInstance.groupInstanceId != instance.groupInstanceId) {
          index += instance.visibleControls;
          continue;
        }

        for (var c in instance.controls.where((c) => c.visible)) {
          if (c.control.id != control.control.id) {
            index++;
            continue;
          }
          return index;
        }
      }
    }

    throw StateError("Weird, can't find the control's index");
  }

  _onControlVisibilityChanging(FormControlVisibilityChanging notification) {
    _hasVisibilityChanges = true;
  }

  _onControlVisibilityChanged(FormControlVisibilityChanged notification) {
    if (!_hasVisibilityChanges) {
      return;
    }

    setState(() {
      print("Setting state");
      var currentControl = _controllerMap[_currentIndex];
      _controllerMap.clear();
      _currentIndex = _getControlIndex(currentControl);
      _controllerMap[_currentIndex] = currentControl;
    });

    _hasVisibilityChanges = false;
  }
}

现在的问题是,如果更改导致在当前页面之前创建新页面,则为了保留在同一页面上,必须更改页面索引,以使其保留在当前页面上,而该部分不会工作。该构建方法被多次调用,由于某种原因最终显示原始索引。

有些示例打印语句显示了我的意思:

flutter: Control Text Box 2 (5) visible: true
flutter: Group instance Section 2 (1) visible controls: 1 -> 2
flutter: Group 1 clearing visible controls count
flutter: Setting state
flutter: Building pageview, current index: 2
flutter: Building 1
flutter: Building 2
flutter: Building pageview, current index: 2
flutter: Building 1

所以我一开始就在索引1上。我在该视图上选择要在索引1之前插入新页面的内容,因此是一个新索引1。我调用set state将当前索引设置为2,因为那是当前视图的新索引。如您所见,小部件中的build方法被调用两次,第一个方法在页面视图中呈现索引1和2,但是下一个方法即使初始索引设置为2也仅呈现索引1。

1 个答案:

答案 0 :(得分:0)

由于我无法运行您发布的最小复制。我试图从示例应用程序本地复制行为。根据我的测试,在itemBuilder上返回null可以正常工作。我正在使用Flutter稳定频道1.22.0版

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

int pageViewIndex;

class _MyHomePageState extends State<MyHomePage> {
  ActionMenu actionMenu;
  final PageController pageController = PageController();
  int currentPageIndex = 0;
  int pageCount = 1;

  @override
  void initState() {
    super.initState();
    actionMenu = ActionMenu(this.addPageView, this.removePageView);
  }

  addPageView() {
    setState(() {
      pageCount++;
    });
  }

  removePageView(BuildContext context) {
    if (pageCount > 1)
      setState(() {
        pageCount--;
      });
    else
      Scaffold.of(context).showSnackBar(SnackBar(
        content: Text("Last page"),
      ));
  }

  navigateToPage(int index) {
    pageController.animateToPage(
      index,
      duration: Duration(milliseconds: 300),
      curve: Curves.ease,
    );
  }

  getCurrentPage(int page) {
    pageViewIndex = page;
  }

  createPage(int page) {
    return Container(
      child: Center(
        child: Text('Page $page'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: <Widget>[
          actionMenu,
        ],
      ),
      body: Container(
        child: PageView.builder(
          controller: pageController,
          onPageChanged: getCurrentPage,
          // itemCount: pageCount,
          itemBuilder: (context, position) {
            if (position == 5) return null;
            return createPage(position + 1);
          },
        ),
      ),
    );
  }
}

enum MenuOptions { addPageAtEnd, deletePageCurrent }
List<Widget> listPageView = List();

class ActionMenu extends StatelessWidget {
  final Function addPageView, removePageView;
  ActionMenu(this.addPageView, this.removePageView);

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<MenuOptions>(
      onSelected: (MenuOptions value) {
        switch (value) {
          case MenuOptions.addPageAtEnd:
            this.addPageView();
            break;
          case MenuOptions.deletePageCurrent:
            this.removePageView(context);
            break;
        }
      },
      itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
        PopupMenuItem<MenuOptions>(
          value: MenuOptions.addPageAtEnd,
          child: const Text('Add Page at End'),
        ),
        const PopupMenuItem<MenuOptions>(
          value: MenuOptions.deletePageCurrent,
          child: Text('Delete Current Page'),
        ),
      ],
    );
  }
}

示例应用的外观如下。

demo