简体   繁体   中英

Flutter Custom Painter drawing is laggy

I am trying to code a drawing app, in which users can choose different pen color and draw colorful drawings . I have created a class PointsGroup which stores list of offsets and associated color. In GestureDetector's onPanUpdate, the PointsGroup is appended to list of PointsGroup and passed to SignaturePainter.

But the drawing is bit laggy, it is not drawn as soon as pen moves.

You can see the video https://free.hubcap.video/v/LtOqoEj9H0dY9F9xC_jSst9HT3tSOJlTi

    import 'package:flutter/material.dart';

List<Color> colorList = [
  Colors.indigo,
  Colors.blue,
  Colors.green,
  Colors.yellow,
  Colors.orange,
  Colors.red
];

void main() => runApp(MaterialApp(
      home: HomePage(),
      debugShowCheckedModeBanner: false,
    ));

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Offset> _points = <Offset>[];
  List<Offset> _setPoints = <Offset>[];
  List<PointsGroup> _ptsGroupList = <PointsGroup>[];
  int startIndex;
  int endIndex;

  @override
  void initState() {
    ColorChoser.penColor = Colors.black;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          GestureDetector(
            onPanStart: (details) {
              setState(() {
                _points.clear();
                startIndex = _ptsGroupList.length;
                ColorChoser.showColorSelector = false;
              });
            },
            onPanUpdate: (DragUpdateDetails details) {
              setState(() {
                RenderBox object = context.findRenderObject();
                Offset _localPosition =
                    object.globalToLocal(details.globalPosition);
                _points = new List.from(_points)..add(_localPosition);
                _setPoints = new List.from(_points);
                _ptsGroupList.add(new PointsGroup(
                    setPoints: _setPoints, setColor: ColorChoser.penColor));
              });
            },
            onPanEnd: (DragEndDetails details) {
              setState(() {
                _points.add(null);
                ColorChoser.showColorSelector = true;
                endIndex = _ptsGroupList.length;
                if (startIndex < endIndex) {
                  _ptsGroupList.replaceRange(
                      startIndex, endIndex - 1, [_ptsGroupList.removeLast()]);
                }
              });
            },
            child: CustomPaint(
              painter: SignaturePainter(grpPointsList: _ptsGroupList),
              size: Size.infinite,
            ),
          ),
          ColorChoser(),
        ],
      ),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.undo),
          onPressed: () {
            setState(() {
              if (_ptsGroupList.length > 0) {
                _ptsGroupList.removeLast();
              }
            });
          }),
    );
  }
}

class ColorChoser extends StatefulWidget {
  const ColorChoser({
    Key key,
  }) : super(key: key);

  static Color backgroundColor = Colors.white;
  static Color penColor = Colors.blue;
  static bool showColorSelector = true;

  @override
  _ColorChoserState createState() => _ColorChoserState();
}

class _ColorChoserState extends State<ColorChoser> {
  @override
  Widget build(BuildContext context) {
    return Visibility(
      visible: ColorChoser.showColorSelector,
      child: Positioned(
        bottom: 0,
        left: 0,
        width: MediaQuery.of(context).size.width,
        child: Container(
          height: 60,
          child: ListView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: colorList.length,
              itemBuilder: (context, index) {
                return InkWell(
                  onTap: () {
                    setState(() {
                      ColorChoser.penColor = colorList[index];
                    });
                  },
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                        horizontal: 4.0, vertical: 5.0),
                    child: Container(
                      color: colorList[index],
                      // height: 30,
                      width: 45,
                    ),
                  ),
                );
              }),
        ),
      ),
    );
  }
}

class SignaturePainter extends CustomPainter {
  List<Offset> points;
  List<PointsGroup> grpPointsList = <PointsGroup>[];
  var paintObj;

  SignaturePainter({
    this.grpPointsList = const [],
  });

  @override
  void paint(Canvas canvas, Size size) {
    for (PointsGroup pts in grpPointsList) {
      points = pts.setPoints;
      paintObj = Paint()
        ..color = pts.setColor
        ..strokeCap = StrokeCap.round
        ..strokeWidth = 5.0;

      for (int i = 0; i < points.length - 1; i++) {
        if (points[i] != null && points[i + 1] != null) {
          canvas.drawLine(points[i], points[i + 1], paintObj);
        }
      }
    }
  }

  @override
  bool shouldRepaint(SignaturePainter oldDelegate) =>
      oldDelegate.points != points;
}

class PointsGroup {
  List<Offset> setPoints = <Offset>[];
  Color setColor;
  PointsGroup({this.setPoints, this.setColor});
}

Also the drawing is not shown for the very first draw. As soon as pen is lifted it starts showing.

PS If there is any alternate way is to achieve the desired multi-colored drawing, it will be okay.

You are clearing all the points whenever onPanStart is triggered (when the user places their finger on the screen). If you remove _points.clear() from onPanStart: (details) {} you will retain all the points that the user draws.

The application begins to lag and framerate is impacted after many points are drawn. You'll notice this when the user has drawn a decent amount on the canvas. To prevent lag from occurring, one strategy is to reduce the number of points being drawn. You can halve the number of points and still give the user the autonomy to draw what they desire by doing this:

final int THRESHOLD = 2;
if (totalPoints % THRESHOLD == 0){
 _points = new List.from(_points)..add(_localPosition);
}

totalPoints is a counter you increment by one in onPanUpdate: (details) {}

Another technique is to wrap the subclass widget that extends CustomPainter, in this case CustomPaint, with a RepaintBoundary widget https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html . This widget will ensure that only the regions of the canvas where painting occurs is redrawn when needed. By limiting refresh rendering to one widget, you will speed up the process and deliver better results.

RepaintBoundary(
  child: CustomPaint(
    isComplex: true,
    willChange: false,
    painter: Painter(
      points: _points,
    ),
  ),
),

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM