Last active
January 20, 2020 19:47
-
-
Save mslimani/5c6d1f77e24f10d730ea8de08fc366e0 to your computer and use it in GitHub Desktop.
Ineat Flutter #1 Custom shape BottomSheet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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