Flutter 签名板 + 保存图片

  • 不说多余的直接上代码
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutterappdz/resource.dart' as res;
import 'package:path_provider/path_provider.dart';
class SignPage extends StatefulWidget {
  const SignPage({Key? key}) : super(key: key);
  @override
  _SignPageState createState() => _SignPageState();
}

class _SignPageState extends State<SignPage> {


  GlobalKey key = GlobalKey();
  final GlobalKey _containerkey = GlobalKey();
  PointerEvent? _event;
  late List<Offset?> points = <Offset>[];
  Color selectColor = Colors.red;
  Uint8List? _postBytes;

  File? fileN;

  double y = 0;
  late double bottom;

  @override
  void initState() {
    super.initState();

    WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
      RenderBox box = _containerkey.currentContext!.findRenderObject() as RenderBox;
      Offset offset = box.localToGlobal(Offset.zero);
      y = offset.dy;
    });

  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('签名'),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children:  [
          const Padding(
            padding: EdgeInsets.symmetric(vertical: 20,horizontal: 16),
            child: Text('请在下方区域手写签名',style: TextStyle(fontSize: 16,fontWeight: FontWeight.w400),),
          ),
          Stack(
            children: [
              Container(
                width: double.infinity,
                color: Colors.white,
                child: Image.asset('assets/sign_image.png',fit: BoxFit.fill,),
              ),
              Positioned(

                left: 0,
                top: 0,
                bottom: 0,
                right: 0,
                child: RepaintBoundary(
                  key: key,
                  child: Stack(
                    children: [
                      Listener(
                        child: Container(
                          key: _containerkey,
                          alignment: Alignment.center,
                          color: Colors.transparent,
//                          width: double.infinity,
//                          height: double.infinity,
//                           child: Text(_event?.toString() ?? "",
//                               style: TextStyle(color: Colors.black)),
                        ),

                        onPointerDown: (PointerDownEvent event) {
                          setState(() {
                            _event = event;
                            points.add(_event?.localPosition ?? Offset.zero);
                          });
                        },
                        onPointerMove: (PointerMoveEvent event) {
                          setState(() {
                            _event = event;
                            if (_event != null){

                              RenderBox box = _containerkey.currentContext!.findRenderObject() as RenderBox;
                              Offset offset = box.localToGlobal(Offset.zero);
                              var dy = offset.dy;
                              var dbottom = box.size.height;


                              if (_event!.localPosition.dy < dbottom && _event!.localPosition.dy > 0){
                                points.add(_event?.localPosition ?? Offset.zero);
                              }
                            }

                          });
                        },
                        onPointerUp: (PointerUpEvent event) {
                          setState(() {
                            _event = event;
                            points.add(Offset.zero);
                          });
                        },
                      ),
                      Positioned(
                        left: (_event != null ? _event?.position.dx ?? 0 : 0),
                        top:  (_event != null ? (_event?.position.dy ?? 0) -
                          y : 0),
                        child: Container(
                          width: 1,
                          height: 1,
                          color: Colors.black,
                        ),),

                      CustomPaint(painter: SignaturePainter(points))


                    ],
                  ),



                )
              )
            ],
          ),

          Padding(
            padding: const EdgeInsets.only(top: 20),
            child: Row(
              children: [
                const SizedBox(
                  width: 24,
                ),
                Expanded(
                  child: OutlinedButton(
                    style: ButtonStyle(
                      padding: MaterialStateProperty.all(const EdgeInsets.symmetric(vertical: 10)),
                      side: MaterialStateProperty.all(const BorderSide(color: res.Colors.majorColor)),
                      shape: MaterialStateProperty.all(
                        RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(8),
                        ),
                      ),
                    ),
                    onPressed: () {

                      setState(() {
                        points = [];
                      });
                    },
                    child: const Text('重置'),
                  ),
                ),

                const SizedBox(
                  width: 10,
                ),
                Expanded(
                  child: ElevatedButton(
                    style: ButtonStyle(
                      padding: MaterialStateProperty.all(const EdgeInsets.symmetric(vertical: 10)),
                      side: MaterialStateProperty.all(const BorderSide(color: res.Colors.majorColor)),
                      shape: MaterialStateProperty.all(
                        RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(8),
                        ),
                      ),
                    ),
                    onPressed: () {
                      _saveSignPic();
                    },
                    child: const Text('确定'),
                  ),
                ),
                const SizedBox(
                  width: 24,
                ),
              ],
            ),
          ),
          if(fileN != null)

            Container(
              color: Colors.red,
              constraints: const BoxConstraints(
                maxHeight: 100,
                maxWidth: 100,
              ),

              child:  Image(image: FileImageEx(fileN!),),
            ),

          
        ],
      ),
    );
  }

  // 保存签名
  void _saveSignPic() async{
    
    RenderRepaintBoundary boundary = key.currentContext!.findRenderObject() as RenderRepaintBoundary;
    
    var image = await boundary.toImage(pixelRatio: 1);
    ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);


    Directory dir = await getTemporaryDirectory();

    String path = dir.path +"/"+ 'sign.png';

    var file = await File(path).create(recursive: true);

    if(byteData != null){
      file.writeAsBytesSync(byteData.buffer.asInt8List(),flush: true);

      setState(() {
        _postBytes = byteData.buffer.asUint8List();
        fileN = file;
      });

    }
  }

}


class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);

  final List<Offset?> points;
  final Color paintColor = Colors.black;

  Paint myPaint = new Paint();

  void paint(Canvas canvas, Size size) {
    myPaint.strokeCap = StrokeCap.round;
    myPaint.strokeWidth = 5.0;
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null &&
        points[i + 1] != null &&
        points[i] != Offset.zero &&
        points[i + 1] != Offset.zero) {
        canvas.drawLine(points[i]!, points[i + 1]!, myPaint);
      }
    }
  }

  bool shouldRepaint(SignaturePainter other) => other.points != points;
}

// ignore: must_be_immutable
class FileImageEx extends FileImage {
  int? fileSize;
  FileImageEx(File file, { double scale = 1.0 })
    : assert(file != null),
      assert(scale != null),
      super(file, scale: scale) {
    fileSize = file.lengthSync();
  }

  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }
    final FileImageEx typedOther = other;
    return file.path == typedOther.file.path
      && scale == typedOther.scale && fileSize == typedOther.fileSize;
  }
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Flutter 是一种跨平台的移动应用开发框架,它提供了丰富的组件和功能,可以帮助开发者快速构建漂亮、流畅的用户界面。对于可滑动自动滚动折线图,在 Flutter 中可以使用 `ListView` 和 `AnimatedContainer` 组件来实现。 首先,我们可以使用 `ListView` 组件来创建一个可以滑动的容器。使用 `ListView.builder` 构建一个动态列表,将折线图中的数据作为列表项进行展示。在 `ListView` 内部添加一个 `ScrollController`,用来控制列表的滚动。 当需要自动滚动时,我们可以通过动画来实现。使用 `AnimatedContainer` 组件来包裹折线图,通过修改它的宽度来实现滚动效果。可以在需要的时候,通过调用 `setState` 方法,来更新 `AnimatedContainer` 的属性值,从而触发动画效果。 在滚动时,可以监听滚动的位置,根据当前滚动的位置来判断是否需要自动滚动。通过 `ScrollController` 的 `addListener` 方法监听滚动事件,计算滚动的位置,并进行相应的判断,如果需要自动滚动,就通过修改 `AnimatedContainer` 的属性值来触发动画。 同时,可以为 `AnimatedContainer` 设置合适的动画时长和曲线,来使滚动效果更加顺滑。 总结来说,要实现可滑动自动滚动折线图,可以使用 `ListView` 和 `AnimatedContainer` 组件。通过监听滚动事件,根据滚动的位置进行判断,并通过修改 `AnimatedContainer` 的属性值来触发动画效果,从而实现自动滚动的效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值