Link two pageviews in flutter

北城余情 提交于 2019-12-11 16:59:56

问题


How to link two pageviews in flutter?

i.e. if one of them goes to page x the other should go to page x as well.

I thought two PageViews having the same controller would do the trick. But that doesn't seem to be the case.

I tried having a list of controllers and when one of the pageviews' page changes, I'm calling jumpToPage on all the other pageviews' controllers but all the other PageViews are not in the widget runtime tree initially (They're outside the screen) thus giving out errors.

In my case PageView(children:[Pageview(...), Pageview(...)]) is the structure.

And after I open the other pageviews once, the errors are all gone but the current pageview is also getting jumped even though I removed it.

There're no infinite loops because of the other pageview's event firing at the same time.

/// Inside a stateful widget
  PageView(
      controller: widget.controller,
      onPageChanged: (pno) {
        widget.controllers.where((x) {
          return x != widget.controllers[widget.idx];
        }).forEach((colpv) {
          colpv.controller?.jumpToPage(pno);
        });
      },
     );

This is a minimal example that reproduces what I'm doing. It's in the ColPageView widget.

The full code

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  SystemChrome.setEnabledSystemUIOverlays([]);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Experiments',
      theme: ThemeData.dark(),
      home: MyHomePage(title: 'FlutterExps'),
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {
  List<PageControllerC> _controllers;
  PageController _rowController;
  PageController _mainController;

  @override
  void initState() {
    _controllers = [
      PageControllerC(
        controller: PageController(keepPage: true),
        recorded: 0,
      ),
      PageControllerC(
        controller: PageController(keepPage: true),
        recorded: 1,
      ),
    ];
    _controllers.forEach((f) {
      f.controller.addListener(() {
        print("Listener on ${f.recorded}");
      });
    });
    _mainController = PageController();
    _rowController = PageController();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _rowController,
        children: [
          ColPageView(
            idx: 0,
            controllers: _controllers,
            controller: _mainController,
            children: <Widget>[
              ColoredWidget(
                color: Colors.cyan,
                direction: ">",
              ),
              ColoredWidget(
                color: Colors.orange,
                direction: ">>",
              ),
            ],
          ),
          ColPageView(
            idx: 1,
            controllers: _controllers,
            controller: _mainController,
            children: [
              ColoredWidget(
                color: Colors.green,
                direction: "<",
              ),
              ColoredWidget(
                color: Colors.yellow,
                direction: "<<",
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class PageControllerC {
  PageController controller;
  int recorded;
  PageControllerC({
    this.recorded,
    this.controller,
  });
}

class ColPageView extends StatefulWidget {
  final List<Widget> children;
  final List<PageControllerC> controllers;
  final int idx;
  final PageController controller;

  const ColPageView({
    Key key,
    this.children = const <Widget>[],
    @required this.controllers,
    @required this.idx,
    this.controller,
  }) : super(key: key);

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

class _ColPageViewState extends State<ColPageView> {
  @override
  Widget build(BuildContext context) {
    return PageView(
      controller: widget.controllers[widget.idx].controller,
    //   controller: widget.controller,
      scrollDirection: Axis.vertical,
      children: widget.children,
      onPageChanged: (pno) {
        widget.controllers.where((x) {
          return x != widget.controllers[widget.idx];
        }).forEach((colpv) {
          // if (colpv != widget.controllers[widget.idx]) {
          colpv.controller?.jumpToPage(pno);
          // }
          // else{
          print("memmem ${widget.idx}");
          // }
        });
        print("col-${widget.idx} changed to $pno");
      },
    );
  }
}

class ColoredWidget extends StatefulWidget {
  final Color color;
  final String direction;

  const ColoredWidget({
    Key key,
    @required this.color,
    @required this.direction,
  }) : super(key: key);

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

class _ColoredWidgetState extends State<ColoredWidget>
    with AutomaticKeepAliveClientMixin<ColoredWidget> {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container(
        color: widget.color,
        child: Center(
          child: Text(
            widget.direction,
            style: TextStyle(
              fontSize: 100,
              color: Colors.black,
            ),
          ),
        ));
  }

  @override
  bool get wantKeepAlive => true;
}


回答1:


I was able to link two pageviews given that they both reside in a pageview.

Note: They're discretely linked.

  • Maintain a list of controllers
  • Track the current vertical position in the HomePage widget.
  • And also the current horizontal pageview's position.
  • If a widget's page is being changed and it is visible in the viewport then make all other pages jump to where this goes. Check if it is in the widget tree before making it jump.
  • Else if it's not in the viewport don't apply the same callback as it should only be affected by the one in the viewport (or the currently scrolling one).
  • When initializing any pageview check the current vertical position and jump to that page.
  • This is not efficient as I'm keeping all the pageviews in the widget tree alive even if they are not visible. (I will update the answer if I come up with one that is efficient)
  • This is working because both pageviews are in a single pageview which is horizontal.
  • I will try to provide another example where both the pageviews are in the viewport (in a row for example) and the linking is continuous.
  • This can be extended to multiple page views and which leads to a fullscreen GridView.

Full code.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  SystemChrome.setEnabledSystemUIOverlays([]);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Experiments',
      theme: ThemeData.dark(),
      home: MyHomePage(title: 'FlutterExps'),
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {
  List<PageController> _controllers;
  PageController _rowController;
  ValueNotifier<int> _horizPage = ValueNotifier(0);
  ValueNotifier<int> _vertPage = ValueNotifier(0);

  @override
  void initState() {
    _controllers = [
      PageController(keepPage: true),
      PageController(keepPage: true),
    ];
    _rowController = PageController();
    _horizPage.value = _rowController.initialPage;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _rowController,
        onPageChanged: (pno) {
          setState(() {
            _horizPage.value = pno;
          });
        },
        children: [
          ColPageView(
            idx: 0,
            currHoriz: _horizPage,
            vertstate: _vertPage,
            controllers: _controllers,
            children: <Widget>[
              ColoredWidget(
                color: Colors.cyan,
                direction: ">",
              ),
              ColoredWidget(
                color: Colors.orange,
                direction: ">>",
              ),
            ],
          ),
          ColPageView(
            idx: 1,
            currHoriz: _horizPage,
            vertstate: _vertPage,
            controllers: _controllers,
            children: [
              ColoredWidget(
                color: Colors.green,
                direction: "<",
              ),
              ColoredWidget(
                color: Colors.yellow,
                direction: "<<",
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class ColPageView extends StatefulWidget {
  final int idx;
  final List<Widget> children;
  final List<PageController> controllers;
  final ValueNotifier<int> currHoriz;
  final ValueNotifier<int> vertstate;

  const ColPageView({
    Key key,
    this.children = const <Widget>[],
    @required this.controllers,
    @required this.currHoriz,
    @required this.vertstate,
    @required this.idx,
  }) : super(key: key);

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

class _ColPageViewState extends State<ColPageView> {
  @override
  void initState() {
    widget.controllers[widget.idx] = PageController(
      initialPage: widget.vertstate.value ?? 0,
      keepPage: true,
    );

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return PageView(
      controller: widget.controllers[widget.idx],
      scrollDirection: Axis.vertical,
      children: widget.children,
      onPageChanged: (widget.idx == widget.currHoriz.value)
          ? (pno) {
              widget.controllers.forEach((colpv) {
                if (colpv != widget.controllers[widget.idx]) {
                  if (colpv.hasClients && colpv.page != pno) {
                    colpv.jumpToPage(pno);
                  }
                }
              });
              // Set latest vertical position
              widget.vertstate.value = pno;

              // print("col-${widget.idx} changed to $pno");
              // set horizontal coord to be null
              // As we've finished dealing with it
              widget.currHoriz.value = null;
            }
          : null,
    );
  }
}

class ColoredWidget extends StatefulWidget {
  final Color color;
  final String direction;

  const ColoredWidget({
    Key key,
    @required this.color,
    @required this.direction,
  }) : super(key: key);

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

class _ColoredWidgetState extends State<ColoredWidget>
    with AutomaticKeepAliveClientMixin<ColoredWidget> {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container(
        color: widget.color,
        child: Center(
          child: Text(
            widget.direction,
            style: TextStyle(
              fontSize: 100,
              color: Colors.black,
            ),
          ),
        ));
  }

  @override
  bool get wantKeepAlive => true;
}



来源:https://stackoverflow.com/questions/58824727/link-two-pageviews-in-flutter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!