How do you position a PageView inside a Stack?

1k views Asked by At

I have a PageView and a button inside a Stack (the PageView is at the bottom of the Stack). I have the button in the bottomCenter using Stack's alignment property.

I want to position the PageView inside the Stack so that it appears above the button (as in vertically above, not above in the Stack). However, it seems that you can't use the Positioned widget with a PageView to accomplish this.

I am using a Stack because the PageView contains a zoomable widget using InteractiveViewer, and I want the button to stay on top when this widget is zoomed in.

Wrapping the PageView with a Positioned widget gives different errors based on if you run the app from scratch, if you do a Flutter Hot Restart, or if you do a Flutter Hot Reload. (However, there are no errors if you do not specify any of the other Positioned parameters, i.e. bottom, left, etc.)

Another problem that occurs when you hot reload is that it does do something to the PageView. It shifts it up, but by more than the specified amount in the bottom parameter. It also cuts off a lot of the width (but doesn't give any overflow errors). You can see this in the second picture that is included.

If you specify the bottom parameter and then do a hot reload it gives this error:

The following assertion was thrown during performResize():
Horizontal viewport was given unbounded width.

Viewports expand in the scrolling direction to fill their container. In this case, a horizontal viewport was given an unlimited amount of horizontal space in which to expand. This situation typically happens when a scrollable widget is nested inside another scrollable widget.

If this widget is always nested in a scrollable widget there is no need to use a viewport because there will always be enough horizontal space for the children. In this case, consider using a Row instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size the width of the viewport to the sum of the widths of its children.

If you specify the bottom parameter, then either stop and run the app from scratch or do a hot restart, it gives these errors:

Error 1:

The following NoSuchMethodError was thrown during performLayout():
The method 'toStringAsFixed' was called on null.
Receiver: null
Tried calling: toStringAsFixed(1)

Error 2 (several variations of this error):

RenderBox was not laid out: RenderPointerListener#99b5a relayoutBoundary=up8 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1785 pos 12: 'hasSize'

Here is a simple app that shows my problem:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(home: MyHomePage());
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  PageController _pageController = PageController(initialPage: 0);
  int currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            TopToolbar(currentIndex == 0
                ? 'Car'
                : currentIndex == 1
                    ? 'Bike'
                    : 'Walk'),
            Expanded(
              child: Stack(
                alignment: AlignmentDirectional.bottomCenter,
                children: [
                  Positioned(
// vvvvvvvvvvvvvvvv Specifying this causes the errors: vvvvvvvvvvvvvvvvvvvvvvvv
                    bottom: 50, // 50 because that is the height of the button
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                    child: PageView(
                      controller: _pageController,
                      onPageChanged: (page) {
                        setState(() {
                          currentIndex = page;
                        });
                      },
                      children: [
                        CarScreen(),
                        BikeScreen(),
                        WalkScreen(),
                      ],
                    ),
                  ),
                  Container(
                      height: 50, // <-------------------- Height of the button
                      color: Colors.lightBlueAccent,
                      child: Text('A button', style: TextStyle(fontSize: 20)))
                ],
              ),
            ),
          ],
        ),
      ),
// ==== I don't think the code below is relevant to the question, but who knows:
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: currentIndex,
        onTap: (value) {
          setState(() {
            currentIndex = value;
            _pageController.jumpToPage(value);
          });
        },
        items: [
          BottomNavigationBarItem(
              icon: Icon(Icons.directions_car), label: 'Car'),
          BottomNavigationBarItem(
              icon: Icon(Icons.directions_bike), label: 'Bike'),
          BottomNavigationBarItem(
              icon: Icon(Icons.directions_walk), label: 'Walk'),
        ],
      ),
    );
  }
}

class TopToolbar extends StatelessWidget {
  final String label;
  TopToolbar(this.label);

  @override
  Widget build(BuildContext context) => Container(
        color: Colors.lightBlueAccent,
        child: Row(
          children: [
            Text('Custom toolbar for $label', style: TextStyle(fontSize: 20)),
          ],
        ),
      );
}

class CarScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) => ScreenWidget('Car');
}

class BikeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) => ScreenWidget('Bike');
}

class WalkScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) => ScreenWidget('Walk');
}

class ScreenWidget extends StatelessWidget {
  final String title;
  ScreenWidget(this.title);
  @override
  Widget build(BuildContext context) => Center(
      child: Container(
          width: 200,
          height: 550,
          alignment: Alignment.center,
          color: Colors.grey,
          child: Text('$title Screen', style: TextStyle(fontSize: 20))));
}


What it looks like without the bottom parameter specified. See that the button covers up part of the PageView, I don't want this to happen, this is what I'm trying to fix:

bottom not specified

What it looks like when you specify the bottom parameter and then do a hot reload. You can see it shifts it up by more than what's specified by bottom, and it cuts off a lot of the width without giving overflow errors:

bottom specified then hot reload

What it looks like when you specify the bottom parameter and then either hot restart, or stop and run the app from scratch. The PageView and the button are gone:

bottom specified then either hot restart or stop and run from scratch

0

There are 0 answers