'Do not want rounded corners in the AppBar when the Sliver App Bar is collapsed

I'm trying to implement a layout, where the Sliver App Bar has rounded bottom corners when expanded, but when it is collapsed I do not want those rounded corners.

Actual Behaviour:

enter image description here

enter image description here

enter image description here

Expected Behaviour:

enter image description here

enter image description here

Here's my SliverAppBar code:

`SliverAppBar(
          systemOverlayStyle: const SystemUiOverlayStyle(
            statusBarColor: Color(0xFFE0E64B),
          ),
          backgroundColor: Color(0xFFE0E64B),
          expandedHeight: 300.0,
          floating: false,
          pinned: true,
          collapsedHeight: 60.0,
          onStretchTrigger: () async {
            setState(() {});
          },
          title: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: const [
              Text(
                'Pokedex',
                style: TextStyle(
                  color: Colors.white,
                ),
              ),
              Text(
                '#025',
                style: TextStyle(
                  color: Colors.white,
                ),
              ),
            ],
          ),
          flexibleSpace: FlexibleSpaceBar(
            collapseMode: CollapseMode.parallax,
            background: Container(
              decoration: const BoxDecoration(
                color: Color(0xFFE0E64B),
                borderRadius: BorderRadius.only(
                  bottomLeft: Radius.circular(50.0),
                  bottomRight: Radius.circular(50.0),
                ),
              ),
              child: Hero(
                tag: 'pokemon_container$index',
                child: Column(
                  children: [
                    const SizedBox(
                      height: 120.0,
                    ),
                    Expanded(
                      child: ClipRRect(
                        child: Image.network(
                          imageUrl,
                          fit: BoxFit.scaleDown,
                        ),
                      ),
                    ),
                    const SizedBox(
                      height: 30.0,
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),`


Solution 1:[1]

 shape: ContinuousRectangleBorder(
              borderRadius: BorderRadius.only(
                  bottomLeft: Radius.circular(30),
                  bottomRight: Radius.circular(30))),

Here is your code. Put it inside sliverAppBar

Solution 2:[2]

NestedScrollView / SliverAppBar solution

This is definitely achievable. SliverAppBar does support what we need, it has support for rounded borders, the shadow effect and changing sizes. For handling the border requirement we can use a RoundedRectangleBorder.

Although for getting a smooth transition for the border change, we need to update the values frequently, when changing the size of the SliverAppBar.

Example code

Do note that the package flutter_riverpod (version 1.0.3) is used for state management in this example.

import 'dart:math';

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        floatHeaderSlivers: true,
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            ExpandingAppBar(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                // Flexible is important for the children widgets added here.
                Flexible(child: Container(color: Colors.yellow, width: 50, height: 50,))
              ],
            )
          ];
        },
        body: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.center,
          children: const <Widget>[
            Text("Hello!")
          ],
        ),
      )
    );
  }
}

/// An SliverAppBar widget with alternating rounded border depending on the
/// expandedHeight.
///
/// Provides easy support for adding children widgets in the
/// expanded area as if it was a Column, although children widgets should be
/// wrapped in a Flexible widget.
class ExpandingAppBar extends ConsumerWidget {
  const ExpandingAppBar({
    Key? key,
    this.children = const <Widget>[],
    this.mainAxisAlignment = MainAxisAlignment.start
  }) : super(key: key);

  final List<Widget> children;
  final MainAxisAlignment mainAxisAlignment;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    RoundedHeaderState state = ref.watch(roundedHeaderProvider);

    return SliverAppBar(
      expandedHeight: state.highestHeight,
      pinned: true,
      primary: true,
      forceElevated: true,
      title: const Text('Pokèdex'),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(bottom: Radius.circular(state.radius)),
      ),
      flexibleSpace: LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
          // We update the state here.
          ref.read(roundedHeaderProvider.notifier).updateHeight(constraints.maxHeight);

          return Opacity(
            opacity: state.scrollFraction,
            child: Padding(
              padding: EdgeInsets.only(top: state.smallestHeight),
              child: Column(mainAxisAlignment: mainAxisAlignment, children: children),
            ),
          );
        },
      ),
    );
  }
}

@immutable
class RoundedHeaderState {
  final double highestHeight = 256;
  final double smallestHeight = kToolbarHeight + 24;
  final double currentHeight;
  final double contentOpacity = 1;

  const RoundedHeaderState({this.currentHeight = 256});

  double get scrollFraction => min(max((currentHeight - smallestHeight) / (highestHeight - smallestHeight), 0), 1);
  double get radius => 64 * scrollFraction;
}

class RoundedHeaderNotifier extends StateNotifier<RoundedHeaderState> {
  RoundedHeaderNotifier(): super(const RoundedHeaderState());

  updateHeight(double currentHeight) {
    final newState = RoundedHeaderState(currentHeight: currentHeight);

    // Check that the new state is not equal to the next (prevents rebuild loop)
    if(state.currentHeight != newState.currentHeight) {

      // Setting state triggers an rebuild, the PostFrameCallback let Flutter
      // postpone the upcoming rebuild at a later time.
      WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
        state = newState;
      });
    }
  }
}

final roundedHeaderProvider = StateNotifierProvider<RoundedHeaderNotifier, RoundedHeaderState>((ref) {
  return RoundedHeaderNotifier();
});

// Pay attention to the ProviderScope wrapping the MaterialApp. Riverpod requires this.
void main() => runApp(
    const ProviderScope(
      child: MaterialApp(home: RoundedSliverExampleScreen())
    )
);

Result - Gif of the SliverAppBar's transition.

Result gif showing the transition in the App bar when it goes from expanded to collapsed and back, on Windows.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tolga Y?lmaz
Solution 2 Tor-Martin Holen