Flutter:在使用PageView建立PageView之前无法访问它

时间:2020-04-06 11:09:43

标签: flutter dart flutter-pageview

如何解决异常-

未处理的异常:'package:flutter / src / widgets / page_view.dart':失败的断言:第179行pos 7:'positions.isNotEmpty':在使用PageView建立它之前,无法访问PageController.page。

注意:-我在两个屏幕中使用了它,当我在两个屏幕之间切换时,它显示了上述异常。

@override
  void initState() {
    super.initState();
      WidgetsBinding.instance.addPostFrameCallback((_) => _animateSlider());
  }

  void _animateSlider() {
    Future.delayed(Duration(seconds: 2)).then(
      (_) {
        int nextPage = _controller.page.round() + 1;

        if (nextPage == widget.slide.length) {
          nextPage = 0;
        }

        _controller
            .animateToPage(nextPage,
                duration: Duration(milliseconds: 300), curve: Curves.linear)
            .then(
              (_) => _animateSlider(),
            );
      },
    );
  }

3 个答案:

答案 0 :(得分:8)

我没有足够的信息来确切地了解您的问题所在,但是我遇到了一个类似的问题,我想将PageView和标签分组在同一小部件​​中,并且希望将当前幻灯片和标签标记为活动因此我需要访问controler.page才能做到这一点。这是我的解决方法:

修复了使用PageView小部件构建FutureBuilder小部件之前访问页面索引的问题

class Carousel extends StatelessWidget {
  final PageController controller;

  Carousel({this.controller});

  /// Used to trigger an event when the widget has been built
  Future<bool> initializeController() {
    Completer<bool> completer = new Completer<bool>();

    /// Callback called after widget has been fully built
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      completer.complete(true);
    });

    return completer.future;
  } // /initializeController()

  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        // **** FIX **** //
        FutureBuilder(
          future: initializeController(),
          builder: (BuildContext context, AsyncSnapshot<void> snap) {
            if (!snap.hasData) {
              // Just return a placeholder widget, here it's nothing but you have to return something to avoid errors
              return SizedBox();
            }

            // Then, if the PageView is built, we return the labels buttons
            return Column(
              children: <Widget>[
                CustomLabelButton(
                  child: Text('Label 1'),
                  isActive: controller.page.round() == 0,
                  onPressed: () {},
                ),
                CustomLabelButton(
                  child: Text('Label 2'),
                  isActive: controller.page.round() == 1,
                  onPressed: () {},
                ),
                CustomLabelButton(
                  child: Text('Label 3'),
                  isActive: controller.page.round() == 2,
                  onPressed: () {},
                ),
              ],
            );
          },
        ),
        // **** /FIX **** //
        PageView(
          physics: BouncingScrollPhysics(),
          controller: controller,
          children: <Widget>[
            CustomPage(),
            CustomPage(),
            CustomPage(),
          ],
        ),
      ],
    );
  }
}

修复如果您需要直接在PageView子级中使用索引

您可以改用有状态的小部件:

class Carousel extends StatefulWidget {
  Carousel();

  @override
  _HomeHorizontalCarouselState createState() => _CarouselState();
}

class _CarouselState extends State<Carousel> {
  final PageController controller = PageController();
  int currentIndex = 0;

  @override
  void initState() {
    super.initState();

    /// Attach a listener which will update the state and refresh the page index
    controller.addListener(() {
      if (controller.page.round() != currentIndex) {
        setState(() {
          currentIndex = controller.page.round();
        });
      }
    });
  }

  @override
  void dispose() {
    controller.dispose();

    super.dispose();
  }

  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
           Column(
              children: <Widget>[
                CustomLabelButton(
                  child: Text('Label 1'),
                  isActive: currentIndex == 0,
                  onPressed: () {},
                ),
                CustomLabelButton(
                  child: Text('Label 2'),
                  isActive: currentIndex == 1,
                  onPressed: () {},
                ),
                CustomLabelButton(
                  child: Text('Label 3'),
                  isActive: currentIndex == 2,
                  onPressed: () {},
                ),
              ]
        ),
        PageView(
          physics: BouncingScrollPhysics(),
          controller: controller,
          children: <Widget>[
            CustomPage(isActive: currentIndex == 0),
            CustomPage(isActive: currentIndex == 1),
            CustomPage(isActive: currentIndex == 2),
          ],
        ),
      ],
    );
  }
}

答案 1 :(得分:1)

这意味着您正在尝试访问PageController.page(可能是您自己,也可能是通过第三方程序包(如Page Indicator)访问的),但是,那时Flutter尚未呈现PageView引用控制器的小部件。

最佳解决方案:将FutureBuilderFuture.value一起使用

在这里,我们仅使用page上的pageController属性将代码包装到将来的构建器中,这样就可以在渲染PageView之后将其渲染。

我们使用Future.value(true)会导致Future立即完成,但仍需要等待足够的时间才能成功完成下一帧,因此我们将先构建PageView,然后再进行引用。

class Carousel extends StatelessWidget {

  final PageController controller;

  Carousel({this.controller});

  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[

        FutureBuilder(
          future: Future.value(true),
          builder: (BuildContext context, AsyncSnapshot<void> snap) {
            
            //If we do not have data as we wait for the future to complete,
            //show any widget, eg. empty Container
            if (!snap.hasData) {
             return Container();
            }

            //Otherwise the future completed, so we can now safely use the controller.page
            return Text(controller.controller.page.round().toString);
          },
        ),

        //This PageView will be built immediately before the widget above it, thanks to
        // the FutureBuilder used above, so whenever the widget above is rendered, it will
        //already use a controller with a built `PageView`        

        PageView(
          physics: BouncingScrollPhysics(),
          controller: controller,
          children: <Widget>[
           AnyWidgetOne(),
           AnyWidgetTwo()
          ],
        ),
      ],
    );
  }
}

或者

或者,您仍然可以使用FutureBuilder,并将其在addPostFrameCallback生命周期的initState中完成,因为它也将在渲染当前帧后完成未来与上述解决方案相同的效果。但是我强烈推荐第一个解决方案,因为它很简单

 WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
     //Future will be completed here 
     // e.g completer.complete(true);
    });

答案 2 :(得分:0)

我认为您可以像这样使用Listener:

int _currentPage;

  @override
  void initState() {
    super.initState();
    _currentPage = 0;
    _controller.addListener(() {
      setState(() {
        _currentPage = _controller.page.toInt();
      });
    });
  }