Version 4.0 has made major adjustments. For migration, please refer to: SmartDialog 3.x Migration to 4.0
The content of this article has been updated, and the content and code in the article are all 4.0 usage
foreword
Q: What is the worst smell you have ever smelled in your life?
A: My long rotten dream.
Brother Moe! ! ! I am coming again!
This time, I can confidently say to everyone: I finally brought you a pub package that can really help you solve many pitfalls!
The previous flutter_smart_dialog, on the basis of maintaining the stability of the api, has carried out various head-grabbing reconstructions and solved a series of problems
Now, I can finally say: it's now a neat, powerful, and minimally intrusive pub package!
about invasive issues
- In order to solve the problem of returning to close the pop-up window, a very inelegant solution was used, which made the intrusion a bit high.
- This really makes me feel like I'm on pins and needles, like a thorn in my back, like a sting in my throat, this problem is finally solved!
At the same time, I designed a pop-up window stack inside the pub package, which can automatically remove the pop-up window at the top of the stack, and can also remove the pop-up window marked in the stack at a fixed point.
existing problems
There are a series of pits using the system pop-up window, let's discuss with you
BuildContext must be passed
- In some scenes, it is necessary to do more parameter transmission work, which is a painful but not difficult problem.
loading popup
Using the system pop-up window as the loading pop-up window, you must have encountered this pit ratio problem
- The loading is encapsulated in the network library: load the loading when requesting the network, press the return button, and close the loading
- Then after the request was over, I found out: Why is my page closed! ! !
The system pop-up window is a routing page, and the pop method is used to close the system, which is easy to close the normal page by mistake.
- Of course, there must be a solution. The place where the route is monitored is handled, and it is not detailed here.
Several system Dialogs pop up on a page, and it is difficult to close a non-stack top popup window at a fixed point
- Eggy, this is caused by the routing stacking and popping mechanism, and it is also necessary to complain when understanding it.
System Dialog, click events cannot penetrate the dark background
- This pit is a problem, I really don't know what to do
Related thinking
Some of the more common problems are listed above. The most serious problem should be the loading problem.
- Loading is a pop-up window used in ultra-high frequency. The method of closing the loading pop-up window can also close the normally used page, which is a hidden danger in itself.
- Penetrating the dialog mask is a very important function. Based on this function, many operations can be realized in actual business.
- Since it is difficult to solve various pain points in the system dialog, and the system dialog is also implemented based on overlay, in this case, we can also highly customize the overlay!
This time, I want to help you solve it at one time: toast messages, loading pop-ups, and more powerful custom dialogs!
After multiple versions of iterative optimization, I think I can brag: SmartDialog replaces Flutter's own Dialog without pressure, and even the functions are more intimate and more diverse!
Get started quickly
initialization
- Introduced (please click pub to view the latest version): pub , github , web effect
dependencies:
flutter_smart_dialog: ^4.0.9+5
- The access method is simpler 😊
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage,
// here
navigatorObservers: [FlutterSmartDialog.observer],
// here
builder: FlutterSmartDialog.init(),
);
}
}
Advanced initialization: configure global custom Loading and Toast
SmartDialog's showLoading and showToast provide a default style. Of course, custom parameters are definitely supported.
- SmartDialog custom Loading or Toast is very simple: However, when using, it may make you feel a little troublesome
for example
- Use custom Loading:
SmartDialog.showLoading(builder: (_) => CustomLoadingWidget);
- The effect we want must be like this:
SmartDialog.showLoading();
- Use custom Loading:
- In view of the above considerations, I added the function of customizing the default Loading and Toast styles at the entrance.
Let me show you the following
The entry needs to be configured: implement toastBuilder and loadingBuilder, and pass in custom Toast and Loading controls
- The parameters of the builder callback are passed from showToast and showLoading, you can use it or not, there is no mandatory
- But I think toast-style msg parameters will definitely be used by everyone. . .
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage,
// here
navigatorObservers: [FlutterSmartDialog.observer],
// here
builder: FlutterSmartDialog.init(
//default toast widget
toastBuilder: (String msg) => CustomToastWidget(msg: msg),
//default loading widget
loadingBuilder: (String msg) => CustomLoadingWidget(msg: msg),
),
);
}
}
Flutter_boost is used: the initialization method needs to be adjusted slightly
@override
Widget build(BuildContext context) {
//here
var initSmartDialog = FlutterSmartDialog.init(
toastBuilder: (String msg) => CustomToastWidget(msg: msg),
loadingBuilder: (String msg) => CustomLoadingWidget(msg: msg),
);
return FlutterBoostApp(
routeFactory,
initialRoute: 'mainPage',
appBuilder: (Widget home) {
return MaterialApp(
home: home,
debugShowCheckedModeBanner: true,
//here
navigatorObservers: [FlutterSmartDialog.observer],
//here
builder: (context, child) => initSmartDialog(context, home),
);
},
);
}
Minimal use
- toast use 💬
SmartDialog.showToast('test toast');
- loadingUse ⏳
SmartDialog.showLoading();
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();
- dialog use 🎨
SmartDialog.show(builder: (context) {
return Container(
height: 80,
width: 180,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(10),
),
alignment: Alignment.center,
child:
Text('easy custom dialog', style: TextStyle(color: Colors.white)),
);
});
OK, as shown above, you can call the corresponding function with very little code
Of course, there are still many places where special optimizations have been made. Later, I will describe it to you in detail.
Questions you may have
When initializing the framework, compared to before, I actually asked everyone to write one more parameter (without setting the custom default toast and loading styles), I feel very guilty 😩
Closing a page is inherently a more complex situation involving
- Physical return button
- AppBar's back button
- Manual pop
In order to monitor these situations, a route monitoring parameter was added as a last resort.
entity return key
The monitoring of the return button is very important and can basically cover most situations
pop route
Although the monitoring of the return button can cover most scenarios, some manual pop scenarios require additional parameter monitoring
Without
FlutterSmartDialog.observer
- If the penetration parameter is turned on (you can interact with the page after the pop-up window), then manually close the page
- this embarrassing situation
Add
FlutterSmartDialog.observer
, it can be handled reasonably- Of course, the transition animation here also provides parameter control whether to enable it😉
About FlutterSmartDialog.init()
This method will not occupy your builder parameters. The builder is called back from init, and you can continue to use it with confidence.
- For example: continue to set Bloc global instance 😄
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage,
navigatorObservers: [FlutterSmartDialog.observer],
builder: FlutterSmartDialog.init(builder: _builder),
);
}
}
Widget _builder(BuildContext context, Widget? child) {
return MultiBlocProvider(
providers: [
BlocProvider.value(value: BlocSpanOneCubit()),
],
child: child!,
);
}
Super practical parameter: backDismiss
This parameter is set to true by default, and the pop-up window will be closed by default when returning; if it is set to false, the page will not be closed
- In this way, it is very easy to make an emergency pop-up window, prohibiting the user's next operation
- Let's look at a scenario: Suppose an open source author decides to abandon the software and does not allow users to use the software's pop-up window.
SmartDialog.show(
backDismiss: false,
clickMaskDismiss: false,
builder: (_) {
return Container(
height: 480,
width: 500,
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: Wrap(
direction: Axis.vertical,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 10,
children: [
// title
Text(
'特大公告',
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
),
// content
Text('鄙人日夜钻研下面秘籍,终于成功钓到富婆'),
Image.network(
'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211102213746.jpeg',
height: 200,
width: 400,
),
Text('鄙人思考了三秒钟,怀着\'沉重\'的心情,决定弃坑本开源软件'),
Text('本人今后的生活是富婆和远方,已无\'精力\' 再维护本开源软件了'),
Text('各位叼毛,有缘江湖再见!'),
// button (only method of close the dialog)
ElevatedButton(
onPressed: () => SmartDialog.dismiss(),
child: Text('再会!'),
)
],
),
),
);
},
);
It can be seen from the above renderings that
- Click on the mask to close the popup
- Clicking the back button cannot close the popup
- We can only close the pop-up window by clicking our own button. The logic of clicking the button can be directly written as closing the app, etc.
Only two simple parameter settings are needed to achieve such a great emergency pop-up window
Set global parameters
The global parameters of SmartDialog have a reasonable default value
In order to cope with changing scenarios, you can modify the global parameters that meet your own requirements
Set the data that meets your requirements, put it in the app entry and initialize it
- Note: If there are no special requirements, you can not initialize the global parameters (there are default values inside)
SmartDialog.config
..custom = SmartConfigCustom()
..attach = SmartConfigAttach()
..loading = SmartConfigLoading()
..toast = SmartConfigToast();
- The comments of the code are very well written. If you don't understand a certain parameter, just click in and take a look.
Attach
This is a very important function. I wanted to add it for a long time, but I was busy and shelved it. It took some time to complete this function and related demos when New Year's Day (2022.1.1) started.
position
It is not difficult to locate the coordinates of the target widget; but it is necessary to get the size of the custom widget we pass in, so that the custom widget can be stacked in a more suitable position (through some calculations, the center point is obtained)
- In fact, Flutter provides a very suitable component
CustomSingleChildLayout
, this component also provides offset coordinate function, which is very suitable in theory - However, the
CustomSingleChildLayout
andSizeTransition
animation controls have a footprint conflict and can only useAnimatedOpacity
fading animation - Displacement animation can't be used, I can't bear it, I abandoned it
CustomSingleChildLayout
; I used all kinds of tricky operations, and finally got the size of the custom widget, which achieved the effect perfectly.
Locate the dialog and use the showAttach method. The parameter comments are written in great detail. If you don't understand the usage, just look at the comments.
Powerful positioning function
- The BuildContext of the target widget must be passed, and the coordinates and size of the target widget need to be calculated through it
void _attachLocation() {
attachDialog(BuildContext context, AlignmentGeometry alignment) async {
SmartDialog.showAttach(
targetContext: context,
usePenetrate: true,
alignment: alignment,
clickMaskDismiss: false,
builder: (_) => Container(width: 100, height: 100, color: randomColor()),
);
await Future.delayed(Duration(milliseconds: 350));
}
//target widget
List<BuildContext> contextList = [];
List<Future Function()> funList = [
() async => await attachDialog(contextList[0], Alignment.topLeft),
() async => await attachDialog(contextList[1], Alignment.topCenter),
() async => await attachDialog(contextList[2], Alignment.topRight),
() async => await attachDialog(contextList[3], Alignment.centerLeft),
() async => await attachDialog(contextList[4], Alignment.center),
() async => await attachDialog(contextList[5], Alignment.centerRight),
() async => await attachDialog(contextList[6], Alignment.bottomLeft),
() async => await attachDialog(contextList[7], Alignment.bottomCenter),
() async => await attachDialog(contextList[8], Alignment.bottomRight),
];
btn({
required String title,
required Function(BuildContext context) onTap,
}) {
return Container(
margin: EdgeInsets.all(25),
child: Builder(builder: (context) {
Color? color = title.contains('all') ? randomColor() : null;
contextList.add(context);
return Container(
width: 130,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: ButtonStyleButton.allOrNull<Color>(color),
),
onPressed: () => onTap(context),
child: Text('$title'),
),
);
}),
);
}
SmartDialog.show(builder: (_) {
return Container(
width: 700,
padding: EdgeInsets.all(50),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: SingleChildScrollView(
child: Wrap(alignment: WrapAlignment.spaceEvenly, children: [
btn(title: 'topLeft', onTap: (context) => funList[0]()),
btn(title: 'topCenter', onTap: (context) => funList[1]()),
btn(title: 'topRight', onTap: (context) => funList[2]()),
btn(title: 'centerLeft', onTap: (context) => funList[3]()),
btn(title: 'center', onTap: (context) => funList[4]()),
btn(title: 'centerRight', onTap: (context) => funList[5]()),
btn(title: 'bottomLeft', onTap: (context) => funList[6]()),
btn(title: 'bottomCenter', onTap: (context) => funList[7]()),
btn(title: 'bottomRight', onTap: (context) => funList[8]()),
btn(
title: 'allOpen',
onTap: (_) async {
for (var item in funList) await item();
},
),
btn(
title: 'allClose',
onTap: (_) => SmartDialog.dismiss(status: SmartStatus.allAttach),
),
]),
),
);
});
}
The animation effect and the show method are almost the same. For this consistent experience, a lot of targeted optimizations have been made internally.
custom coordinates
- In most cases, targetContext is basically used
SmartDialog.showAttach(
targetContext: context,
builder: (_) => Container(width: 100, height: 100, color: Colors.red),
);
Of course, there are still a few cases where custom coordinates need to be used. The targetBuilder parameter is also provided here: if the targetBuilder parameter is set, the targetContext will automatically become invalid.
- targetContext is a very common scenario, so here it is set as a mandatory parameter
- The callback parameters in targetBuilder are also calculated from targetContext
- In some special cases, the targetContext can be set to be empty, and the default value of the callback parameter in targetBuilder is zero
SmartDialog.showAttach(
targetContext: context,
target: Offset(100, 100);,
builder: (_) => Container(width: 100, height: 100, color: Colors.red),
);
- It seems that the custom coordinate point effect
void _attachPoint() async {
targetDialog(Offset offset) {
var random = Random().nextInt(100) % 5;
var alignment = Alignment.topCenter;
if (random == 0) alignment = Alignment.topCenter;
if (random == 1) alignment = Alignment.centerLeft;
if (random == 2) alignment = Alignment.center;
if (random == 3) alignment = Alignment.centerRight;
if (random == 4) alignment = Alignment.bottomCenter;
SmartDialog.showAttach(
targetContext: null,
targetBuilder: (_, __) => offset,
usePenetrate: true,
clickMaskDismiss: false,
alignment: alignment,
keepSingle: true,
builder: (_) {
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(width: 100, height: 100, color: randomColor()),
);
},
);
}
SmartDialog.show(builder: (_) {
return Container(
width: 600,
height: 400,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: GestureDetector(
onTapDown: (detail) => targetDialog(detail.globalPosition),
child: Container(
width: 500,
height: 300,
color: Colors.grey,
alignment: Alignment.center,
child: Text('click me', style: TextStyle(color: Colors.white)),
),
),
);
});
}
targetBuilder
is a very powerful parameter. Combine it with the scalePointBuilder
parameter to create a lot of interesting bubble pop-ups.
Mimic DropdownButton
Actually imitating DropdownButton is not easy
- First calculate the position of the DropdownButton control and display the clicked collapsed control on its position
- Need to handle the click event of the area outside the DropdownButton (close the DropdownButton outside the click area)
- You also need to listen to the return event and manually pop the routing event; for this type of event, you need to close the DropdownButton
- This thing needs to be customized, which is quite annoying; however, now you can use
SmartDialog.showAttach
to easily imitate one, and the above-mentioned matters needing attention have been handled for you
void _attachImitate() {
//模仿DropdownButton
imitateDialog(BuildContext context) {
var list = ['小呆呆', '小菲菲', '小猪猪'];
SmartDialog.showAttach(
targetContext: context,
usePenetrate: true,
builder: (_) {
return Container(
margin: EdgeInsets.all(10),
decoration: BoxDecoration(boxShadow: [
BoxShadow(color: Colors.black12, blurRadius: 8, spreadRadius: 0.2)
]),
child: Column(
children: List.generate(list.length, (index) {
return Material(
color: Colors.white,
child: InkWell(
onTap: () => SmartDialog.dismiss(),
child: Container(
height: 50,
width: 100,
alignment: Alignment.center,
child: Text('${list[index]}'),
),
),
);
}),
),
);
},
);
}
//imitate widget
dropdownButton({String title = 'Dropdown'}) {
return DropdownButton<String>(
value: '1',
items: [
DropdownMenuItem(value: '1', child: Text('$title:小呆呆')),
DropdownMenuItem(value: '2', child: Text('小菲菲')),
DropdownMenuItem(value: '3', child: Text('小猪猪'))
],
onChanged: (value) {},
);
}
imitateDropdownButton() {
return Builder(builder: (context) {
return Stack(children: [
dropdownButton(title: 'Attach'),
InkWell(
onTap: () => imitateDialog(context),
child: Container(height: 50, width: 140, color: Colors.transparent),
)
]);
});
}
SmartDialog.show(builder: (_) {
return Container(
width: 600,
height: 400,
alignment: Alignment.center,
margin: EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Container(
padding: EdgeInsets.symmetric(horizontal: 100),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [dropdownButton(), imitateDropdownButton()],
),
),
),
);
});
}
highlight
This time, the function of highlighting a specific area of the mask has been added, which is a very practical function!
- You only need to set
highlightBuilder
parameter To define the highlighted area, it must be an opaque Widget, such as Contaienr, and a color must be set (no color value required)
- It is also possible to use pictures of various strange shapes, so that the highlighted areas of various complex shapes can be displayed
-
highlightBuilder
return type is Positioned, you can locate any area on the screen that needs to be highlighted - You can quickly get the coordinates and size of the target widget through the parameters of the
highlightBuilder
callback
SmartDialog.showAttach(
targetContext: context,
alignment: Alignment.bottomCenter,
highlightBuilder: (Offset targetOffset, Size targetSize) {
return Positioned(
top: targetOffset.dy - 10,
left: targetOffset.dx - 10,
child: Container(
height: targetSize.height + 20,
width: targetSize.width + 20,
color: Colors.white,
),
);
},
builder: (_) => Container(width: 100, height: 100, color: Colors.red),
)
actual business scenarios
- Here are two common examples. The code is a little too much, so I won't post it. If you are interested, please check: flutter_use
The above two business scenarios are very common. Sometimes, we need to be above or below the target widget or a specific area without being covered by a mask.
If you do it yourself, you can do it, but it will be very troublesome; now you can use the showAttach
highlightBuilder
parameter in ---481492ef746ed3b915b58a3b71a49c09--- to easily achieve this requirement
bootstrap action
Guidance operations are still very common in apps, and you need to specify the area to highlight, and then introduce its functions
Using the
showAttach
highlightBuilder
parameter in ---1cd696d9016b3cdea5d7c9edea149a69---, this requirement can also be easily achieved. Let's see the effect.- The code is also a little bit more, if you are interested, please check: flutter_use
Dialog chapter
bells and whistles
The pop-up window pops up from different positions, and the animation is different
- Alignment: If this parameter is set differently, the animation effect will be different.
void _dialogLocation() async {
locationDialog({
required AlignmentGeometry alignment,
double width = double.infinity,
double height = double.infinity,
}) async {
SmartDialog.show(
alignment: alignment,
builder: (_) => Container(width: width, height: height, color: randomColor()),
);
await Future.delayed(Duration(milliseconds: 500));
}
//left
await locationDialog(width: 70, alignment: Alignment.centerLeft);
//top
await locationDialog(height: 70, alignment: Alignment.topCenter);
//right
await locationDialog(width: 70, alignment: Alignment.centerRight);
//bottom
await locationDialog(height: 70, alignment: Alignment.bottomCenter);
//center
await locationDialog(height: 100, width: 100, alignment: Alignment.center);
}
- usePenetrate: Interaction events penetrate the mask
SmartDialog.show(
alignment: Alignment.centerRight,
usePenetrate: true,
clickMaskDismiss: false,
builder: (_) {
return Container(
width: 80,
height: double.infinity,
color: randomColor(),
);
},
);
dialog stack
- This is a powerful and practical function: you can easily close a pop-up window at a fixed point
void _dialogStack() async {
stackDialog({
required AlignmentGeometry alignment,
required String tag,
double width = double.infinity,
double height = double.infinity,
}) async {
SmartDialog.show(
tag: tag,
alignment: alignment,
builder: (_) {
return Container(
width: width,
height: height,
color: randomColor(),
alignment: Alignment.center,
child: Text('dialog $tag', style: TextStyle(color: Colors.white)),
);
},
);
await Future.delayed(Duration(milliseconds: 500));
}
//left
await stackDialog(tag: 'A', width: 70, alignment: Alignment.centerLeft);
//top
await stackDialog(tag: 'B', height: 70, alignment: Alignment.topCenter);
//right
await stackDialog(tag: 'C', width: 70, alignment: Alignment.centerRight);
//bottom
await stackDialog(tag: 'D', height: 70, alignment: Alignment.bottomCenter);
//center:the stack handler
SmartDialog.show(
alignment: Alignment.center,
builder: (_) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 20),
child: Wrap(spacing: 20, children: [
ElevatedButton(
child: Text('close dialog A'),
onPressed: () => SmartDialog.dismiss(tag: 'A'),
),
ElevatedButton(
child: Text('close dialog B'),
onPressed: () => SmartDialog.dismiss(tag: 'B'),
),
ElevatedButton(
child: Text('close dialog C'),
onPressed: () => SmartDialog.dismiss(tag: 'C'),
),
ElevatedButton(
child: Text('close dialog D'),
onPressed: () => SmartDialog.dismiss(tag: 'D'),
),
]),
);
},
);
}
Loading chapter
Pit Avoidance Guide
After loading is turned on, it can be turned off using the following methods
- SmartDialog.dismiss(): can close loading and dialog
- status is set to SmartStatus.loading: just turn off loading
// easy close
SmartDialog.dismiss();
// exact close
SmartDialog.dismiss(status: SmartStatus.loading);
Generally speaking, the loading pop-up window is encapsulated in the network library, and automatically opens and closes with the request status.
- Based on this scenario, I suggest: when using dismiss, add the status parameter and set it to: SmartStatus.loading
pit ratio scene
- When the network request is loaded, the loading is also turned on. At this time, it is easy to touch the back button by mistake and close the loading.
- When the network request ends, the dismiss method is automatically called
- Because the loading has been closed, assuming that the page has a SmartDialog pop-up window at this time, dismiss if the status is not set will close the SmartDialog pop-up window.
- Of course, this situation is easy to solve, encapsulate the loading of the network library, use:
SmartDialog.dismiss(status: SmartStatus.loading);
just close it
status
parameters are designed to accurately close the corresponding type of pop-up window, which can play a huge role in some special scenarios- If you understand the meaning of this parameter, you must be confident about when to add the
status
parameter.
- If you understand the meaning of this parameter, you must be confident about when to add the
Parameter Description
The parameters are very detailed in the comments, so I won't go into details, let's see the effect
- maskWidget: Powerful mask customization function 😆, use your brain. . .
var maskWidget = Container(
width: double.infinity,
height: double.infinity,
child: Opacity(
opacity: 0.6,
child: Image.network(
'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101103911.jpeg',
fit: BoxFit.fill,
),
),
);
SmartDialog.showLoading(maskWidget: maskWidget);
- maskColor: supports quick custom mask color
SmartDialog.showLoading(maskColor: randomColor().withOpacity(0.3));
/// random color
Color randomColor() => Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);
- animationType: animation effect switching
SmartDialog.showLoading(animationType: SmartAnimationType.scale);
- usePenetrate: Interaction events can penetrate the mask, which is a very useful function and is critical for some special demand scenarios
SmartDialog.showLoading(usePenetrate: true);
Custom Loading
Using showLoading
can easily customize a powerful loading pop-up window; I have limited brains, so I will simply demonstrate
Customize a loading layout
class CustomLoading extends StatefulWidget {
const CustomLoading({Key? key, this.type = 0}) : super(key: key);
final int type;
@override
_CustomLoadingState createState() => _CustomLoadingState();
}
class _CustomLoadingState extends State<CustomLoading>
with TickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_controller.forward();
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reset();
_controller.forward();
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(children: [
// smile
Visibility(visible: widget.type == 0, child: _buildLoadingOne()),
// icon
Visibility(visible: widget.type == 1, child: _buildLoadingTwo()),
// normal
Visibility(visible: widget.type == 2, child: _buildLoadingThree()),
]);
}
Widget _buildLoadingOne() {
return Stack(alignment: Alignment.center, children: [
RotationTransition(
alignment: Alignment.center,
turns: _controller,
child: Image.network(
'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101174606.png',
height: 110,
width: 110,
),
),
Image.network(
'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101181404.png',
height: 60,
width: 60,
),
]);
}
Widget _buildLoadingTwo() {
return Stack(alignment: Alignment.center, children: [
Image.network(
'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101162946.png',
height: 50,
width: 50,
),
RotationTransition(
alignment: Alignment.center,
turns: _controller,
child: Image.network(
'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101173708.png',
height: 80,
width: 80,
),
),
]);
}
Widget _buildLoadingThree() {
return Center(
child: Container(
height: 120,
width: 180,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
alignment: Alignment.center,
child: Column(mainAxisSize: MainAxisSize.min, children: [
RotationTransition(
alignment: Alignment.center,
turns: _controller,
child: Image.network(
'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101163010.png',
height: 50,
width: 50,
),
),
Container(
margin: EdgeInsets.only(top: 20),
child: Text('loading...'),
),
]),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
to see the effect
- effect one
SmartDialog.showLoading(
animationType: SmartAnimationType.scale,
builder: (_) => CustomLoading(),
);
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();
- effect two
SmartDialog.showLoading(
animationType: SmartAnimationType.scale,
builder: (_) => CustomLoading(type: 1),
);
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();
- effect three
SmartDialog.showLoading(builder: (_) => CustomLoading(type: 2));
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();
Toast
The particularity of toast
Strictly speaking, toast is a very special pop-up window, I think it should have the following characteristics
Toast messages should be displayed one by one, and subsequent messages should not top off the previous toast
This is a pit point. If the inside of the frame is not dealt with, it is easy to cause the toast in the back to directly top off the toast in the front.
- Of course, the type parameter is provided internally, and you can choose the display logic you want
Displayed on the top layer of the page, it should not be blocked by some pop-up windows and the like
- It can be found that the layout of loading and dialog masks and other layouts do not block the toast information.
Deal with keyboard occlusion
The keyboard is a bit pitted, it will directly block all layouts, and it can only save the country with curves
- A special treatment is made here. When the keyboard is aroused, the toast will dynamically adjust the distance between itself and the bottom of the screen.
- This can play a role, the keyboard will not block the toast effect
Custom Toast
Parameter Description
Some parameters of toast are not exposed, only msg is exposed
For example: toast font size, font color, toast background color, etc., no parameters are provided
- First, I feel that the provision of these parameters will make the overall parameter input very large, and the random flowers will gradually enter the charming eyes.
- The second is that even if I provide a lot of parameters, it may not meet those strange aesthetics and needs
Based on the above considerations, I directly provided a large number of low-level parameters
You can customize the toast as you like
- Note that not only toasts can be customized, such as: success prompts, failure prompts, warning prompts, etc.
- Toast has done a lot of optimization,
displayType
parameters, so that you can have a variety of display logic, use your imagination
- Note: If the
builder
parameter is used, themsg
parameter will be invalid
More powerful custom toast
- First, a whole custom toast
class CustomToast extends StatelessWidget {
const CustomToast(this.msg, {Key? key}) : super(key: key);
final String msg;
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
margin: EdgeInsets.only(bottom: 30),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 7),
decoration: BoxDecoration(
color: _randomColor(),
borderRadius: BorderRadius.circular(100),
),
child: Row(mainAxisSize: MainAxisSize.min, children: [
//icon
Container(
margin: EdgeInsets.only(right: 15),
child: Icon(Icons.add_moderator, color: _randomColor()),
),
//msg
Text('$msg', style: TextStyle(color: Colors.white)),
]),
),
);
}
Color _randomColor() {
return Color.fromRGBO(
Random().nextInt(256),
Random().nextInt(256),
Random().nextInt(256),
1,
);
}
}
- use
SmartDialog.showToast('', builder: (_) => CustomToast('custom toast'));
- Effect
Angry little tricks
There is a scene comparing egg cones
- We use StatefulWidget to encapsulate a widget
- In a special case, we need to trigger a method inside this component outside this component
- For this scenario, there are many ways to implement it, but it may be a bit troublesome to get it
Here is a simple idea that can be easily triggered, a method inside the component
- build a widget
class OtherTrick extends StatefulWidget {
const OtherTrick({Key? key, this.onUpdate}) : super(key: key);
final Function(VoidCallback onInvoke)? onUpdate;
@override
_OtherTrickState createState() => _OtherTrickState();
}
class _OtherTrickState extends State<OtherTrick> {
int _count = 0;
@override
void initState() {
// here
widget.onUpdate?.call(() {
_count++;
setState(() {});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 50, vertical: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
child: Text('Counter: $_count ', style: TextStyle(fontSize: 30.0)),
),
);
}
}
- Show this component, then trigger it externally
void _otherTrick() async {
VoidCallback? callback;
// display
SmartDialog.show(
alignment: Alignment.center,
builder: (_) =>
OtherTrick(onUpdate: (VoidCallback onInvoke) => callback = onInvoke),
);
await Future.delayed(const Duration(milliseconds: 500));
// handler
SmartDialog.show(
alignment: Alignment.centerRight,
maskColor: Colors.transparent,
builder: (_) {
return Container(
height: double.infinity,
width: 150,
color: Colors.white,
alignment: Alignment.center,
child: ElevatedButton(
child: const Text('add'),
onPressed: () => callback?.call(),
),
);
},
);
}
- Let's see the effect
at last
Related address
- github: flutter_smart_dialog
- The demo address in the text: flutter_use
- demo demonstration effect: online experience
Hey, people are always moving forward in constant confusion. . .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。