Last week, I published a small video on Twitter that gained a lot of reactions:

This article will explain how I achieve this effect that you can find in almost all music players.

The Expandable Bottom Sheet

I used this package to achieve the same example, but the ExpandableBottomSheet didn't expose a way to get the current offset, so I copied the code and adapted it.

expandable_bottom_sheet | Flutter Package
This is a BottomSheet with a draggable height like the Google Maps App on Android.
/// [onOffsetChanged] will be executed if the offset changes.
/// (offset, minOffset, maxOffset)
final Function(double?, double?, double?)? onOffsetChanged;

You can dive into the code here to see how it was implemented, but it was pretty simple.

Then I could use it like that:

body: ExpandableBottomSheet(
  background: CustomScrollView(
    ...content of the first tutorial
  ),
  persistentContentHeight: 64,
  expandableContent: Player(percentageOpen: _percentageOpen),
  onOffsetChanged: (offset, minOffset, maxOffset) {
    if (maxOffset == null || offset == null || minOffset == null) {
      return;
    }
    final range = maxOffset - minOffset;
    final currentOffset = offset - minOffset;
    setState(() {
      _percentageOpen = max(0, 1 - (currentOffset / range));
    });
  },
  enableToggle: true,
  isDraggable: true,
),

The BottomNavigationBar

To make the BottomNavigationBar disappear as I'm scrolling; I wrapped the bar in a SizedBox and added a SingleChildScrollView to prevent overflowing pixels.

SizedBox(
  height: max(0, (1 - _percentageOpen) * _maxSizeBottomNavigationBar),
  child: SingleChildScrollView(
    child: BottomNavigationBar(

The Player

To create the animation between the open and the closed Player, I used a straightforward Stack and two Opacity widgets. I tweaked the values to have the perfect fade in and fade out:

/// Closed player
opacity: max(0, 1 - (percentageOpen * 4)),

/// Open player
opacity: percentageOpen > 0.5
	? min(1, max(0, percentageOpen - 0.5) * 2)
	: 0
   

Finally, the picture is above the two other widgets and has a growing height that will take the correct size.

final imageHeight = 64 + (percentageOpen * height * 0.5);

Conclusion

I hope you liked this small walk-through on how I achieved the visual effect. If you want to see the complete code, it's right here.

GitHub - Lyokone/quick-flutter-design: Quick Flutter Design you can repeat easily
Quick Flutter Design you can repeat easily. Contribute to Lyokone/quick-flutter-design development by creating an account on GitHub.

You can subscribe to my newsletter below or follow me on Twitter to not miss any of my following articles.