I. Start of the project

1. When building a Flutter project, first make clear whether Java or Kotlin, objectC or Swift is the language used by the native terminal; otherwise, it will be troublesome to change the language later if the wrong choice is made

2. Choose their own routing management and state management packages, and decide the project architecture. As far as I am concerned, fluro and Provider were used in the first project A routing management a state management, project directory new Store and route folder, respectively store provider model file and Fluro configuration file, to the second project, found Getx, a collection of dependency injection, routing management, state management package, use! The project directory structure has changed a lot, and the overall organization is clean

The first one

The second

3. Common package configuration, such as Getx needs to change the outer MaterialApp into GetMaterialApp, Flutter_ScreenUtil needs to initialize the design diagram scale,provider global import,Dio encapsulation, interceptor, network prompt, etc

2. Global configuration

1. Because some of the small widgets of Flutter are reusable, and the App needs a uniform style, default files such as style colors are placed in the Command folder

Colours. Dart, you can preset a static class to store common theme colours

Styles. dart, you can preset font styles, line styles, various fixed value intervals

2. It is recommended to manage back-end interfaces globally, which is neat, easy to maintain, and comfortable

3. The Models folder may not be commonly used on the Web side, but I think it is necessary in Dart. The Json string returned from the back end must be formatted as a class through the Model class, which can greatly reduce spelling and type errors. QuickType input JSON object, one click output model class!

4. Determine whether to force vertical screen? Dart needs to be configured

// Force landscape
  SystemChrome.setPreferredOrientations(
      [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
Copy the code

5. Check whether the layout and style of the status bar on the top and bottom need to be modified. With SystemUiOverlayStyle and SystemChrome. SetSystemUIOverlayStyle (SystemUiOverlayStyle); To configure the

6. Set the font not to follow the system

Refer to the address

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Container(color: Colors.white),
      builder: (context, widget) {
        return MediaQuery(
          // Set the text size to be consistent with system Settings
          data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), child: widget, ); }); }}Copy the code

7. Internationalization configuration using some widgets will display English, such as IOS style dialog, and Display Chinese.

  flutter_localizations:
    sdk: flutter
Copy the code

Import the package and add it to the configuration item in main.dart MetrialApp

   // Set localization, part of native content to display Chinese, for example, long press selected text to display copy and cut
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate, / / the internationalization
        GlobalWidgetsLocalizations.delegate,/ / the internationalization
        const FallbackCupertinoLocalisationsDelegate() // This is to resolve an error]./ / the internationalization
      supportedLocales: [
        const Locale('zh'.'CH'),
        const Locale('en'.'US')],Copy the code

The getter ‘alertDialogLabel’ was called on null Dart added the following class and then instantiated in localized localization of the MetrialApp delegates see article 7

class FallbackCupertinoLocalisationsDelegate
    extends LocalizationsDelegate<CupertinoLocalizations> {
  const FallbackCupertinoLocalisationsDelegate();

  @override
  bool isSupported(Locale locale) => true;

  @override
  Future<CupertinoLocalizations> load(Locale locale) =>
      DefaultCupertinoLocalizations.load(locale);

  @override
  bool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}
Copy the code

9. The recent update to flutter limits the maximum number of catchedimages to 100, 1000mb, while business needs to cache more. This is required

    class ChangeCatchImage extends WidgetsFlutterBinding {
  @overridecreateImageCache() *{* Global.myImageCatche = ImageCache() .. maximumSize =1000
      ..maximumSizeBytes = 1000 << 20; // 1000MB
    returnGlobal.myImageCatche; }}Copy the code

Then instantiate ChangeCatchImage() in main.dart runApp

Business module

Common business module code analysis, such as login page, splash screen page, home page, log out and so onCopy the code

1. Get Getx first

A folder is a business module that manages data by itself and shares data through dependency injection, which is very comfortable

Including logic logic control layer state data management layer View view component layer, current service reuse widgets are written in folders

2. Login module

As the entrance portal of app, it is indispensable to be cool and beautiful, which requires attention to performance optimization, and the input place and verification logic should be designed safely

  • First of all, regarding animation performance optimization, the most critical point is to accurately update the components that need to be changed. We can check the update scope through devtool

  • Secondly, security design, in a simple way, limit login times, prohibit simple passwords, encrypt transmission, verify token, etc. Advanced version, such as preventing parameter injection, filtering sensitive characters, etc
  • Before login account verification, password verification, mandatory items, and so on, and then login request, need to add loading, button disable, do not need to shake
  • After login, save the basic information of the local user (there may be security problems, but no further investigation), and then check the existence of the basic information by default next login, verify the expiration time and token, and then log in to the home page implicitly

3. Splash Splash module

On the preparation page of the app landing home page, you can embed advertisements or customize the software promotion animation, which prompts you to skip after three seconds. How to gracefully add the app flash screen page? Dart sets the initial page to splash, and then determines whether to go to the home page or the login and registration page by jumping logic. For example, I used Getx here

4. Operate the boot module

The first time you use an app, or after a major update, there are usually two types of action guides that I used in my project

The first result diagram

The second,

Both are based on overlayEntry() and overlay.of (context).insert(overlayEntry). The second implementation guides flutter_intro with a package operation: ^2.2.1 Bind the Widget’s GlobalKey to get the Element information, get the location size, and make sure the box is positioned correctly. The outer mask is created with overlayEntry() as in the first

And then once it’s created, it shows overlay.of (context).insert(your_overlayEntry) and switches to the next one at some button like I got it, next page or something

     onPressed: () {
                  // Execute the remove method to destroy the first overlayEntry instance
                  overlayEntryTap.remove();
                  / / the second
                  Overlay.of(context).insert(overlayEntryScale);
                },
Copy the code

For the flutter_intro package involved in the second implementation, please paste my code and refer to pub for details

final intro = Intro(
  // There are a few steps to create 2 GlobalKeys for later use
  stepCount: 2.// Click next to the mask
  maskClosable: true.// Highlight the inner margin between the area and the widget
  padding: EdgeInsets.all(0),
  // The rounded radius of the highlighted area
  borderRadius: BorderRadius.all(Radius.circular(4)),
  // use defaultTheme
  widgetBuilder: StepWidgetBuilder.useDefaultTheme(
    texts: ["Click Add Favorites"."Drop down to bookmark"],
    buttonTextBuilder: (currPage, totalPage) {
      return currPage < totalPage - 1
          ? 'I see${currPage + 3}/${totalPage + 2}'
          : 'complete${currPage + 3}/${totalPage + 2}'; },),); .// The key is used here to bind any Widget
  Positioned(
              key: intro.keys[1],
              top: 0,
              right: 20,...). .Copy the code

5.CustomPaint Drawing board module

Results figure

Flutter was chosen because of the large number of drawing requirements. We chose Flutter because of its own SKia, which is efficient, smooth and platform-consistent with many pitfalls

Let’s start with Pigfoot CustomPaint

As the name suggests, this is a personalization drawing component, and its job is to create a canvas for you, and you can paint it any way you want, so let’s see how it works first, formatting

  • You need to write it in the Widget tree first
    Container( 
    child: CustomPaint(
    painter: myPainter(),
    ),
    Copy the code

    If you look at the parameter list, you can see that Painter receives a CustomePainter object, and you can pay attention to the child parameter. In this case, the sun widget can be added to the child widget to optimize performance. If the sun widget is added to the child widget, the sun widget can be added to the child widget to optimize performance by repainting a moving cloud with a stationary sun

  • So let’s create myPainter()
    class myPainter extends CustomPainter { 
    @override 
    void paint(Canvas canvas, Size size) { 
    // Create a brush
    final Paint paint = Paint(); 
    // Draw a circle
    canvas.drawCircle(Offset(50.50), 5, paint); 
    } 
    @override 
    bool shouldRepaint(CustomPainter oldDelegate) => false;
    }
Copy the code
  • Here we need to implement two important functions (code above)

The first paint() function comes with the canvas object, canvas size, so we can use canvas’s built-in drawing function! The draw function, on the other hand, receives a Paint brush object

This brush object is used to set the brush color, thickness, style, splice style and so on

Paint paint = Paint(); 
// Set the brushpaint .. style = PaintingStyle.stroke .. color = Colors.red .. strokeWidth =10;
      
Copy the code

The second function shouldRepaint(), as its name suggests, determines if it needs to be redrawn, if it returns false it doesn’t need to be redrawn, only paine() once, if it returns true it always needs to be redrawn, depending on what you need to do if you want to draw something like a bar graph that gets higher and higher in value

The code is as follows:

class BarChartPainter extends CustomPainter {
  final List<double> datas;
  final List<double> datasrc;
  final List<String> xAxis;
  final double max;
  final Animation<double> animation;

  BarChartPainter(
      {@required this.xAxis,
      @required this.datas,
      this.max,
      this.datasrc,
      this.animation})
      : super(repaint: animation);

  @override
  void paint(Canvas canvas, Size size) {
    _darwBars(canvas, size);
    _drawAxis(canvas, size);
  }

  @override
  bool shouldRepaint(BarChartPainter oldDelegate) => true;

  // Draw the axes
  void _drawAxis(Canvas canvas, Size size) {
    final double sw = size.width;
    final double sh = size.height;

    Use Paint to define the style of the path
    finalPaint paint = Paint() .. color = Colors.grey .. style = PaintingStyle.stroke .. strokeWidth =1
      ..strokeCap = StrokeCap.round;

    // Use Path to define the Path to draw from the top left to the bottom left of the canvas to the bottom right
    finalPath path = Path() .. moveTo(40, sh) .. lineTo(sw -20, sh);

    // Use the drawPath method to draw a path
    canvas.drawPath(path, paint);
  }

  // Draw the column
  void _darwBars(Canvas canvas, Size size) {
    final sh = size.height;
    finalpaint = Paint().. style = PaintingStyle.fill;final double _barWidth = size.width / 20;
    final double _barGap = size.width / 25 * 2 + 18;
    final double textFontSize = 14.0;

    for (int i = 0; i < datas.length; i++) {
      final double data = datas[i] * ((size.height - 15) / max);
      final top = sh - data;
      // The left edge of the rectangle is the current index multiplied by the width of the rectangle plus the spacing between the rectangles
      final double left = i * _barWidth + (i * _barGap) + _barGap;
      // Use the rect.fromltwh method to create the rectangle to draw
      final rect = RRect.fromLTRBAndCorners(
          left, top, left + _barWidth, top + data,
          topLeft: Radius.circular(5), topRight: Radius.circular(3));
      // Use the drawRect method to draw a rectangle

      final offset = Offset(
        left + _barWidth / 2 - textFontSize / 2 - 8,
        top - textFontSize - 5,); paint.color = Color(0xFF59C8FD);

      / / draw bar
      canvas.drawRRect(rect, paint);

      // Use TextPainter to draw the values on the rectangle
      TextPainter(
        text: TextSpan(
          text: datas[i] == 0.0 ? ' ' : datas[i].toStringAsFixed(0) + "%",
          style: TextStyle(
            fontSize: textFontSize,
            color: paint.color,
            // color: Colours.gray_33,), ), textAlign: TextAlign.center, textDirection: TextDirection.ltr, ) .. layout( minWidth:0, maxWidth: textFontSize * data.toString().length, ) .. paint(canvas, offset);final xData = xAxis[i];
      final xOffset = Offset(left, sh + 6);
      // Draw the horizontal axis identifier
      TextPainter(
        textAlign: TextAlign.center,
        text: TextSpan(
          text: '$xData'! =' '
              ? '$xData'.substring(0.4) + The '-' + '$xData'.substring(4.6)
              : ' ',
          style: TextStyle(
            fontSize: 12, color: Colors.black, ), ), textDirection: TextDirection.ltr, ) .. layout( minWidth:0, maxWidth: size.width, ) .. paint(canvas, xOffset); }}}Copy the code

Ok,customPainter, this is how we use it. Let’s go back to the topic. Drawing the artboard is actually quite complicated

Take the most classic pencil drawing actually simple pencil drawing, even with a brush, similar to signature, are very simple, online tutorial a pile

The general idea is to add a GestureDetector, which mainly uses onPanUpdate event to trigger drawing action in real time. Drawing with canvas is simple, but performance optimization is complex

Here give me directly testing the optimal solution First line of the new coordinate point to the previous points, which can be connected to a few more than once, which is similar to the processing technique of throttling, such as five panUpate triggered the callback, first connect the five points into line, the reunification of the sixth time to draw a line, if still have what good way, glad to hope!) A detailed project will be sorted out separately in the future

6. Websocket IM module

Results figure

Only the most basic text image file function

Simply the realization of each function to say, will be arranged in detail, and add audio and videoCopy the code
  • About the websocket

    The first thing you must do is connect to webSocket, using a package called web_socket_channel

    Then initialize the WebSocket

    // Initialize the webSocket
    initWebsocket(){
     Global.channel = IOWebSocketChannel.connect(
       WebsocketUrl, / / the websocket address
       Note that ping is sent every 10000 milliseconds. If pong is not received in 10000ms, the connection is disconnected by default
       // If this parameter is too small, such as 100ms, it will disconnect itself after a while
       pingInterval: Duration(milliseconds: 10000));// Listen for server messages
     Global.channel.stream.listen(
         (mes) => onMessage(mes),// Process the message
         onError: (error) => {onError(error)}, // Connection error
         onDone: () => {onDone()}, // Disconnect the connection
         cancelOnError: true // Unsubscribe if the Settings are incorrect
     );
    }
    Copy the code
  • Process the message

    Enter the page to load the chat messages, and use listView.build () for the long list, which is much better when there are many messages

    Each time a new message is listened for, it is added to the array and the view is updated.

    Adding messages is where it gets tricky

    First of all, there are four cases: a. Self-sent and at the bottom of the ListView, B. Posted by yourself but not at the bottom of the ListView, C. Posted by others and at the bottom, D. Someone else’s is not at the bottom.

    A and B, C: As long as it is their own hair to scroll to the bottom, at the bottom of the scroll slowly, a kind of message pull up the feeling

    // Make sure the new message has been added and rendered to the LIstView
    // Add a delay and then scroll
    // Scroll directly to the bottom of the ListView
    scrollController.jumpTo(scrollController.position.maxScrollExtent);
    // Scroll to a certain element
    Scrollable.ensureVisible(
         // Add GlobalKey to each message object to get the current context
         state.messageList[index].key.currentContext,
         duration: Duration(milliseconds: 100),
         curve: Curves.easeInOut,
         // Control the alignment
         alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd);
    Copy the code

    D: In this case, I made a prompt similar to wechat

When you click to locate the message, there is a pit, because with listview. build, when you scroll through the top message, the bottom message is not loaded, so you don’t get the currentContext, because the element is not rendered, and the location is corrupted Render, then clear as you slide down.

  • Documents and Pictures

    Several packages are used: File_picker, Open_file, path_provider

    File_picker, which is used to select files and images, can be configured with single or multiple options. You need to add permissions to your Android profile

    Open_file, similar to wechat click file, download first, then call local default program to open file

    Path_provider: provides the system available path for creating file directories

    (to be continued)