Like with awesome bottom sheet of Telegram
i would like to have that in our application, i tired to implementing that, but unfortunately i don’t have more experience about that and i can implement below code like with that which it doesn’t have more feature.
here i attached some Gif
images such as what features i want to have them on our application:
opening animation:
switch between tabs animation:
expand and collapsing animation:
preventing closing bottom sheet during dragging down:
Full code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isLong = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample'),
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: _onPressed,
child: Text('open'),
)
],
),
),
);
}
_onPressed() {
setImages();
Navigator.of(context).push(TransparentRoute(builder: (context) => NewWidget(images)));
}
List<String> images =[];
void setImages() {
images = List.generate(
isLong ? 5 : 25,
(_) => 'http://placeimg.com/100/100/any',
);
}
}
class NewWidget extends StatefulWidget {
const NewWidget(this.images, {Key? key}) : super(key: key);
final List<String> images;
@override
_NewWidgetState createState() => _NewWidgetState();
}
class _NewWidgetState extends State<NewWidget> {
bool isBig = false;
bool isBouncing = true;
final double topOffset = 200;
final double miniHandleHeight = 20;
double safeAreaPadding = 0;
double? startBigAnimationOffset;
double? startStickyOffset;
double backgroundHeight = 0;
double get savedAppBarHeight => safeAreaPadding + kToolbarHeight;
final ScrollController controller = ScrollController();
@override
void initState() {
WidgetsBinding.instance!.addPostFrameCallback(_afterLayout);
super.initState();
}
void _afterLayout(_) {
var media = MediaQuery.of(context);
safeAreaPadding = media.padding.top;
startBigAnimationOffset = topOffset - savedAppBarHeight;
startStickyOffset = (startBigAnimationOffset! + 10);
backgroundHeight = media.size.height - miniHandleHeight - topOffset;
controller.addListener(scrollListener);
}
void scrollListener() {
var offset = controller.offset;
if (offset < 0) {
goOut();
} else if (offset < startBigAnimationOffset! && isBig || offset < startStickyOffset!) {
setState(() {
isBig = false;
});
} else if (offset > startBigAnimationOffset! && !isBig || offset > startStickyOffset!) {
setState(() {
isBig = true;
});
}
if (offset < topOffset && !isBouncing) {
setState(() => isBouncing = true);
} else if (offset > topOffset && isBouncing) {
setState(() => isBouncing = false);
}
}
void goOut() {
controller.dispose();
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: /*isStack ? Colors.white : */Colors.transparent,
body: Stack(
children: [
ListView(
padding: EdgeInsets.zero,
physics: isBouncing ? BouncingScrollPhysics() : ClampingScrollPhysics(),
controller: controller,
children: <Widget>[
Container(
alignment: Alignment.bottomCenter,
height: topOffset,
child: TweenAnimationBuilder(
tween: Tween(begin: 0.0, end: isBig ? 1.0 : 0.0),
duration: Duration(milliseconds: 200),
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 10),
child: Container(
height: 5,
width: 60,
),
),
),
builder: (_, number, child) {
return Container(
height: savedAppBarHeight * (number as double) + miniHandleHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
top: Radius.circular((1 - number) * 50),
),
color: Colors.white,
),
child: Opacity(opacity: 1 - (number), child: child),
);
}),
),
Container(
padding: EdgeInsets.all(5),
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - savedAppBarHeight,
),
decoration: BoxDecoration(
color: Colors.white,
),
child: getGrid(),
)
],
)
],
),
),
);
}
Widget getGrid() {
return GridView.count(
crossAxisSpacing: 10,
mainAxisSpacing: 10,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
crossAxisCount: 3,
children: widget.images.map((url) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
child: Image(
image: NetworkImage(url),
),
);
}).toList(),
);
}
}
class TransparentRoute extends PageRoute<void> {
TransparentRoute({
required this.builder,
RouteSettings? settings,
}) : super(settings: settings, fullscreenDialog: false);
final WidgetBuilder builder;
@override
bool get opaque => false;
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
bool get maintainState => true;
@override
Duration get transitionDuration => Duration(milliseconds: 350);
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final result = builder(context);
return Container(
color: Colors.black.withOpacity(0.5),
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeIn,
)),
child: result,
),
);
}
}
2
Answers
Here is the outline, with which you can try to implement it. If you feel difficulty, feel free to ask for more suggestions. IMHO, this is not very challenging to implement (but of course need some time to adjust the fine details into what you want).
Firstly, you mentioned animations many times. Have a look at the official guide here: https://flutter.dev/docs/development/ui/animation. For example, if we want to implement the "opening animation" and the "switch between tabs animation" (and other similar things), we want a Container (or other widgets) to have a size changing with time as the animation goes. There are many ways to implement this, as is suggested in the link provided above. For example, we may use
AnimatedContainer
directly – it even has a video explaining this here. You can also use explicit animations or other things.Secondly, as for the animating icons, you may use Lottie https://lottiefiles.com/ to create whatever complex animation as you like. Its flutter plugin is here: https://pub.dev/packages/lottie.
Thirdly, as for "preventing closing bottom sheet during dragging down", I will suggest the following:
ListView(children: [Container(height:300,color:Colors.black), ...your_real_children...])
. Then when dragging down it will not be closed, but only show a header of at most 300 height.Lastly, you are not restricted to use showModalBottomSheet or things like that. Indeed, when using flutter, you are completely free. So you can just do
Navigator.push
and draw whatever widget you like without the constraints that modal bottom sheet gives you. Personally, I suggest that you first start with showModalBottomSheet, then when it does not satisfy your needs, you just directly copy its source code and do whatever modifications as you needed. In short, use something in Flutter, and when you need more control, copy the source code and modify it.You can be helped from this link https://pub.dev/packages/modal_bottom_sheet
another helpful link [https://pub.dev/packages/draggable_bottom_sheet][2]