Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sethladd/7b14de354562016325661029150a56ec to your computer and use it in GitHub Desktop.
Save sethladd/7b14de354562016325661029150a56ec to your computer and use it in GitHub Desktop.
// Vibe coded in DartPad w/ Gemini on May 15 2025
// I wanted to create this effect as I've always liked it.
// Enjoy!
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expandable Bar',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
useMaterial3: true,
),
home: const ExpandableBar(),
);
}
}
class ExpandableBar extends StatefulWidget {
const ExpandableBar({super.key});
@override
State<ExpandableBar> createState() => _ExpandableBarState();
}
class _ExpandableBarState extends State<ExpandableBar> {
double dragDistance = 0.0;
final double maxDragDistance = 200.0;
void _handleDragUpdate(DragUpdateDetails details) {
setState(() {
dragDistance -= details.delta.dy;
dragDistance = dragDistance.clamp(0.0, maxDragDistance);
});
}
void _handleDragEnd(DragEndDetails details) {
setState(() {
if (dragDistance > maxDragDistance / 2) {
dragDistance = maxDragDistance;
} else {
dragDistance = 0.0;
}
});
}
@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double barHeight = 50.0;
double verticalPositionFactor = dragDistance / maxDragDistance;
// Initial position of the menu icon
double icon1LeftStart = (screenWidth - (36 * 3)) / 2;
const double icon1TopStart = 7;
// Final position of the menu icon (top-left)
const double icon1LeftFinal = 16.0;
const double icon1TopFinal = 20.0;
// Curve calculation for menu icon
double curvedFactor = math.sin(verticalPositionFactor * math.pi / 2);
double icon1Left = icon1LeftStart + (icon1LeftFinal - icon1LeftStart) * curvedFactor;
double icon1Top = icon1TopStart + (icon1TopFinal - icon1TopStart) * curvedFactor;
// Initial position of the search icon
double icon2LeftStart = (screenWidth - (36 * 3)) / 2 + 36;
const double icon2TopStart = 7;
// Final position of the search icon (directly below menu icon)
const double icon2LeftFinal = 16.0;
const double icon2TopFinal = 20.0 + 36.0;
// Curve calculation for search icon
double icon2Left = icon2LeftStart + (icon2LeftFinal - icon2LeftStart) * curvedFactor;
double icon2Top = icon2TopStart + (icon2TopFinal - icon2TopStart) * curvedFactor;
// Initial position of the third icon (settings)
double icon3LeftStart = (screenWidth - (36 * 3)) / 2 + 36 + 36; // to the right of the search icon
const double icon3TopStart = 7;
// Final position of the third icon (directly below search icon)
const double icon3LeftFinal = 16.0;
const double icon3TopFinal = 20.0 + 36.0 + 36.0;
// Curve calculation for the third icon
double icon3Left = icon3LeftStart + (icon3LeftFinal - icon3LeftStart) * curvedFactor;
double icon3Top = icon3TopStart + (icon3TopFinal - icon3TopStart) * curvedFactor;
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
alignment: Alignment.bottomCenter,
children: [
Positioned(
bottom: 0,
child: GestureDetector(
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: Container(
width: screenWidth,
height: barHeight + dragDistance,
color: Colors.grey[200],
child: Stack(
children: [
Positioned(
top: icon1Top,
left: icon1Left,
child: const Icon(Icons.menu, size: 36),
),
Positioned(
top: icon2Top,
left: icon2Left,
child: const Icon(Icons.search, size: 36),
),
Positioned(
top: icon3Top,
left: icon3Left,
child: const Icon(Icons.settings, size: 36),
),
],
),
),
),
),
],
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment