** 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…