Wrap middle element to new row if needed?

141 views Asked by At

I have a navigation bar at the bottom of a multi-page form, with buttons to go back or forward, and an indicator for the current page.

Right now, I have the indicator place in a Row above another Row that contains the buttons, like this:

Current Widget

This works, and it will work even on small display sizes. However, I would rather have the indicators placed on the same row as the buttons if there is enough space, like this (except that the indicator is not centered):

Desired Widget

The problem is, this could be too wide for some devices, especially if there are more than just a few pages. In that case, I would like either the page indicator or the buttons to "wrap" to a new row, as in the current design.

It would be easy to put everything in a Wrap, but that will make the NEXT-button wrap instead of the page indicator, since that is the last element.

Is there an easy way to make the middle element wrap onto a new row if needed? Or does one have to resort to the black magic of manually calculating sizes and creating two different layouts?

1

There are 1 answers

0
Kherel On

The easiest solution is to add invisible wrap to calculate the height.

enter image description here

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: SafeArea(
          child: MyHomePage(),
        ),
      ),
    );
  }
}

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

class _MyHomePageState extends State<MyHomePage> {
  int currentPageIndex = 1;
  int pageCount = 8;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  currentPageIndex.toString(),
                ),
                Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    RaisedButton(
                      child: Text('+1 page'),
                      onPressed: () => setState(() => ++pageCount),
                    ),
                    SizedBox(
                      width: 10,
                    ),
                    RaisedButton(
                      child: Text('-1 page'),
                      onPressed: () => setState(() => --pageCount),
                    ),
                  ],
                )
              ],
            ),
          ),
        ),
        Container(
          child: _BottomNavigation(
            onPrev: () => setState(() => --currentPageIndex),
            onNext: () => setState(() => ++currentPageIndex),
            currentCount: currentPageIndex,
            totalCount: pageCount,
          ),
        ),
      ],
    );
  }
}

class _BottomNavigation extends StatelessWidget {
  const _BottomNavigation({
    Key key,
    @required this.totalCount,
    @required this.currentCount,
    @required this.onNext,
    @required this.onPrev,
  })  : assert(totalCount != null),
        assert(currentCount != null),
        assert(onNext != null),
        assert(onPrev != null),
        super(key: key);

  final void Function() onPrev;
  final void Function() onNext;
  final int totalCount;
  final int currentCount;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        buildHelper(),
        buildIndicatorBar(),
        Positioned(
          bottom: 0,
          left: 0,
          right: 0,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              _button(
                '<< PREIOUS',
                onPrev,
                isVisible: currentCount > 1,
              ),
              _button(
                'NEXT >>',
                onNext,
                isVisible: currentCount < totalCount,
              ),
            ],
          ),
        ),
      ],
    );
  }

  Wrap buildHelper() {
    return Wrap(
      children: List.generate(
        totalCount,
        (index) {
          return Container(
            width: index == 0 ? 250 : 15,
            height: 20,
          );
        },
      ),
    );
  }

  Row buildIndicatorBar() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: List.generate(totalCount, (index) {
        var isLast = totalCount != index + 1;
        var isCurrent = currentCount == index + 1;

        return Container(
          height: isCurrent ? 20 : 10,
          width: isCurrent ? 20 : 10,
          margin: EdgeInsets.only(right: isLast ? 10 : 0),
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: isCurrent ? Colors.blueAccent : null,
            border: Border.all(
              color: Colors.blueAccent,
            ),
          ),
        );
      }),
    );
  }

  Widget _button(String text, void Function() onPress, {bool isVisible}) {
    return Visibility(
      visible: isVisible,
      child: GestureDetector(
        onTap: onPress,
        child: Text(text),
      ),
    );
  }
}