Skip to content

Instantly share code, notes, and snippets.

@mslimani
Last active January 20, 2020 19:47
Show Gist options
  • Save mslimani/5c6d1f77e24f10d730ea8de08fc366e0 to your computer and use it in GitHub Desktop.
Save mslimani/5c6d1f77e24f10d730ea8de08fc366e0 to your computer and use it in GitHub Desktop.
Ineat Flutter #1 Custom shape BottomSheet
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
key: _scaffoldKey,
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: FloatingActionButton(
child: Icon(Icons.add_a_photo),
onPressed: () {
_scaffoldKey.currentState.showBottomSheet(
(BuildContext context) => _BottomSheetContainer(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
onTap: () => Navigator.pop(context),
leading: Icon(Icons.photo),
title: Text('Photo')),
ListTile(
onTap: () => Navigator.pop(context),
leading: Icon(Icons.image),
title: Text('Gallery')),
],
),
),
backgroundColor: Colors.transparent,
);
},
),
),
),
);
}
}
class _BottomSheetContainer extends StatefulWidget {
final Widget child;
const _BottomSheetContainer({Key key, this.child}) : super(key: key);
@override
_BottomSheetContainerState createState() => _BottomSheetContainerState();
}
class _BottomSheetContainerState extends State<_BottomSheetContainer> {
ValueListenable<ScaffoldGeometry> _geometryListenable;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_geometryListenable = Scaffold.geometryOf(context);
}
@override
Widget build(BuildContext context) {
return PhysicalShape(
clipper: _BottomSheetClipper(
scaffoldGeometry: _geometryListenable,
),
elevation: 10,
color: Theme.of(context).cardColor,
clipBehavior: Clip.none,
child: Material(
type: MaterialType.transparency,
child: widget.child,
),
);
}
}
class _BottomSheetClipper extends CustomClipper<Path> {
final ValueListenable<ScaffoldGeometry> scaffoldGeometry;
_BottomSheetClipper({this.scaffoldGeometry});
@override
Path getClip(Size size) {
Rect host = Offset.zero & size;
final floatingActionButtonSize =
scaffoldGeometry.value.floatingActionButtonArea.size;
final floatingActionButtonOffset = Offset(
size.width / 2 - floatingActionButtonSize.width / 2,
-floatingActionButtonSize.height / 3);
Rect guest = floatingActionButtonOffset & floatingActionButtonSize;
final double notchRadius = guest.width / 1.5;
const double s1 = 30.0;
const double s2 = 1.0;
final double r = notchRadius;
final double a = (-1.0 * r - s2) * 1.1;
final double b = host.top - guest.center.dy;
final double n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r));
final double p2xA = ((a * r * r) - n2) / (a * a + b * b);
final double p2xB = ((a * r * r) + n2) / (a * a + b * b);
final double p2yA = math.sqrt(r * r - p2xA * p2xA);
final double p2yB = math.sqrt(r * r - p2xB * p2xB);
final List<Offset> p = List<Offset>(6);
p[0] = Offset(a - s1, b);
p[1] = Offset(a, b);
final double cmp = b < 0 ? -1.0 : 1.0;
p[2] = cmp * p2yA > cmp * p2yB ? Offset(p2xA, p2yA) : Offset(p2xB, p2yB);
p[3] = Offset(-1.0 * p[2].dx, p[2].dy);
p[4] = Offset(-1.0 * p[1].dx, p[1].dy);
p[5] = Offset(-1.0 * p[0].dx, p[0].dy);
for (int i = 0; i < p.length; i += 1) p[i] += guest.center;
final cornerRadius = notchRadius / 1.8;
return Path()
..moveTo(host.left, host.top + cornerRadius)
..quadraticBezierTo(
host.left, host.top, host.left + cornerRadius, host.top)
..lineTo(p[0].dx, p[0].dy)
..quadraticBezierTo(p[1].dx, p[1].dy, p[2].dx, p[2].dy)
..arcToPoint(p[3], radius: Radius.circular(notchRadius), clockwise: false)
..quadraticBezierTo(p[4].dx, p[4].dy, p[5].dx, p[5].dy)
..lineTo(host.right - cornerRadius, host.top)
..quadraticBezierTo(
host.right, host.top, host.right, host.top + cornerRadius)
..lineTo(host.right, host.bottom)
..lineTo(host.left, host.bottom)
..close();
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment