Jay Babani
Jay Babani

Reputation: 103

Sliver not allowing to set Max Width of Container?

I am trying to make a responsive page that works well on Mobile and Desktop. I am using SliverAppBar inside NestedScrollView. The issue is maxwidth constraints to a container are totally ignored in NestedScrollView. This behavior is also same with CustomScrollView.

How to restrict the size of Container in NestedScrollView body widget.

Below is the code:


import "dart:math";
import "package:flutter/material.dart";

class ResponsiveSliver extends StatefulWidget {
  static const routeName = 'responsive-sliver';

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

class _ResponsiveSliverState extends State<ResponsiveSliver> with SingleTickerProviderStateMixin {
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        floatHeaderSlivers: true,
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            SliverAppBar(
              title: Text(
                "Responsive Sliver",
              ),
              centerTitle: true,
              pinned: true,
              floating: true,
            ),
          ];
        },
        body: ConstrainedBox(
          constraints: BoxConstraints(maxWidth: 200),
          child: Container(
            child: ListView.separated(
              separatorBuilder: (context, child) => Divider(
                height: 1,
              ),
              padding: EdgeInsets.all(0.0),
              itemCount: 10,
              itemBuilder: (context, i) {
                return Container(
                  constraints: BoxConstraints(maxWidth: 200),
                  height: 100,
                  // width: double.infinity,
                  color: Colors.primaries[Random().nextInt(Colors.primaries.length)],
                );
              },
            ),
          ),
        ),
      ),
    );
  }

}

In my code above you can notice is am using maxWidth constraints,

ConstrainedBox( constraints: BoxConstraints(maxWidth: 200), child: ())

and

Container( constraints: BoxConstraints(maxWidth: 200), child: (),)

But these maxWidth constraints are completely ignored and it takes full width of the available screen.

I want to make NestedScrollView of full screen width which thus makes SliverAppbar cover whole screen width.

So far, I understand the Constraints take the width based on the size of their parent. In this case parent NestedScrollView is taking full screen width and is thus ignoring ConstrainedBox maxWidth property.

So how to make SliverAppBar full width in size and still set Max. Width on a Container?

Please advice how to resolve this.

Thank you

Upvotes: 5

Views: 3323

Answers (3)

James Allen
James Allen

Reputation: 7149

You can solve this layout problem quite nicely using Center. I prefer this to using the UnconstrainedBox solution, which doesn't work if the widget in question will overflow the maximum width (it will clip). By wrapping your ConstrainedBox with a Center, the scroll view will respect the maximum width you impose on the child widget using the ConstrainedBox, AND if the widget exceeds the maximum width it will behave nicely (eg Text will wrap).

For example (using SliverList, but applies to NestedScrollView, CustomScrollView etc.):

    SliverList.builder(
      itemCount: items.length,
      itemBuilder: (context, index) =>
        Center(   // <-- Wrap ConstrainedBox with Center
          child: ConstrainedBox(
            constraints: BoxConstraints(
              maxWidth: 200,
            ),
            child: Item(items[index]),
          ),
        ),
    )

Upvotes: 0

DevKev
DevKev

Reputation: 121

Any scrollable widget by default forces the child to expand and doesn't care about its horizontal constraints unless you constrain the scrollable widget.

However, in most cases you'll want to only constrain the child. There are few ways to do that here but the best will wrapping it in an UnconstrainedBox.

import "dart:math";
import "package:flutter/material.dart";

class ResponsiveSliver extends StatefulWidget {
  static const routeName = 'responsive-sliver';

  const ResponsiveSliver({super.key});

  @override
  State<ResponsiveSliver> createState() => _ResponsiveSliverState();
}

class _ResponsiveSliverState extends State<ResponsiveSliver> with SingleTickerProviderStateMixin {
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        floatHeaderSlivers: true,
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            const SliverAppBar(
              title: Text(
                "Responsive Sliver",
              ),
              centerTitle: true,
              pinned: true,
              floating: true,
            ),
          ];
        },
        body: UnconstrainedBox(
          child: Container(
            width: 200,
            child: ListView.separated(
              separatorBuilder: (context, child) => const Divider(
                height: 1,
              ),
              padding: const EdgeInsets.all(0.0),
              itemCount: 10,
              itemBuilder: (context, i) {
                return Container(
                  height: 100,
                  color: Colors.primaries[Random().nextInt(Colors.primaries.length)],
                );
              },
            ),
          ),
        ),
      ),
    );
  }

}

It simply does the opposite of the ConstrainedBox. Instead of forcing constraints, it doesn't impose any incoming constraints i.e from ListView, NestedScrollView, CustomSrollView etc. but allows the child to lay itself using its own constraints.

Refer to Flutter Documentation here which explicitly indicates that

A widget that imposes no constraints on its child, allowing it to render at its "natural" size.

This allows a child to render at the size it would render if it were alone on an infinite canvas with no constraints. This container will then attempt to adopt the same size, within the limits of its own constraints. If it ends up with a different size, it will align the child based on alignment. If the box cannot expand enough to accommodate the entire child, the child will be clipped.

In debug mode, if the child overflows the container, a warning will be printed on the console, and black and yellow striped areas will appear where the overflow occurs.

Surprisingly(or as intended), it works without any issues as long as your child doesn't overflow. A hidden gem indeed.

However, I couldn't have found this solution without Mustafa Tahir's article in medium. You can find it here for more ways. Though I liked this one best as it is clean.

Upvotes: 4

i4guar
i4guar

Reputation: 677

I solved it by using padding instead of a BoxConstraint.

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

class ConstrainedSliverWidth extends StatelessWidget {
  final Widget child;
  final double maxWidth;
  const ConstrainedSliverWidth({
    Key? key,
    required this.child,
    required this.maxWidth,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var size = MediaQuery.of(context).size;
    var padding = (size.width - maxWidth) / 2;
    return Padding(
      padding:
          EdgeInsets.symmetric(horizontal: max(padding, 0)),
      child: child,
    );
  }
}

If you replace ConstrainedBox with ConstrainedSliverWidth it should work.

Upvotes: 0

Related Questions