0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flutterで手書き署名機能(ペイント)を作ってみた

Posted at

#はじめに
こちらの記事はユアマイスターアドベントカレンダー2021、8日目の記事です
(投稿が遅れ、申し訳ありません!!)。

業務で手書き署名機能を実装する可能性がでたので、実際にどのように作るのか試してみました。
手書き署名で調べてましたが、実際にはペイント機能を作ればよさそうでした。

#参考
下記を参考にさせてもらいました。
https://www.egao-inc.co.jp/programming/flutter-paint/

#実装

後に説明するSignerとクリア用ボタンを設置しておきます。

import 'package:flutter/material.dart';
import 'package:test/signer.dart';

class SignPage extends StatefulWidget {
  const SignPage({Key? key}) : super(key: key);

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


class _SignPageState extends State<SignPage> {
  final SignController _controller = SignController();

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: const Text('署名'),
        centerTitle: true,
      ),
      body: Signer(
        paintController: _controller,
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            heroTag: "clear",
            onPressed: () => _controller.clear(),
            child: const Text('クリア'),
          ),
        ],
      ),
    );
  }
}

GestureDetectorでタッチイベントを検出します。
https://api.flutter.dev/flutter/widgets/GestureDetector-class.html

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:test/sign_history.dart';

class Signer extends StatefulWidget {
  final SignController paintController;

  Signer({required this.paintController})
      : super(key: ValueKey<SignController>(paintController));

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

class _SignerState extends State<Signer> {
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      child: GestureDetector(
        child: CustomPaint(
          willChange: true,
          painter: _CustomPainter(
            widget.paintController._paintHistory,
            repaint: widget.paintController,
          ),
        ),
        onPanStart: _onPaintStart,
        onPanUpdate: _onPaintUpdate,
        onPanEnd: _onPaintEnd,
      ),
      width: double.infinity,
      height: double.infinity,
    );
  }

  void _onPaintStart(DragStartDetails start) {
    widget.paintController._paintHistory
        .addPaint(_getGlobalToLocalPosition(start.globalPosition));
    widget.paintController._notifyListeners();
  }

  void _onPaintUpdate(DragUpdateDetails update) {
    widget.paintController._paintHistory
        .updatePaint(_getGlobalToLocalPosition(update.globalPosition));
    widget.paintController._notifyListeners();
  }

  void _onPaintEnd(DragEndDetails end) {
    widget.paintController._paintHistory.endPaint();
    widget.paintController._notifyListeners();
  }

  Offset _getGlobalToLocalPosition(Offset global) {
    return (context.findRenderObject() as RenderBox).globalToLocal(global);
  }
}

class _CustomPainter extends CustomPainter {
  final SignHistory _paintHistory;

  _CustomPainter(this._paintHistory, {required Listenable repaint})
      : super(repaint: repaint);

  @override
  void paint(Canvas canvas, Size size) {
    _paintHistory.draw(canvas, size);
  }

  @override
  bool shouldRepaint(_CustomPainter oldDelegate) => true;
}

class SignController extends ChangeNotifier {
  final SignHistory _paintHistory = SignHistory();

  final Color _drawColor = Colors.black;

  final double _thickness = 2.0;

  final Color _backgroundColor = Colors.white;

  SignController() : super() {
    Paint paint = Paint();
    paint.color = _drawColor;
    paint.style = PaintingStyle.stroke;
    paint.strokeWidth = _thickness;
    _paintHistory.currentPaint = paint;
    _paintHistory.backgroundColor = _backgroundColor;
  }

  void _notifyListeners() {
    notifyListeners();
  }

  void clear() {
    _paintHistory.clear();
    notifyListeners();
  }
}

描画の履歴を管理するwidgetです。

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class _PaintData {
  _PaintData({
    required this.path,
  }) : super();

  Path path;
}

class SignHistory {
  final List<MapEntry<_PaintData, Paint>> _paintList =
      <MapEntry<_PaintData, Paint>>[];

  final Paint _backgroundPaint = Paint();

  bool _inDrag = false;

  late Paint currentPaint;

  set backgroundColor(color) => _backgroundPaint.color = color;

  void clear() {

    if (!_inDrag) {
      _paintList.clear();
    }
  }

  void addPaint(Offset startPoint) {
    if (!_inDrag) {
      _inDrag = true;
      Path path = Path();
      path.moveTo(startPoint.dx, startPoint.dy);
      _PaintData data = _PaintData(path: path);
      _paintList.add(MapEntry<_PaintData, Paint>(data, currentPaint));
    }
  }

  void updatePaint(Offset nextPoint) {
    if (_inDrag) {
      _PaintData data = _paintList.last.key;
      Path path = data.path;
      path.lineTo(nextPoint.dx, nextPoint.dy);
    }
  }

  void endPaint() {
    _inDrag = false;
  }

  void draw(Canvas canvas, Size size) {
    canvas.drawRect(
      Rect.fromLTWH(
        0.0,
        0.0,
        size.width,
        size.height,
      ),
      _backgroundPaint,
    );

    for (MapEntry<_PaintData, Paint> data in _paintList) {
      canvas.drawPath(data.key.path, data.value);
    }
  }
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?