Let Flutter transform your dialog! (Attach,Dialog,Loading,Toast)

Posted by DarkEden on Tue, 04 Jan 2022 11:13:43 +0100

preface

Q: What's the most smelly thing you've ever smelled in your life?

A: My rotten dream.

Brother Meng!!! I'm here again!

This time, I can confidently say to you: I finally brought you a pub package that can really help you solve many pit ratio scenes!

Put the previous shuttle_ smart_ Dialog, on the basis of maintaining the stability of the api, has carried out various scratch head reconfigurations and solved a series of problems

Now, I can finally say: it is now a simple, powerful and low intrusive pub package!

On invasive issues

  • Before, in order to solve the problem of closing the pop-up window, a very inelegant solution was used, resulting in a little high invasiveness
  • This really makes me on pins and needles, like a thorn in my back, like a lump in my throat. This problem has finally been solved!

At the same time, I designed a pop-up stack inside the pub package, which can automatically remove the pop-up window at the top of the stack or remove the marked pop-up window in the stack at a fixed point.

Existing problems

Using the system pop-up window, there are a series of pits to discuss with you

  • BuildContext must be passed

    • In some scenes, we must do more communication and reference work, which is a painful but not difficult problem
  • loading Popup

    • Using the system pop-up window as the loading pop-up window, I must have encountered this pit ratio problem
      • Loading is encapsulated in the network library: when loading is requested for the network, press the return button and close loading
      • Then after the request, I found: why is my page closed!!!
    • The system pop-up window is a routing page. 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. There is no detailed table here
  • Multiple system dialogs pop up on a page. It is difficult to close a non stack pop-up window at a fixed point

    • Eggs, which are caused by routing stack, make complaints about them.
  • In the system Dialog, the click event cannot penetrate the dark background

    • I really can't do anything about this pit ratio problem

Relevant thinking

The above lists some common problems. The most serious problem should be the problem of loading

  • Loading is a pop-up window for UHF use. The method of closing the loading pop-up window can also close the normally used pages, which is a hidden danger in itself

  • Penetrating 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, we can also highly customize overlay!

This time, I want to help you solve it at one time: toast message, loading pop-up window, and more powerful custom dialog!

Get started quickly

initialization

dependencies:
  flutter_smart_dialog: ^3.2.0
  • More concise access mode 😊
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(),
    );
  }
}

Minimalist use

  • toast usage 💬
SmartDialog.showToast('test toast');

  • loading usage ⏳
SmartDialog.showLoading();
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss(); 

  • dialog usage 🎨
var custom = Container(
    height: 80,
    width: 180,
    decoration: BoxDecoration(
        color: Colors.black,
        borderRadius: BorderRadius.circular(20),
    ),
    alignment: Alignment.center,
    child: Text('easy custom dialog', style: TextStyle(color: Colors.white)),
);
// here
SmartDialog.show(widget: custom, isLoadingTemp: false);

OK, as shown above, you can call the corresponding functions with very little code

Of course, there are many special optimizations in the interior. Next, I will describe them in detail

You may have questions

When initializing the framework, I felt very guilty for letting everyone write one more parameter than before 😩

Closing a page is essentially a complex situation involving

  • Physical return key
  • back button of AppBar
  • Manual pop

In order to monitor these conditions, a route monitoring parameter has to be added

Entity return key

Monitoring the return button is very important and can basically cover most situations

pop routing

Although the monitoring of the return button can cover most scenarios, some manual pop scenarios need to add parameter monitoring

  • Without fluttersmartdialog observer
    • If the penetration parameter is turned on (you can interact with the page after the pop-up window), then close the page manually
    • There will be such an embarrassing situation

  • Added fluttersmartdialog Observer, you can handle it reasonably
    • Of course, the transition animation here also provides parameters to control whether it is turned on 😉

About fluttersmartdialog init()

This method will not occupy your builder parameters. The init internal callback comes out of the builder. You can continue to set it boldly and safely

  • For example: continue to set the global instance of Bloc 😄
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: backdisassiss

  • This parameter is set to true by default, and the pop-up window will be closed by default when returning; If set to false, the page will not be closed
    • In this way, you can easily make an emergency pop-up window to prohibit 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 pop-up window of the software
SmartDialog.show(
  // here
  backDismiss: false,
  clickBgDismissTemp: false,
  isLoadingTemp: false,
  widget: 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(
            'Extraordinary announcement',
            style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
          ),
          // content
          Text('I studied the following secret script day and night and finally succeeded in catching a rich woman'),
          Image.network(
            'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211102213746.jpeg',
            height: 200,
            width: 400,
          ),
          Text('I thought for three seconds, with\'heavy\'I decided to abandon the open source software'),
          Text('My future life is a rich woman and far away, no longer\'energy\' Maintain this open source software'),
          Text('I'll see you in the Jianghu!'),
          // button (only method of close the dialog)
          ElevatedButton(
            onPressed: () => SmartDialog.dismiss(),
            child: Text('Goodbye!'),
          )
        ],
      ),
    ),
  ),
);

As can be seen from the effect drawing above

  • Click the mask to close the pop-up window
  • Click the back button to close the pop-up window
  • We can only click our own button to close the pop-up window. The logic of clicking the button can be directly written as closing app and so on

It only needs two simple parameter settings to realize 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 changeable scenarios, you can modify the global parameters to 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
  ..alignment = Alignment.center
  ..isPenetrate = false
  ..clickBgDismiss = true
  ..maskColor = Colors.black.withOpacity(0.35)
  ..maskWidget = null
  ..animationDuration = Duration(milliseconds: 260)
  ..isUseAnimation = true
  ..isLoading = true
  ..antiShake = false
  ..antiShakeTime = Duration(milliseconds: 300);
  • The code comments are well written. If you don't understand a parameter, just click in and have a look

Attach

This is a very important function. I wanted to add it for a long time, but it's busy and has been shelved; At the beginning of new year's Day (January 1, 2022), it took some time to complete this function and related demo

location

Locating the coordinates of the target widget is not difficult; However, we must get the size of the custom widget we passed in, so that we can stack the custom widget in a more appropriate position (obtain the center point through some calculations)

  • In fact, Flutter provides a very suitable component CustomSingleChildLayout, which also provides the function of offset coordinates, which is very suitable
  • However, there is a footprint conflict between the CustomSingleChildLayout and SizeTransition animation controls. Only AnimatedOpacity fade animation can be used
  • Displacement animation can't be used, which I can't bear. I abandon CustomSingleChildLayout; After using various operations, I 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
var attach = (BuildContext context, AlignmentGeometry alignment) async {
  SmartDialog.showAttach(
    targetContext: context,
    isPenetrateTemp: true,
    alignmentTemp: alignment,
    clickBgDismissTemp: false,
    widget: Container(width: 100, height: 100, color: randomColor()),
  );
  await Future.delayed(Duration(milliseconds: 350));
};

//target widget
List<BuildContext> contextList = [];
List<Future Function()> funList = [
  () async => await attach(contextList[0], Alignment.topLeft),
  () async => await attach(contextList[1], Alignment.topCenter),
  () async => await attach(contextList[2], Alignment.topRight),
  () async => await attach(contextList[3], Alignment.centerLeft),
  () async => await attach(contextList[4], Alignment.center),
  () async => await attach(contextList[5], Alignment.centerRight),
  () async => await attach(contextList[6], Alignment.bottomLeft),
  () async => await attach(contextList[7], Alignment.bottomCenter),
  () async => await attach(contextList[8], Alignment.bottomRight),
];
var btn = ({
  required String title,
  required Function(BuildContext context) onTap,
}) {
  return 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(
  isLoadingTemp: false,
  widget: Container(
    width: 700,
    padding: EdgeInsets.all(70),
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(20),
      color: Colors.white,
    ),
    child: Wrap(
      spacing: 50,
      runSpacing: 50,
      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 is almost consistent with the show method. For this consistent experience, considerable targeted optimization has been made internally

Custom coordinate points

  • In most cases, targetContext is basically used
SmartDialog.showAttach(
  targetContext: context,
  widget: Container(width: 100, height: 100, color: Colors.red),
);
  • Of course, there are a few cases where user-defined coordinates need to be used. The target parameter is also provided here: if the target parameter is set, the targetContext will automatically become invalid
    • targetContext is a very common scenario, so it is set as a required parameter here, but you can set it to null
SmartDialog.showAttach(
  targetContext: null,
  target: Offset(100, 100);,
  widget: Container(width: 100, height: 100, color: Colors.red),
);
  • It seems that the custom coordinate point effect
var attach = (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,
    target: offset,
    isPenetrateTemp: true,
    clickBgDismissTemp: false,
    alignmentTemp: alignment,
    keepSingle: true,
    widget: ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: Container(width: 100, height: 100, color: randomColor()),
    ),
  );
};

SmartDialog.show(
  isLoadingTemp: false,
  widget: Container(
    width: 600,
    height: 400,
    alignment: Alignment.center,
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(20),
      color: Colors.white,
    ),
    child: GestureDetector(
      onTapDown: (detail) => attach(detail.globalPosition),
      child: Container(
        width: 500,
        height: 300,
        color: Colors.grey,
        alignment: Alignment.center,
        child: Text('click me', style: TextStyle(color: Colors.white)),
      ),
    ),
  ),
);

Imitate DropdownButton

  • In fact, it's not easy to imitate the DropdownButton
    • First, calculate the position of the DropdownButton control and display the collapsed control after clicking on it
    • Click events outside the DropdownButton area need to be handled (click outside the area to close the DropdownButton)
    • You also need to listen to return events and manually route pop events; For this kind of event, you need to close the DropdownButton
  • This thing needs to be customized. It's very big; But now you can use smartdialog Showattach easily imitates one. The above matters needing attention have been handled for you
//Imitate DropdownButton
var imitate = (BuildContext context) {
  var list = ['Little fool', 'Little Feifei', 'Little pig'];
  SmartDialog.showAttach(
    targetContext: context,
    isPenetrateTemp: true,
    widget: 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
var dropdownButton = ({String title = 'Dropdown'}) {
  return DropdownButton<String>(
    value: '1',
    items: [
      DropdownMenuItem(value: '1', child: Text('$title: Little fool')),
      DropdownMenuItem(value: '2', child: Text('Little Feifei')),
      DropdownMenuItem(value: '3', child: Text('Little pig'))
    ],
    onChanged: (value) {},
  );
};
var imitateDropdownButton = () {
  return Builder(builder: (context) {
    return Stack(children: [
      dropdownButton(title: 'Attach'),
      GestureDetector(
        onTap: () => imitate(context),
        child: Container(height: 50, width: 140, color: Colors.transparent),
      )
    ]);
  });
};
SmartDialog.show(
  isLoadingTemp: false,
  widget: Container(
    width: 600,
    height: 400,
    alignment: Alignment.center,
    padding: EdgeInsets.symmetric(horizontal: 100),
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(20),
      color: Colors.white,
    ),
    child: MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [dropdownButton(), imitateDropdownButton()],
      ),
    ),
  ),
);

Highlight

This time, the function of highlighting a specific area of the mask is added, which is a very practical function!

  • You just need to set the highlight parameter
    • To define the highlighted area, it must be a opaque Widget, such as Contaienr, and a color must be set (color value is not required)
      • It's OK to use all kinds of strange pictures, so you can display the highlighted areas of various complex graphics
    • The highlight type is Positioned. You can locate any area on the screen that needs to be highlighted
SmartDialog.showAttach(
  targetContext: context,
  alignmentTemp: Alignment.bottomCenter,
  highlight: Positioned(
    right: 190,
    bottom: 190,
    child: Container(
      height: 120,
      width: 120,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(10),
        color: Colors.white,
      ),
    ),
  ),
  widget: Container(width: 100, height: 100, color: Colors.red),
);

Actual business scenario

  • Here are two common examples. If there is a little bit more code, we won't post it. Please check it out if you are interested: flutter_use

The above two business scenarios are very common. Sometimes, we need a specific area above or below the target widget that is not covered by a mask

If you do it yourself, you can do it, but it will be very troublesome; Now you can easily implement this requirement using the highlight parameter in showAttach

Boot operation

The boot operation is still very common on the app. You need to specify an area to highlight and then introduce its functions

  • You can also easily implement this requirement by using the highlight parameter in showAttach. Let's see the effect
    • There are also a little more codes. For those interested, please check: flutter_use

Dialog

garish

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

  • alignmentTemp: the animation effect will be different with different parameter settings
var location = ({
  double width = double.infinity,
  double height = double.infinity,
}) {
  return Container(width: width, height: height, color: randomColor());
};

//left
SmartDialog.show(
  widget: location(width: 70),
  alignmentTemp: Alignment.centerLeft,
);
await Future.delayed(Duration(milliseconds: 500));
//top
SmartDialog.show(
  widget: location(height: 70),
  alignmentTemp: Alignment.topCenter,
);
await Future.delayed(Duration(milliseconds: 500));
//right
SmartDialog.show(
  widget: location(width: 70),
  alignmentTemp: Alignment.centerRight,
);
await Future.delayed(Duration(milliseconds: 500));
//bottom
SmartDialog.show(
  widget: location(height: 70),
  alignmentTemp: Alignment.bottomCenter,
);
await Future.delayed(Duration(milliseconds: 500));
//center
SmartDialog.show(
  widget: location(height: 100, width: 100),
  alignmentTemp: Alignment.center,
  isLoadingTemp: false,
);

  • isPenetrateTemp: interactive event penetration mask
SmartDialog.show(
    alignmentTemp: Alignment.centerRight,
    isPenetrateTemp: true,
    clickBgDismissTemp: false,
    widget: 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
var stack = ({
  double width = double.infinity,
  double height = double.infinity,
  String? msg,
}) {
  return Container(
    width: width,
    height: height,
    color: randomColor(),
    alignment: Alignment.center,
    child: Text('dialog $msg', style: TextStyle(color: Colors.white)),
  );
};

//left
SmartDialog.show(
  tag: 'A',
  widget: stack(msg: 'A', width: 70),
  alignmentTemp: Alignment.centerLeft,
);
await Future.delayed(Duration(milliseconds: 500));
//top
SmartDialog.show(
  tag: 'B',
  widget: stack(msg: 'B', height: 70),
  alignmentTemp: Alignment.topCenter,
);
await Future.delayed(Duration(milliseconds: 500));
//right
SmartDialog.show(
  tag: 'C',
  widget: stack(msg: 'C', width: 70),
  alignmentTemp: Alignment.centerRight,
);
await Future.delayed(Duration(milliseconds: 500));
//bottom
SmartDialog.show(
  tag: 'D',
  widget: stack(msg: 'D', height: 70),
  alignmentTemp: Alignment.bottomCenter,
);
await Future.delayed(Duration(milliseconds: 500));

//center: the stack handler
SmartDialog.show(
  alignmentTemp: Alignment.center,
  isLoadingTemp: false,
  widget: 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 article

Pit avoidance Guide

  • After loading is enabled, you can close it in the following ways
    • SmartDialog. Disass (): you can close loading and dialog
    • status is set to smartstatus Loading: Just close 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 that when using dismiss, add the status parameter and set it to smartstatus loading
  • Pit ratio scene
    • When the network requests to load, loading is also opened. At this time, it is easy to touch the return button by mistake to close loading
    • When the network request ends, the dismiss method is automatically called
    • Because loading has been closed, assuming that there is a SmartDialog pop-up window on the page at this time, disass without status will close the SmartDialog pop-up window
    • Of course, this situation is easy to solve. The loading encapsulated in the network library uses: smartdialog dismiss(status: SmartStatus.loading); Just close it
  • The status parameter is designed to accurately close the pop-up window of the corresponding type. It can play a great role in some special scenarios
    • If you understand the meaning of this parameter, you will know when to add the status parameter

Parameter description

The parameters are written in the notes in great detail, so I won't repeat them. Let's see the effect

  • maskWidgetTemp: powerful mask customization 😆, Play your brain hole...
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(maskWidgetTemp: maskWidget);

  • maskColorTemp: supports quick custom mask colors
SmartDialog.showLoading(maskColorTemp: randomColor().withOpacity(0.3));

/// random color
Color randomColor() => Color.fromRGBO(
    Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);

  • Background: supports loading custom background
SmartDialog.showLoading(background: randomColor());

/// random color
Color randomColor() => Color.fromRGBO(
    Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);

  • isLoadingTemp: animation effect Toggle
SmartDialog.showLoading(isLoadingTemp: false);

  • isPenetrateTemp: interactive events can penetrate the mask, which is a very useful function and is key to some special demand scenarios
SmartDialog.showLoading(isPenetrateTemp: true);

Custom Loading

Using showLoading, you can easily customize a powerful loading pop-up window; My brain hole is limited. Let's simply demonstrate it

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

Let's see the effect

  • Effect one
SmartDialog.showLoading(isLoadingTemp: false, widget: CustomLoading());
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();

  • Effect II
SmartDialog.showLoading(
    isLoadingTemp: false,
    widget: CustomLoading(type: 1),
);
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();

  • Effect three
SmartDialog.showLoading(widget: CustomLoading(type: 2));
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();

Toast

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. If the interior of the frame is not handled, it is easy to see that the rear toast will directly top the front toast
    • Of course, the type parameter is provided internally, and you can choose the display logic you want

It is displayed at the top of the page and should not be obscured by some pop-up windows

  • It can be found that the masks of loading and dialog do not obscure the toast information

Handle the occlusion of the keyboard

  • The keyboard is a bit of a pit. It will directly block all the layouts and can only save the country by curving
    • A special treatment is made here. When the keyboard is aroused, toast will dynamically adjust the distance between itself and the bottom of the screen
    • In this way, the keyboard will not block the toast effect

Customize Toast

Parameter description

Some parameters of toast are not exposed, only msg and alignment are exposed

  • For example, toast font size, font color, toast background color, etc. do not provide parameters
    • First, I feel that providing these parameters will make the overall parameter input very much, and the confusion will gradually become charming
    • Second, I feel that even if I provide many parameters, it will not necessarily meet those strange aesthetic and needs
  • Based on the above considerations, I directly provide a large number of underlying parameters
    • You can customize toast as you like
      • Note that you can not only customize toast, such as success prompt, failure prompt, warning prompt, etc
      • Toast has made a lot of optimizations and type parameters, so that you can have a variety of display logic and give full play to your imagination
    • Note: if widget parameters are used, msg and alignment parameters will become invalid

Adjust the position of toast display

SmartDialog.showToast('the toast at the bottom');
SmartDialog.showToast('the toast at the center', alignment: Alignment.center);
SmartDialog.showToast('the toast at the top', alignment: Alignment.topCenter);

More powerful custom toast

  • First, the 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('', widget: CustomToast('custom toast'));
  • effect

Coquettish tips

There is a scene that compares egg cones

  • We use stateful widget to encapsulate a small component
  • In a special case, we need to trigger a method inside the component outside the component
  • There are many implementation methods for this scenario, but it may be a little troublesome

Here is a simple idea, which can easily trigger a method inside the component

  • Create 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 the component and trigger it externally
VoidCallback? callback;

// display
SmartDialog.show(
  alignmentTemp: Alignment.center,
  widget: OtherTrick(
    onUpdate: (VoidCallback onInvoke) => callback = onInvoke,
  ),
);

await Future.delayed(Duration(milliseconds: 500));

// handler
SmartDialog.show(
  alignmentTemp: Alignment.centerRight,
  maskColorTemp: Colors.transparent,
  widget: Container(
    height: double.infinity,
    width: 150,
    color: Colors.white,
    alignment: Alignment.center,
    child: ElevatedButton(
      child: Text('add'),
      onPressed: () => callback?.call(),
    ),
  ),
);
  • Let's see the effect

last

Relevant address

Alas, people always move forward in constant confusion...

Topics: .NET dart jsdelivr