flutter 学习之 做一个简单的画板(CustomPainter)

本文介绍如何在Flutter中使用CustomPainter进行绘图,通过三个步骤:继承CustomPainter并重写paint和shouldRepaint方法,利用Canvas进行绘制,以及使用CustomPaint构建Widget。文中以创建一个简单的画板为例,讲解了画点和线的操作,并讨论了如何处理手指滑动时的线条绘制。最后提到虽然目前实现存在不流畅的问题,但提供了源码地址以供进一步研究和优化。

今天来学习一下flutter中的绘制,从如何使用Canvas draw/paint了解到,在Flutter中使用绘制方式自定义Widget,需要以下三个步骤:

  • 1.继承CustomPainter并重写paint方法和shouldRepaint方法

  • 2.在写paint方法中绘制内容

  • 3.使用CustomPaint来构建Widget

先通过写一个简单的画板来学习一下paint的使用

first:声明一个画纸的class,如上所说继承CustomPainter并重写paint方法和shouldRepaint方法

class Paper extends CustomPainter {
  Paper({
    @required this.lines,
    this.positions,
  }) {
    _paint = Paint()
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;
  }

  Paint _paint;
  final List<List<TolyCircle>> lines;
  final List<TolyCircle> positions;
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
    for (int i = 0; i < lines.length; i++) {
      drawLine(canvas, lines[i]);
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return true;
  }

  void drawLine(Canvas canvas, List<TolyCircle> positions) {
    //注意这里是positions.length - 1
    for (var i = 0; i < positions.length - 1; i++) {
      if (positions[i] != null && positions[i + 1] != null) {
        canvas.drawLine(positions[i].pos, positions[i + 1].pos,
            _paint..strokeWidth = positions[i].radius);
            _paint..color = positions[i].color ?? Colors.red;
      }
    }
  }
}

这里注意一下drawLine中for循环 i < positions.length - 1;而不是i < positions.length ;

second:那画纸上画的是什么呢?点?线?都可以,线就是一些点的 集合,所以我们在声明一个点的class,最重要的就是要有一个Offset属性(点的位置)

class TolyDrawable {
  Offset pos;
  Color color;
  TolyDrawable(this.pos,this.color);
}

class TolyCircle extends TolyDrawable {
  double radius;
  TolyCircle(Color color, Offset pos, {this.radius = 4.0}) : super(pos,color);
}

third:把我们的画纸放手机屏幕上,就开始划拉吧~

因为我们手指每滑动一下都要更新页面,所以组件为有状态组件,_lines为状态量,在移动时将点加入当前所画的线

class _AnimationPageState extends State<AnimationPage> {
  var _positions = <TolyCircle>[];
  var _lines = <List<TolyCircle>>[];
  Offset _oldPos;

  @override
  Widget build(BuildContext context) {
    //按下时表示新添加一条线,并记录上一点位置
    void _panDown(DragDownDetails details) {
      var x = details.globalPosition.dx;
      var y = details.globalPosition.dy;
      // _positions.add(TolyCircle(Colors.red, Offset(x, y), radius: 4.0));
      print('pan down ${_positions.toString()}');
      _lines.add(_positions);
      _oldPos = Offset(x, y);
    }

    ///移动中,将点添加到点集中
    void _panUpdate(DragUpdateDetails details) {
      var x = details.globalPosition.dx;
      var y = details.globalPosition.dy;
      var curPos = Offset(x, y);
      if ((curPos - _oldPos).distance > 3) {
        var tolyCirle = TolyCircle(Colors.blue, curPos, radius: 4.0);
        _positions.add(tolyCirle);
        _oldPos = curPos;
        setState(() {});
      }
    }

    /// 抬起后,将旧线拷贝到线集中
    void _panEnd(DragEndDetails details) {
      var oldLine = <TolyCircle>[];
      for (var i = 0; i < _positions.length; i++) {
        oldLine.add(_positions[i]);
      }
      _lines.add(oldLine);
      _positions.clear();
      print(_lines.toString());
    }

    return GestureDetector(
      child: Scaffold(
        body: CustomPaint(
          painter: Paper(lines: _lines),
        ),
      ),
      onPanDown: (DragDownDetails details) {
        _panDown(details);
      },
      onPanUpdate: _panUpdate,
      onPanEnd: _panEnd,
      onDoubleTap: () {
        _lines.clear();
        setState(() {});
      },
    );
  }
}

这里着重注意一下_panDown()手指按下的时候要开始画一条新的线,所以调用_lines.add(_positions);时 _positions其实是一个空数组,这样就保证了每次手指按下然后抬起画一条线,和下一次重复画线的动作,画的线是分开的 ,这里描述的可能不太容易懂,可以自己试试,如果_panDown的时候只是_positions.add(TolyCircle(Colors.red, Offset(x, y), radius: 4.0));是什么效果,每次画的线就变成了收尾相连的了。。。

下面是效果图:

测试感觉着还是不太流畅,不太顺滑,需要再研究一下怎么优化

文末加上源码地址:https://github.com/AnleSu/flutter_paintPaper  以后有空会继续更新paint相关的demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值