** With the rise of FLUTTER, more and more companies start to use flutter. Recently, an old colleague asked me how to use flutter to achieve a little game of draw and guess. Now I would like to share this with you

Implemented functions

  • Sketchpad free doodle
  • Select a brush color
  • Select the brush size
  • Undo to the previous step
  • The undo
  • Empty canvas
  • The eraser
  • Real-time sending to the server based on WebSocket
  • The WebSocket server forwards to other connections
  • Accept WebSocket message content rendering

The technology used

  • Basic components (Scaffold, AppBar, IconButton, Container, Column, Stack, Padding, Icon, etc.)
  • Custom CustomPainter, using Paint on Canvas
  • Gesture recognition using the GestureDetector event
  • The state management of Flutter is implemented based on the Provider plugin
  • Simple implementation of WebSocket communication (real projects will consider more issues such as heartbeat, reconnection, network fluctuation handling, etc.)

The final result

Real-time three-screen synchronization

Actual combat began

  • Open pubspec.yaml reference state management and the WebSocket library
Dev_dependencies: flutter_test: SDK: flutter provider: ^4.0.1 web_socket_channel: ^1.1.0Copy the code
  • Dart Creates the draw_entity.dart entity class
import 'package:flutter/widgets.dart'; // Pengzhenkun-2020.04.30 class DrawEntity {Offset Offset; String color; double strokeWidth; DrawEntity(this.offset, {this.color ="default", enclosing strokeWidth = 5.0}); }Copy the code
  • Create signature_Painter. Dart custom artboard
import 'package:flutter/material.dart';
import 'package:fluttercontrol/page/drawguess/draw_entity.dart';
import 'package:fluttercontrol/page/drawguess/draw_provider.dart'; // Custom Canvas Canvas (pengzhenkun-2020.04.30) class SignaturePainter extends CustomPainter {List<DrawEntity> pointsList; Paint pt; SignaturePainter(this.pointslist) {pt = Paint() // Sets the pen properties.. color = pintColor["default"].. strokeCap = StrokeCap.round .. isAntiAlias =true. StrokeWidth = 3.0.. style = PaintingStyle.stroke .. strokeJoin = StrokeJoin.bevel; } void paint(Canvas canvas, Size size) {for(int i = 0; i < pointsList.length - 1; I++) {// draw a lineif(pointsList[i] ! = null && pointsList[i + 1] ! = null) { pt .. color = pintColor[pointsList[i].color] .. strokeWidth = pointsList[i].strokeWidth; canvas.drawLine(pointsList[i].offset, pointsList[i + 1].offset, pt); Bool shouldRepaint(SignaturePainter Other) => other.pointslist! = pointsList; }Copy the code
  • Create draw_provider.dart status management
  • Record cancelled data, stored data to draw, preprocessed data, default colors, default font sizes, Socket connections (for easy understanding, Socket connections are also written in this class)
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:fluttercontrol/page/drawguess/draw_entity.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/io.dart'; // Optional palette Color Map<String, Color> pintColor = {'default': Color(0xFFB275F5),
  'black': Colors.black,
  'brown': Colors.brown,
  'gray': Colors.grey,
  'blueGrey': Colors.blueGrey,
  'blue': Colors.blue,
  'cyan': Colors.cyan,
  'deepPurple': Colors.deepPurple,
  'orange': Colors.orange,
  'green': Colors.green,
  'indigo': Colors.indigo,
  'pink': Colors.pink,
  'teal': Colors.teal,
  'red': Colors.red,
  'purple': Colors.purple,
  'blueAccent': Colors.blueAccent,
  'white': Colors.white, }; (pengzhenkun - 2020.04.30) class DrawProvider with ChangeNotifier {final String _URL ='the ws: / / 10.10.3.55:8080 / mini'; List<List<DrawEntity>> undoPoints = List<List<DrawEntity>>(); List<List<DrawEntity>> points = List<List<DrawEntity>>(); List<DrawEntity> pointsList = List<DrawEntity>(); // Preprocess the data to avoid processing stentons when drawing String pentColor ="default"; // Default color double pentSize = 5; // Default font size //Socket connection WebSocketChannel _channel; // Start the connectionconnect() {
    _socketConnect();
  }

  _socketConnect() { _channel = IOWebSocketChannel.connect(_URL); _channel.stream.listen((message) {// Listen to the messageprint("Received a message:$message");
        message = jsonDecode(message);
        if (message["type"] = ="sendDraw") {// drawing continuouslyif (points.length == 0) {
            points.add(List<DrawEntity>());
            points.add(List<DrawEntity>());
          }
          pentColor = message["pentColor"];
          pentSize = message["pentSize"]; Points [point.length-2]. Add (DrawEntity(Offset(message["dx"], message["dy"]), color: pentColor, strokeWidth: pentSize)); // Notification updatesetState();
        } else if (message["type"] = ="sendDrawNull"Add (List<DrawEntity>()); // Add (List<DrawEntity>()); // Notification updatesetState();
        } else if (message["type"] = ="clear") {// Clear artboard points.clear(); // Notification updatesetState();
        } else if (message["type"] = ="sendDrawUndo"UndoPoints. Add (points[points.length-3]); RemoveAt (points.length-3); // Remove data // notify updatessetState();
        } else if (message["type"] = ="reverseUndoDate"List<DrawEntity> ss = undoPoints. RemoveLast (); points.insert(points.length - 2, ss); // Notification updatesetState();
        }
      },
      onDone: () {
        print("Connection disconnected onDone"); // Try to reconnect _socketConnect(); }, onError: (err) {print("Connection error onError");
      },
      cancelOnError: true,); } // Clear the dataclear() {// Clear data points.clear(); // Notification updatesetState();
    _channel.sink
        .add(jsonEncode({'uuid': 'xxxx'.'type': 'clear'.'msg': 'clear'})); } // Draw data sendDraw(Offset)localPosition) {
    if(points.length == 0) { points.add(List<DrawEntity>()); points.add(List<DrawEntity>()); } // add DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity(DrawEntity)))localPosition, color: pentColor, strokeWidth: pentSize));
//    points.add(localPosition); // Notification updatesetState(); _channel.sink.add(jsonEncode({'uuid': 'xxxx'.'type': 'sendDraw'.'pentColor': pentColor,
      'pentSize': pentSize,
      "dx": localPosition.dx,
      "dy": localPosition.dy })); } // Draw a Null data partition identifiersendDrawNull() {// add(List<DrawEntity>()); // Notification updatesetState(); _channel.sink.add(jsonEncode({'uuid': 'xxxx'.'type': 'sendDrawNull'})); } // Undo a piece of dataundoDate() {undopoints.add (points[points.length-3]); RemoveAt (points.length-3); // Remove datasetState(); _channel.sink.add(jsonEncode({'uuid': 'xxxx'.'type': 'sendDrawUndo'})); } // Undo a piece of datareverseUndoDate() {
    List<DrawEntity> ss = undoPoints.removeLast();
    points.insert(points.length - 2, ss);

    setState(); _channel.sink.add(jsonEncode({'uuid': 'xxxx'.'type': 'reverseUndoDate'}));
  }

  @override
  void dispose() { _channel.sink? .close(); super.dispose(); }_update() {
    pointsList = List<DrawEntity>();
    for(int i = 0; i < points.length - 1; i++) { pointsList.addAll(points[i]); pointsList.add(null); }}setState() { _update(); notifyListeners(); }}Copy the code
  • With this implementation in place, create draw_page.dart to set up our home page
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:fluttercontrol/page/drawguess/draw_provider.dart';
import 'package:fluttercontrol/page/drawguess/widget/signature_painter.dart';
import 'package:provider/provider.dart'; Class DrawPage extends StatefulWidget {@override _DrawPageState createState() => _DrawPageState(); } class _DrawPageState extends State<DrawPage> { DrawProvider _provider = DrawProvider(); @override voidinitState() {
    super.initState();
    _provider.connect();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("WebSocket Draw"), actions: <Widget>[ IconButton( icon: Icon(Icons.call_missed_outgoing), onPressed: () {// undo _provider.undoDate();},), IconButton(icon: icon (icon.call_missed), onPressed: () {/ / the revocation _provider reverseUndoDate ();},),),), body: ChangeNotifierProvider. Value (the value: _provider, child: Consumer<DrawProvider>( builder: (context, drawProvider, _) {returnContainer( color: Color(0x18262B33), child: Column( children: <Widget>[ Expanded( child: Stack( children: [ Container( color: Colors. White,), Text (drawProvider. Points. Length. The toString ()), GestureDetector (/ / gestures detector, a special widget that you want to add a widge gestures, OnPanUpdate: (DragUpdateDetails details) {// Press RenderBox referenceBox = context.findRenderObject(); OffsetlocalPosition = referenceBox
                                  .globalToLocal(details.globalPosition);
                              drawProvider.sendDraw(localPosition); }, onPanEnd: (DragEndDetails details) { drawProvider.sendDrawNull(); }, // lift up), CustomPaint(Painter: SignaturePainter(drawprovider.pointslist),],),), Padding(Padding: EdgeInsets.only(left: 10, right: 80, bottom: 20), child: Wrap( spacing: 5, runSpacing: 5, crossAxisAlignment: WrapCrossAlignment.center, children: <Widget>[ buildInkWell(drawProvider, 5), buildInkWell(drawProvider, 8), buildInkWell(drawProvider, 10), buildInkWell(drawProvider, 15), buildInkWell(drawProvider, 17), buildInkWell(drawProvider, 20), ], ), ), Padding( padding: EdgeInsets.only(left: 10, right: 80, bottom: 20), child: Wrap( spacing: 5, runSpacing: 5, children: pintColor.keys.map((key) { Color value = pintColor[key];return InkWell(
                            onTap: () {
//                          setColor(context, key);
                              drawProvider.pentColor = key;
                              drawProvider.notifyListeners();
                            },
                            child: Container(
                              width: 32,
                              height: 32,
                              color: value,
                              child: drawProvider.pentColor == key
                                  ? Icon(
                                      Icons.done,
                                      color: Colors.white,
                                    )
                                  : null,
                            ),
                          );
                        }).toList(),
                      ),
                    )
                  ],
                ),
              );
            },
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _provider.clear,
          tooltip: ' ',
          child: Icon(Icons.clear),
        ));
  }

  InkWell buildInkWell(DrawProvider drawProvider, double size) {
    returnInkWell( onTap: () { drawProvider.pentSize = size; drawProvider.notifyListeners(); }, child: Container( width: 40, height: 40, child: Center( child: Container( decoration: new BoxDecoration( color: PintColor [drawProvider. PentColor], // Set the Angle to the borderRadius: Borderradius.all (radius.circular (size / 2)), // Set border: drawProvider. PentSize == size? Border.all(width: 1, color: Colors.black) : null, ), width: size, height: size, ), ), ), ); } @override voiddispose() { _provider.dispose(); super.dispose(); }}Copy the code
  • The WebSocket server is written using Dart and does not have much logic, just forwarding data.
  • The core code is as follows
Void handMsg(dynamic MSG, SCT) {print(${MSG} ${MSG} + webSockets.length.toString());
    
    msg = jsonDecode(msg);
    if (msg["type"] = ="sendDraw"| | / / are continuous drawing MSG ["type"] = ="clear"| | / / empty sketchpad MSG ["type"] = ="sendDrawNull"| | / / holding your hands, add placeholder MSG ["type"] = ="sendDrawUndo"| | / / cancellation, the cache to cancel the container MSG ["type"] = ="reverseUndoDate")// Undo data // reply to all other clients what did the current client sendfor (WebSocket webSocket inWebSockets) {// Determine if there is a close code, and reply if there is no proof that the client is not currently closedif(webSocket.closeCode == null && webSocket ! Websocket. add(jsonEncode(MSG)); }}}Copy the code

You’re done

Attached to the source code:

  • Flutter:gitee.com/pengzhenkun…
  • DartServer:gitee.com/pengzhenkun…