Fluent easyloading – making global toast / loading easier

Time:2020-9-29

flutter_easyloadingAn easy to useFlutterPlug in, including 23 kinds of loading animation effect, progress bar display, toast display. Pure flutter side implementation, supporting IOS and Android.

Open source address:https://github.com/huangjiank…

preface

FlutteryesGoogleA set of open source cross platform launched in 2017UIFramework, which can be quickly used iniOSAndroidandWebBuild high quality native user interface on the platform.FlutterSo far, it can be said that it is highly sought after, attracting a large number ofAppNative developersWebIt is also due to the continuous investment of developers in their armsFlutterIt is a new star in the cross platform field. Generally speaking, its ecology is not very perfect at present. I believe that for students who are used to the original development, looking for the wheel certainly does not have the feeling of being ready to do it. For example, this article will talk about how to use theFlutterSimple and convenient display in applicationToastperhapsLoadingWhat about the frame?

explore

At first, I also found several excellent plug-ins in pub

  • Fluttertoast: this plug-in should be a lot of new entrantsFlutterIt depends on the native, but for UI level problems, it is best to solve them in the flutter side, so as to facilitate later maintenance and reduce compatibility problems;
  • flutter_ Oktoast: pureFlutterEnd implementation, easy to call. But it lacksloading, progress bar display, can still be customized implementation;

After trial, we found that these plug-ins can not meet our product needs more or less. So we built such a wheel according to the needs of our products. We also hope that we can help students in need. Effect preview:

Fluent easyloading - making global toast / loading easier

realization

ShowDialog implementation

Let’s take a look at the way we implemented the pop-up window in the early daysshowDialogSome source codes are as follows:

Future<T> showDialog<T>({
  @required BuildContext context,
  bool barrierDismissible = true,
  @Deprecated(
    'Instead of using the "child" argument, return the child from a closure '
    'provided to the "builder" argument. This will ensure that the BuildContext '
    'is appropriate for widgets built in the dialog. '
    'This feature was deprecated after v0.2.3.'
  )
  Widget child,
  WidgetBuilder builder,
  bool useRootNavigator = true,
})

Here is a required parametercontextI’m sure I haveFlutterStudents who have been developing for a period of time will be rightBuildContextSome understanding. In shortBuildContextIt’s buildingWidgetIs the application context inFlutterAn important part of.BuildContextIt only appears in two places:

  • StatelessWidget.buildMethod: createStatelessWidgetOfbuildmethod
  • StateObjects: CreatingStatefulWidgetOfStateObject’sbuildThe other isStateMember variables of

ofBuildContextFurther discussion is not within the scope of this articleshowDialogTo achieve the pop-up window operation, the problem we have to consider is how to get it anywhere conveniently and quicklyBuildContextTo achieve the pop-up window. If some students happen to use itshowDialogIn this way, I’m sure you’ll find that you can get it anywhereBuildContextIt’s not that simple, and it generates a lot of unnecessary code.

So, can we only use this extremely unfriendly experience?

Of course not. Please keep reading.

Introduction to fluent easyloading

Flutter EasyLoadingIt’s easy to useFlutterPlug in, containing23speciesloadingAnimation effect, progress bar displayToastExhibition. pureFlutterEnd implementation, good compatibility, supportiOSAndroid。 Let’s take a brief look at how to use itFlutter EasyLoading

install

Add the following code to thepubspec.yamlDocument:

dependencies:
  flutter_ Easyloading: ^ 1.1.0 // please use the latest version

Import

import 'package:flutter_easyloading/flutter_easyloading.dart';

How to use it

First, use theFlutterEasyLoadingComponents wrap your app components:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ///Sub components are usually [materialapplication] or [cupertinoapp]
    ///This is done to ensure that the loading component can overlay other components
    return FlutterEasyLoading(
      child: MaterialApp(
        title: 'Flutter EasyLoading',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Flutter EasyLoading'),
      ),
    );
  }
}

Then, use it

EasyLoading.show(status: 'loading...'); 

EasyLoading.showProgress(0.3, status: 'downloading...');

EasyLoading.showSuccess('Great Success!');

EasyLoading.showError('Failed with Error');

EasyLoading.showInfo('Useful Information.');

EasyLoading.dismiss();

custom style

First of all, let’s take a lookFlutter EasyLoadingCurrently supported custom properties:

///Loading style, default[ EasyLoadingStyle.dark ].
EasyLoadingStyle loadingStyle;

///Indicator type of loading, default [easy] LoadingIndicatorType.fadingCircle ].
EasyLoadingIndicatorType indicatorType;

///Mask type of loading, default[ EasyLoadingMaskType.none ].
EasyLoadingMaskType maskType;

///Text alignment, default[ TextAlign.center ].
TextAlign textAlign;

///The inner margin of the loading content area
EdgeInsets contentPadding;

///The inner margin of the text
EdgeInsets textPadding;

///The size of the indicator. The default value is 40.0
double indicatorSize;

///The fillet size of loading. The default value is 5.0
double radius;

///Text size, default 15.0
double fontSize;

///Width of progress bar indicator, default 2.0
double progressWidth;

///The display time of [showsuccess] [showerror] [showinfo] is 2000ms by default
Duration displayDuration;

///Color of text, only for[ EasyLoadingStyle.custom ]Effective
Color textColor;

///Indicator color, only for[ EasyLoadingStyle.custom ]Effective
Color indicatorColor;

///The color of the progress bar indicator, only for[ EasyLoadingStyle.custom ]Effective
Color progressColor;

///Background color of loading, only for[ EasyLoadingStyle.custom ]Effective
Color backgroundColor;

///The background color of the mask, only for[ EasyLoadingMaskType.custom ]Effective
Color maskColor;

///When loading is displayed, whether the user is allowed to operate
bool userInteractions;

///Custom component showing success status
Widget successWidget;

///Custom component showing failure status
Widget errorWidget;

///A custom component that shows the state of information
Widget infoWidget;

becauseEasyLoadingIt is a global singleton, so we can customize its style anywhere:

EasyLoading.instance
  ..displayDuration = const Duration(milliseconds: 2000)
  ..indicatorType = EasyLoadingIndicatorType.fadingCircle
  ..loadingStyle = EasyLoadingStyle.dark
  ..indicatorSize = 45.0
  ..radius = 10.0
  ..backgroundColor = Colors.green
  ..indicatorColor = Colors.yellow
  ..textColor = Colors.yellow
  ..maskColor = Colors.blue.withOpacity(0.5);

For more types of indicator animation, see fluent_ spinkit showcase

As you can see,Flutter EasyLoadingIntegration and use of quite simple, and there are rich custom styles, there will always be you satisfied.

Next, let’s take a lookFlutter EasyLoadingCode implementation.

Implementation of fluent easyloading

This article will introduce through the following two knowledge pointsFlutter EasyLoadingThe main implementation process and ideas are as follows

  • OverlayOverlayEntryRealize global pop-up window
  • CustomPaintAndCanvasDrawing round progress bar

Overlay and overlay entry realize global pop-up window

Let’s take a look at the officialOverlayDescription of:

/// A [Stack] of entries that can be managed independently.
///
/// Overlays let independent child widgets "float" visual elements on top of
/// other widgets by inserting them into the overlay's [Stack]. The overlay lets
/// each of these widgets manage their participation in the overlay using
/// [OverlayEntry] objects.
///
/// Although you can create an [Overlay] directly, it's most common to use the
/// overlay created by the [Navigator] in a [WidgetsApp] or a [MaterialApp]. The
/// navigator uses its overlay to manage the visual appearance of its routes.
///
/// See also:
///
///  * [OverlayEntry].
///  * [OverlayState].
///  * [WidgetsApp].
///  * [MaterialApp].
class Overlay extends StatefulWidget {}

in other words,OverlayIt’s aStackOfWidget, you canOverlayEntryInsert intoOverlayMake independentchildWindow hovers over otherWidgetabove. With this feature, we can useOverlaytakeMaterialApporCupertinoAppWrap it up. The purpose of this is to ensure thatloadingComponents can be overlaid on other components because theFlutterThere will only be oneMaterialApporCupertinoAppRoot node component. (Note: the practice here is referred to fluent_ Oktoast plug-in, thank you).

In addition, the purpose of doing so can also solve another core problem: willcontextCache to memory, all subsequent calls do not need to providecontext。 The implementation is as follows:

@override
Widget build(BuildContext context) {
  return Directionality(
    child: Overlay(
      initialEntries: [
        OverlayEntry(
          builder: (BuildContext _context) {
            //Cache context
            EasyLoading.instance.context = _context;
            //The child here must be either materialapplication or cupertinoapp
            return widget.child;
          },
        ),
      ],
    ),
    textDirection: widget.textDirection,
  );
}
//Create overlay entry
OverlayEntry _overlayEntry = OverlayEntry(
  builder: (BuildContext context) => LoadingContainer(
    key: _key,
    status: status,
    indicator: w,
    animation: _animation,
  ),
);

//Insert overlay entry into overlay
//Through Overlay.of () we can get the overlay of the app root node
Overlay.of(_getInstance().context).insert(_overlayEntry);

//Call the remove() method of the overlay entry itself to remove itself from the overlay
_overlayEntry.remove();

OverlayOverlayEntryWe can also use them in more scenarios, for example, similarPopupWindowPop up window effect, global customizationDialogPop up windows and so on. As long as we use it flexibly, we can achieve many effects we want.

CustomPaintAndCanvasDrawing round progress bar

Almost all of themUIThe system will provide a self drawingUIThis interface usually provides a2DcanvasCanvasCanvasThe interior encapsulates some basic drawingAPIWe can go throughCanvasDraw various custom graphics. stayFlutterProvides aCustomPaintComponent, which can be combined with a brushCustomPainterTo draw custom graphics. Next, I will briefly introduce the implementation of the circular progress bar.

Let’s have a look firstCustomPaintConstructor:

const CustomPaint({
  Key key,
  this.painter,
  this.foregroundPainter,
  this.size = Size.zero,
  this.isComplex = false,
  this.willChange = false,
  Widget child,
})
  • Painter: background brush, which will be displayed after the child node;
  • Foregroundpainter: foreground brush, which is displayed in front of the child node
  • Size: whenchildbynullRepresents the default drawing area size, if anychildThis parameter is ignored and the canvas size ischildSize. If sochildHowever, if you want to specify a canvas of a specific size, you can use theSizeBoxpackageCustomPaintrealization.
  • Iscomplex: whether the drawing is complex, if so,FlutterSome caching strategies are applied to reduce the overhead of repeated rendering.
  • Will change: andisComplexWhen caching is enabled, this attribute represents whether the drawing changes in the next frame.

As you can see, we need to provide a foreground or background brush for drawing, and both can be provided at the same time. Our brushes need to be inheritedCustomPainterClass, we implement real rendering logic in the brush class.

Next, let’s take a look at how to get throughCustomPainterDraw a circular progress bar:

class _CirclePainter extends CustomPainter {
  final Color color;
  final double value;
  final double width;

  _CirclePainter({
    @required this.color,
    @required this.value,
    @required this.width,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..strokeWidth = width
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;
    canvas.drawArc(
      Offset.zero & size,
      -math.pi / 2,
      math.pi * 2 * value,
      false,
      paint,
    );
  }

  @override
  bool shouldRepaint(_CirclePainter oldDelegate) => value != oldDelegate.value;
}

We can see from above,CustomPainterA virtual function is defined inpaint:

void paint(Canvas canvas, Size size);

This function is the core of drawing. It contains the following two parameters:

  • Canvas: canvas, including various drawing methods, such asDrawLineDraw rectDraw circleetc.
  • Size: current drawing area size

Now that we have the canvas, we need a brush.FlutterProvidedPaintClass to implement the brush. You can also configure various attributes of the brush, such as thickness, color, style, etc

final paint = Paint()
  ... color = color // color
  . strokewidth = width // width
  ..style = PaintingStyle.stroke
  ..strokeCap = StrokeCap.round;

Finally, we just need to use itdrawArcMethods the arc was drawn

canvas.drawArc(
  Offset.zero & size,
  -math.pi / 2,
  math.pi * 2 * value,
  false,
  paint,
);

Here we are. In addition, we also need to pay attention to the drawing performance. Fortunately, an override is provided in the classshouldRepaintThis method determines when the canvas will be redrawn, which is quite effective in improving the rendering performance in complex rendering.

@override
bool shouldRepaint(_CirclePainter oldDelegate) => value != oldDelegate.value;

epilogue

without doubt,FlutterMaybe there are still many problems, but I believe more people will be willing to accompanyFlutterGrow together. in anticipation ofFlutterThe improvement of the ecosystem. Later I will gradually improveFlutter EasyLoadingWe look forward to your valuable comments.

Finally, hopeFlutter EasyLoadingIt will help you.