首页 > 解决方案 > 颤动中的全屏GridView

问题描述

我想实现双向 Pageview 或全屏 Gridview。

即布局看起来像这样。

     |       |       |
     |       |       |
_____|_______|_______|____
     |       |       |
     |       |       |
.....|  i,j  | i+1,j |.....
     |       |       |
     |       |       |
_____|_______|_______|_____
     |       |       |
     |       |       |
.....| i,j+1 |i+1,j+1|.....
     |       |       |
     |       |       |
_____|_______|_______|_____
     |       |       |
     |       |       |
     |       |       |

每个i,j代表一个全屏。因此,设备的视口将只能(i,j) 在任何时间点查看特定内容。

从那个位置开始滑动屏幕

我想指定行数、列数。(不仅仅是4个)

到目前为止,这是我的代码。呈现 4 个这样的屏幕Video

(SVG)

(我还没有处理控制器逻辑)

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

void main() {
  // Hide the status bar
  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> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        children: [
          PageView(
            scrollDirection: Axis.vertical,
            children: [
              ColoredWidget(
                color: Colors.cyan,
                direction: ">",
              ),
              ColoredWidget(
                color: Colors.orange,
                direction: ">>",
              ),
            ],
          ),
          PageView(
            scrollDirection: Axis.vertical,
            children: [
              ColoredWidget(
                color: Colors.green,
                direction: "<",
              ),
              ColoredWidget(
                color: Colors.yellow,
                direction: "<<",
              ),
            ],
          ),
        ],
      ),
    );
  }
}

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;
}

但这显然行不通,因为我需要控制所有连接的相邻 PageViews 等。我不明白如何进行。

标签: flutterflutter-layout

解决方案


我能够做到。

Github上的最小代码

// import 'dart:math';

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;
  // No need to use valuenotifiers here
  // But I need pass by reference thus using it as a wrapper
  ValueNotifier<int> currIdxNotifier = ValueNotifier(0);
  ValueNotifier<int> currUpNotifier = ValueNotifier(0);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        // pageSnapping: true,
        controller: _rowController,
        onPageChanged: (pno) {
                    // MUST set state and trigger a rebuild
                    // As horizontal viewport changed
          setState(() {
            currIdxNotifier.value = pno;
            // print("${currIdxNotifier.value} horizz");
          });
        },
        children: [
          ColPageView(
            idx: 0,
            currup: currUpNotifier,
            notifier: currIdxNotifier,
            controllers: _controllers,
            children: <Widget>[
              ColoredWidget(
                color: Colors.orange[50],
                text: "0, 0",
              ),
              ColoredWidget(
                color: Colors.orange[100],
                text: "0, 1",
              ),
              ColoredWidget(
                color: Colors.orange[200],
                text: "0, 2",
              ),
              ColoredWidget(
                color: Colors.orange[300],
                text: "0, 3",
              ),
            ],
          ),
          ColPageView(
            idx: 1,
            currup: currUpNotifier,
            notifier: currIdxNotifier,
            controllers: _controllers,
            children: [
              ColoredWidget(
                color: Colors.green[100],
                text: "1, 0",
              ),
              ColoredWidget(
                color: Colors.green[200],
                text: "1, 1",
              ),
              ColoredWidget(
                color: Colors.green[300],
                text: "1, 2",
              ),
              ColoredWidget(
                color: Colors.green[400],
                text: "1, 3",
              ),
            ],
          ),
          ColPageView(
            idx: 2,
            currup: currUpNotifier,
            notifier: currIdxNotifier,
            controllers: _controllers,
            children: [
              ColoredWidget(
                color: Colors.teal[100],
                text: "2, 0",
              ),
              ColoredWidget(
                color: Colors.teal[200],
                text: "2, 1",
              ),
              ColoredWidget(
                color: Colors.teal[300],
                text: "2, 2",
              ),
              ColoredWidget(
                color: Colors.teal[400],
                text: "2, 3",
              ),
            ],
          ),
        ],
      ),
    );
  }
}

/// All the vertical pageviews are here
class ColPageView extends StatefulWidget {
  final List<Widget> children;
  final List<PageController> controllers;
  final ValueNotifier<int> notifier;
  final ValueNotifier<int> currup;
  final int idx;

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

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

class _ColPageViewState extends State<ColPageView> {
  @override
  void initState() {
    // Just initialized
    // Set the start value to be the current vertical value
    widget.controllers[widget.idx] = PageController(
      initialPage: widget.currup.value ?? 0,
      keepPage: true,
    );
    // print("INIT STATE ${widget.idx}");
    // print("INIT STATE ${widget.currup.value}");
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return PageView(
      // pageSnapping: true,
      controller: widget.controllers[widget.idx],
      //   controller: widget.controller,
      scrollDirection: Axis.vertical,
      children: widget.children,
      onPageChanged: (widget.notifier.value == widget.idx)
          ? (pno) {
              // if the global horizontal page is the current widget
              // var rand = Random();
              // var randnn = rand.nextDouble();
              widget.controllers.forEach((colpv) {
                if (widget.controllers[widget.idx] == colpv) {
                  // print("same widget so return $randnn");
                  return;
                }
                // https://github.com/flutter/flutter/issues/20621#issuecomment-445504085
                // Only if the controller has clients
                bool isSelected = colpv.hasClients
                    ? colpv.page == pno
                    : colpv.initialPage == pno;

                // Not the same page as everyone
                if (!isSelected) {
                  // print("not selected");
                  if (colpv.hasClients) {
                    colpv.animateToPage(
                      pno,
                      duration: Duration(milliseconds: 200),
                      curve: Curves.easeIn,
                    );
                  }
                }
                // set the current updated value of the vertical coord
                widget.currup.value = pno;
                // print("$pno $isSelected");
              });
              // print("col-${widget.idx} changed to $pno");

              // set horizontal coord to be null
              // As we've finished dealing with it
              widget.notifier.value = null;
            }
          : (_) {
              // Others which are not the currently moving pageview
              // SHOULD not have any listeners
              // Spent 5hrs trying to figure this out
              // print("nope ${widget.notifier.value} == ${widget.idx}");
            },
    );
  }
}

/// A Widget that simply displays a color and an input text
/// NOTE: This is a StatefulWidget because needs to use keepalive
class ColoredWidget extends StatefulWidget {
  /// Color to display the widget in
  final Color color;
  final String text;

  const ColoredWidget({
    Key key,
    @required this.color,
    @required this.text,
  }) : 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.text,
            style: TextStyle(
              fontSize: 100,
              color: Colors.black,
            ),
          ),
        ));
  }

  // Need to use this or the state of the pageview will be lost
  // In this case, if not using keepalive it would still work but
  // it will scroll down every time the page gets changed horizontally
  // TODO: Destroy if not next to current pageview
  @override
  bool get wantKeepAlive => true;
}

关于如何链接到综合浏览量的类似问题(由我提出)。


推荐阅读