1

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,
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),`

2 Answers2

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

Here is your code. Put it inside sliverAppBar

Tolga Yılmaz
  • 506
  • 1
  • 3
  • 15
  • thank you for quick response. The issue with this approach is that when I collapse the app bar, it still have the rounded corners. I want the appbar to have rounded corners only when it is expanded – Anmol Singh Sahi Jan 22 '22 at 19:32
  • man, I am not sure that we can do it only with SliverAppBar. Maybe it can be done with functionality but this issue beyond me. – Tolga Yılmaz Jan 22 '22 at 19:46
0

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.

Tor-Martin Holen
  • 1,359
  • 1
  • 13
  • 19