Home > other >  Why does a Container in a SingleChildScrollView not constrain content?
Why does a Container in a SingleChildScrollView not constrain content?

Time:05-31

I'm pretty new both to Flutter/Dart and Stack Overflow. I started with some trip ETA code and modified it to be an initiative turn tracker for a fantasy RPG tabletop game.

My intent is to keep the End Encounter button on the screen at all times, and have the timeline content scroll in a window above it.

I added a SingleChildScrollView into _TurnSectionState at line 178, but as soon as I add a Container into it I get the overflow. I tried for about 3 hours before asking for help here.

Pixel overflow!

    final data = _data(1);

    return Scaffold(
      appBar: TitleAppBar(widget._title),
      body: Center(
        child: SingleChildScrollView(
          child: SizedBox(
            width: 380.0,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: _MissionName(
                    timeSliceInfo: data,
                  ),
                ),
                const Divider(height: 1.0),
                _TurnSection(processes: data.deliveryProcesses),
                const Divider(height: 1.0),
                const Padding(
                  padding: EdgeInsets.all(20.0),
                  child: _OnTimeBar(),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

// Mission name
class _MissionName extends StatelessWidget {
  const _MissionName({
    Key? key,
    required this.timeSliceInfo,
  }) : super(key: key);

  final _TimeSliceInfo timeSliceInfo;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text(
          missions[currentMission].getTitle(),
          style: const TextStyle(
            fontWeight: FontWeight.bold,
          ),
        ),
      ],
    );
  }
}

// Lists items in each turn
class _InnerTimeline extends StatefulWidget {
  const _InnerTimeline({
    required this.messages,
  });

  final List<ActivationLineItem> messages;

  @override
  State<_InnerTimeline> createState() => _InnerTimelinePageState();
}

class _InnerTimelinePageState extends State<_InnerTimeline> {
  @override
  initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    bool isEdgeIndex(int index) {
      return index == 0 || index == widget.messages.length   1;
    }

    // Interior connector lines
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: FixedTimeline.tileBuilder(
        theme: TimelineTheme.of(context).copyWith(
          nodePosition: 0,
          connectorTheme: TimelineTheme.of(context).connectorTheme.copyWith(
                thickness: 1.0,
              ),
          indicatorTheme: TimelineTheme.of(context).indicatorTheme.copyWith(
                size: 10.0,
                position: 0.5,
              ),
        ),
        builder: TimelineTileBuilder(
          indicatorBuilder: (_, index) =>
              !isEdgeIndex(index) ? Indicator.outlined(borderWidth: 1.0) : null,
          startConnectorBuilder: (_, index) => Connector.solidLine(),
          endConnectorBuilder: (_, index) => Connector.solidLine(),
          contentsBuilder: (_, index) {
            if (isEdgeIndex(index)) {
              return null;
            }

            // Line item (init value   icon   text)
            return Padding(
                padding: const EdgeInsets.only(left: 8.0),
                child: Row(children: [
                  Text(widget.messages[index - 1].tick.toString()),
                  const SizedBox(width: 6),
                  Image(
                      image: AssetImage(widget.messages[index - 1].thumbnail),
                      width: 20,
                      height: 20),
                  const SizedBox(width: 6),
                  Expanded(
                      child: Text(widget.messages[index - 1].message,
                          overflow: TextOverflow.ellipsis, maxLines: 1))
                ]));
          },
          itemExtentBuilder: (_, index) => isEdgeIndex(index) ? 10.0 : 30.0,
          nodeItemOverlapBuilder: (_, index) =>
              isEdgeIndex(index) ? true : null,
          itemCount: widget.messages.length   2,
        ),
      ),
    );
  }
}

// Outer timeline (List of turns)
class _TurnSection extends StatefulWidget {
  const _TurnSection({Key? key, required processes})
      : _processes = processes,
        super(key: key);

  final List<TurnContents> _processes;

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

class _TurnSectionState extends State<_TurnSection> {
  bool isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return DefaultTextStyle(
      style: const TextStyle(
        color: Color(0xff9b9b9b), // Nobel Gray
        fontSize: 12.5,
      ),
      child: SingleChildScrollView(
        child: SizedBox(
          height: 480,
          child: Column(
            children: [
              FixedTimeline.tileBuilder(
                theme: TimelineThemeData(
                  nodePosition: 0,
                  color: const Color(0xff989898), // Spanish Gray
                  indicatorTheme: const IndicatorThemeData(
                    position: 0,
                    size: 20.0, // Outer timeline circle size
                  ),
                  connectorTheme: const ConnectorThemeData(
                    thickness: 2.5, // Outer timeline line thickness
                  ),
                ),
                builder: TimelineTileBuilder.connected(
                  connectionDirection: ConnectionDirection.before,
                  itemCount: widget._processes.length,
                  contentsBuilder: (_, index) {
                    if (widget._processes[index].isCompleted) return null;

                    return GestureDetector(
                      onTap: () {
                        int turnNum = widget._processes[index].getTurnNum();
                        if (turnNum == currentTurn) {
                          // Ask if ready for next turn
                          showDialog<String>(
                            context: context,
                            builder: (BuildContext context) => AlertDialog(
                              title: const Text('Next Turn?'),
                              content: Text('Ready to start turn ?'  
                                  (currentTurn   1).toString()),
                              actions: <Widget>[
                                TextButton(
                                  onPressed: () =>
                                      Navigator.pop(context, 'Yes'),
                                  child: const Text('Yes'),
                                ),
                                TextButton(
                                  onPressed: () => Navigator.pop(context, 'No'),
                                  child: const Text('No'),
                                ),
                              ],
                            ),
                          ).then((value) {
                            if (value == 'Yes') {
                              currentTurn  ; // Move to the next turn
                              setState(() {}); // Draw the screen
                            }
                          });
                        }
                      },
                      child: Padding(
                        padding: const EdgeInsets.only(left: 8.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text(
                              widget._processes[index]
                                  .getName(), // Turn name font size
                              style:
                                  DefaultTextStyle.of(context).style.copyWith(
                                        fontSize: 18.0,
                                      ),
                            ),
                            _InnerTimeline(
                                messages:
                                    widget._processes[index].getMessages()),
                          ],
                        ),
                      ),
                    );
                  },
                  indicatorBuilder: (_, index) {
                    if (index <= currentTurn) {
                      return const DotIndicator(
                        color: Color(0xff66c97f), // Emerald Green dot
                        child: Icon(
                          Icons.check,
                          color: Colors.white,
                          size: 12.0,
                        ),
                      );
                    } else {
                      return const OutlinedDotIndicator(
                        borderWidth: 2.5,
                      );
                    }
                  },
                  connectorBuilder: (_, index, ___) => SolidLineConnector(
                    color: index <= currentTurn
                        ? const Color(0xff66c97f) // Emerald Green connector
                        : null, // Green
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _OnTimeBar extends StatelessWidget {
  const _OnTimeBar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        MaterialButton(
          onPressed: () {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('On Time!'),
              ),
            );
          },
          elevation: 0,
          shape: const StadiumBorder(),
          color: const Color(0xff66c97f),
          textColor: Colors.white,
          child: const Text('End Encounter'),
        ),
        const Spacer(),
        const SizedBox(width: 12.0),
      ],
    );
  }
}

/*
List<TurnContents> buildTimeline() {
  List<TurnContents> turns;

  return turns;
}
*/

_TimeSliceInfo _data(int id) => _TimeSliceInfo(
      id: id,
      date: DateTime.now(),
      deliveryProcesses: ImpulseBuilder().buildTimeline(),
    );

class _TimeSliceInfo {
  const _TimeSliceInfo({
    required this.id,
    required this.date,
    required this.deliveryProcesses,
  });

  final int id;

  final DateTime date;
  final List<TurnContents> deliveryProcesses;```
}


  [1]: https://i.stack.imgur.com/mGd5X.png

CodePudding user response:

use Expanded and in SingleChildScrollView add physics:ScrollPhysics()

  Expanded(
     child: Container(
          child: SingleChildScrollView(
          physics: ScrollPhysics(),
           child:  Column(
                  children: []
                         )
                       )
                     )
                   );
  • Related