1
头图

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
 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();
  • 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');

toastDefault

  • loadingUse
 SmartDialog.showLoading();
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();

loadingDefault

  • 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)),
  );
});

dialogEasy

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

initBack

pop route

Although the monitoring of the return button can cover most scenarios, some manual pop scenarios require additional parameter monitoring

  • Without adding 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

initPopOne

  • Add FlutterSmartDialog.observer , it can be handled reasonably

    • Of course, the transition animation here also provides parameter control whether to enable it😉

initPopTwo

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('再会!'),
            )
          ],
        ),
      ),
    );
  },
);

hardClose

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 after the start of New Year's Day (2022.1.1).

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 and SizeTransition animation controls have a footprint conflict and can only use AnimatedOpacity 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, and it 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),
          ),
        ]),
      ),
    );
  });
}

attachLocation

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)),
        ),
      ),
    );
  });
}

attachPoint

targetBuilder is a very powerful parameter. Combining it with the scalePointBuilder parameter can make a lot of very interesting bubble popups.

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 points need to be paid attention to 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()],
          ),
        ),
      ),
    );
  });
}

attachImitate

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 highlightBuilder callback parameter
 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

attachBusiness

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 ---76e6db8b6a5a1c893b07e64280654248--- 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 ---9d7426e5c7aa7d3312ae141abecb5252---, 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

attachGuide

Dialog chapter

bells and whistles

The pop-up window pops up from different positions, and the animation is different

image-20211031221419600

  • 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);
}

dialogLocation

  • 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(),
    );
  },
);

dialogPenetrate

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'),
          ),
        ]),
      );
    },
  );
}

dialogStack

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, then you must be confident about when to add the status parameter

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);

loadingOne

  • 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);

loadingTwo

  • animationType: animation effect switching
 SmartDialog.showLoading(animationType: SmartAnimationType.scale);

loadingFour

  • 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);

loadingFive

Custom Loading

Using showLoading can easily customize a powerful loading pop-up window; I have limited brain holes, 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();

loadingSmile

  • effect two
 SmartDialog.showLoading(
  animationType: SmartAnimationType.scale,
  builder: (_) => CustomLoading(type: 1),
);
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();

loadingIcon

  • effect three
 SmartDialog.showLoading(builder: (_) => CustomLoading(type: 2));
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();

loadingNormal

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

toastOne

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.

toastTwo

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

toastSmart

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, the msg 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

toastCustom

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

trick

at last

Related address
Hey, people are always moving forward in constant confusion. . .

梦


小呆呆666
177 声望1.1k 粉丝

Android Flutter