Android Flutter利用贝塞尔曲线画一个小海豚

目录
  • 前言
  • 效果图
  • 实现步骤
  • 总结

前言

贝塞尔曲线的应用填补了计算机绘制与手绘之前的差距,更能表达人想画出的曲线,为了更好的理解万能的贝塞尔曲线,而海豚是我认为在海洋生物中身体曲线最完美的海洋生物,在海洋中游泳速度最高可达80km/h;比驱逐舰速度还快,学习绘制正好学到了贝塞尔曲线,那么我们今天就用贝塞尔曲线画看看能不能画一只可爱的小海豚呢。

效果图

先上效果图:

实现步骤

path路径绘制贝塞尔曲线的方法非常简单,只需要传入控制点即可,二阶就传1个控制点1个终点,三阶就传2个控制点和1个终点,但是要找到合适控制的点就没那么容易了,这时候我们如果可以用手指在屏幕上不断调试寻找合适的点岂不是非常方便,接下来我们就先实现下面的功能,通过手指不断调试控制点位并将多个贝塞尔曲线进行连接。

可以看到一个三阶贝塞尔需要1个起点、2个控制点和1个终点组成,首先我们需要通过手势识别将这些控制点存储起来然后赋值给绘制组件进行更新就可以了,这里我们需要用到状态管理ChangeNotifier类,它继承Listenable,因为在绘制组件的构造方法里有一个参数repaint接受Listenable类型来控制是否重新绘制,数据变化就重新绘制。

const CustomPainter({ Listenable? repaint }) : _repaint = repaint;

因为CustomPainter的构造方法里的repaint参数就是负责更新绘制的,所以我们先要定义一个类继承ChangeNotifier来存储这些数据。

代码:

class TouchController extends ChangeNotifier {
  List<Offset> _points = []; //点集合
  int _selectIndex = -1;// 选中的点 更新位置用

  int get selectIndex => _selectIndex;

  List<Offset> get points => _points;

  // 选择某一个点 保存index
  set selectIndex(int value) {
    if (_selectIndex == value) return;
    _selectIndex = value;
    notifyListeners();// 通知刷新
  }
   // 选中的点标记
  Offset? get selectPoint => _selectIndex == -1 ? null : _points[_selectIndex];

  // 添加一个点
  void addPoint(Offset point) {
    points.add(point);
    notifyListeners();
  }
   // 手指移动时更新当前点的位置
  void updatePoint(int index, Offset point) {
    points[index] = point;
    notifyListeners();
  }
    // 删除最后一个点 相当于撤回上一步操作
  void removeLast() {
    points.removeLast();
    notifyListeners();
  }

}

有了存储数据的空间之后,我们就需要通过手势去获取这些点,通过手势在画布上的操作获取当前的位置进行存储以及更新。

 GestureDetector(
  child: CustomPaint(
    painter:
        _DolphinPainter(widget.touchController, widget.image),
  ),
  onPanDown: (d) {
    // 按压
    judgeZone(d.localPosition);
  },
  onPanUpdate: (d) {
    // 移动
    if (widget.touchController.selectIndex != -1) {
      widget.touchController.updatePoint(
          widget.touchController.selectIndex, d.localPosition);
    }
  },
)
///判断出是否在某点的半径为r圆范围内
bool judgeCircleArea(Offset src, Offset dst, double r) =>
    (src - dst).distance <= r;
///手指按下触发
void judgeZone(Offset src) {
  /// 循环所有的点
  for (int i = 0; i < widget.touchController.points.length; i++) {
    // 判断手指按的位置有没有按过的点
    if (judgeCircleArea(src, widget.touchController.points[i], 20)) {
      // 有点 不添加更新选中的点
      widget.touchController.selectIndex = i;
      return;
    }
  }
  // 无点 添加新的点 并将选中的点清空
  widget.touchController.addPoint(src);
  widget.touchController.selectIndex = -1;
}

到这里我们的手势按压和移动就会将数据存储到我们刚才定义的类中,接下来我们需要将这些数据赋予真正的绘制组件 CustomPainter

class _DolphinPainter extends CustomPainter {
  final TouchController touchController;// 存储数据类
//  final ui.Image image;

  _DolphinPainter(this.touchController, this.image)
    // 这个地方传入需要更新的 Listenable
      : super(repaint: touchController);

  List<Offset>? pos; //存储手势按压的点

  @override
  void paint(Canvas canvas, Size size) {
    // 画布原点平移到屏幕中央
    canvas.translate(size.width / 2, size.height / 2);
    // ,因为手势识别的原点是左上角,所以这里将存储的点相对的原点进行偏移到跟画布一致 负值向左上角偏移
    pos = touchController.points
        .map((e) => e.translate(-size.width / 2, -size.height / 2))
        .toList();

// 定义画笔
    var paint = Paint()
      ..strokeWidth = 2
      ..color = Colors.purple
      ..style = PaintingStyle.stroke
      ..isAntiAlias = true;

    // canvas.drawImage(image, Offset(-image.width / 2, -image.height / 2), paint);

    // 如果点小于4个 那么就只绘制点 如果>=4个点 那么就绘制贝塞尔曲线
    if (pos != null && pos!.length >= 4) {
      var path = Path();
      // 设置起点 手指第一个按压的点
      path.moveTo(pos![0].dx, (pos![0].dy));
      // path添加第一个贝塞尔曲线
      path.cubicTo(pos![1].dx,pos![1].dy, pos![2].dx, pos![2].dy, pos![3].dx,
          pos![3].dy);
          //绘制辅助线
      _drawHelpLine(canvas, size, paint, 0);
      // 绘制首个贝塞尔曲线
      canvas.drawPath(path, paint..color = Colors.purple);

      // for循环 绘制第2个以后的曲线 以上个终点为下一个的起点
      for (int i = 1; i < (pos!.length - 1) ~/ 3; i++) {
          //之后贝塞尔曲线的起点都是上一个贝塞尔曲线的终点
          // 比如第一个曲线 1,2,3,4.第二个就是4,5,6,7...以此类推,这样我们才能把线连接起来绘制图案
        // 这里把绘制之前的颜色覆盖
      // canvas.drawPath(path, paint..color = Colors.white);
        // 绘制辅助线
        _drawHelpLine(canvas, size, paint, i);
        //绘制贝塞尔曲线
        path.cubicTo(
          pos![i * 3 + 1].dx,
          pos![i * 3 + 1].dy,
          pos![i * 3 + 2].dx,
          pos![i * 3 + 2].dy,
          pos![i * 3 + 3].dx,
          pos![i * 3 + 3].dy,
        );

        if (i == 8) {
          path.close();
        }
        canvas.drawPath(path, paint..color = Colors.purple);
      }

      // 绘制辅助点
      _drawHelpPoint(canvas, paint);
      // 选中点
      _drawHelpSelectPoint(canvas, size, paint);
    } else {
      // 绘制辅助点
      _drawHelpPoint(canvas, paint);
    }

    // 画眼睛 眼睛位于起点的左侧,所以中心点向左偏移
    canvas.drawCircle(
        pos!.first.translate(-50, 5),
        10,
        paint
          ..color = Colors.black87
          ..style = PaintingStyle.stroke
          ..strokeWidth = 2);
    canvas.drawCircle(
        pos!.first.translate(-53, 5),
        7,
        paint
          ..color = Colors.black87
          ..style = PaintingStyle.fill);
  }
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
  return false;
}

void _drawHelpPoint(Canvas canvas, Paint paint) {
  canvas.drawPoints(
      PointMode.points,
      pos ?? [],
      paint
        ..strokeWidth = 10
        ..strokeCap = StrokeCap.round
        ..color = Colors.redAccent);
}

void _drawHelpSelectPoint(Canvas canvas, Size size, Paint paint) {
  Offset? selectPos = touchController.selectPoint;
  selectPos = selectPos?.translate(-size.width / 2, -size.height / 2);
  if (selectPos == null) return;
  canvas.drawCircle(
      selectPos,
      10,
      paint
        ..color = Colors.green
        ..strokeWidth = 2);
}

void _drawHelpLine(Canvas canvas, Size size, Paint paint, int i) {
  canvas.drawLine(
      Offset(pos![i * 3].dx, pos![i * 3].dy),
      Offset(pos![i * 3 + 1].dx, pos![i * 3 + 1].dy),
      paint
        ..color = Colors.redAccent
        ..strokeWidth = 2);

  canvas.drawLine(
      Offset(pos![i * 3 + 1].dx, pos![i * 3 + 1].dy),
      Offset(pos![i * 3 + 2].dx, pos![i * 3 + 2].dy),
      paint
        ..color = Colors.redAccent
        ..strokeWidth = 2);

  canvas.drawLine(
      Offset(pos![i * 3 + 2].dx, pos![i * 3 + 2].dy),
      Offset(pos![i * 3 + 3].dx, pos![i * 3 + 3].dy),
      paint
        ..color = Colors.redAccent
        ..strokeWidth = 2);
}

最终在我们的手指的控制以及辅助线的帮助下,图案就慢慢的绘制出来了。

去掉辅助线和点

然后将画笔改为填充,那么就得到我们一开始那副可爱的小海豚了。

总结

通过这个小海豚图案我们可以更加的理解贝塞尔曲线的绘制机制,通过你的手势控制,你也可以画出任何曲线和任何图案,可以说贝塞尔曲线就是绘制中的灵魂,掌握了贝塞尔曲线就相当于掌握了所有绘制组件,因为理论上来说,所有的二维图形都可以被贝塞尔曲线画出来,只要我们能准确的找到控制的点,就可以绘制无限可能的图案。

以上就是Android Flutter利用贝塞尔曲线画一个小海豚的详细内容,更多关于Android Flutter海豚的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android贝塞尔曲线实现直播点赞效果

    本文实例为大家分享了Android实现直播点赞效果的具体代码,供大家参考,具体内容如下 效果展示 原理分析 点赞效果最主要的难点和原理在于贝塞尔曲线动画的生成,我们通过图片主要讲解贝塞尔曲线动画 1.需要找到贝塞尔曲线的四个点 2.通过三级贝塞尔曲线的公式计算,获取贝塞尔曲线的轨迹路径点 3.通过设置点赞图片X,Y坐标,从而形成点赞的效果 实现步骤 1.初始化变量 //1.继承RelativeLayout public class ChristmasView extends RelativeLa

  • Android贝塞尔曲线实现手指轨迹

    本文实例为大家分享了Android贝塞尔曲线实现手指轨迹的具体代码,供大家参考,具体内容如下 1.使用贝塞尔曲线前 MyView.java public class MyView extends View { // 实例一个路径对象 private Path mPath = new Path(); public MyView(Context context) { super(context); // TODO Auto-generated constructor stub } public My

  • Android中贝塞尔曲线的绘制方法示例代码

    贝塞尔曲线,很多人可能不太了解,什么叫做贝塞尔曲线呢?这里先做一下简单介绍:贝塞尔曲线也可以叫做贝济埃曲线或者贝兹曲线,它由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋.一般的矢量图形软件常利用贝塞尔曲线来精确画出曲线. 上面的介绍中,"线段像可伸缩的皮筋"这句话非常关键,但也特别好理解.至于贝塞尔曲线的详细内容大家可以查阅相关资料.        Android提供的贝塞尔曲线绘制接口 在Android开发中,要实现贝塞尔曲线其实还是很简单的,因为Android已经给我们提

  • android贝塞尔曲线实现波浪效果

    本文实例为大家分享了android贝塞尔曲线实现波浪效果的具体代码,供大家参考,具体内容如下 因为手机录制gif不知道下什么软件好,所以暂时就先忽略效果图了 我在屏幕外多画了1.5个波浪,延伸至屏幕内.然后不断的循环,向右边移动.就有一种波浪的效果. 所以现在只需要画出左边的波长,然后再通过循环添加所有的波长即可. 第一个曲线已经确定了控制点和终点的坐标, 第二条曲线也可以很明显的看出来终点是在x轴的0点坐标,Y轴不变,而控制点是在负的波长的1/4的位置 有了上下曲线以后,其他的就可以直接通过循

  • Android Path绘制贝塞尔曲线实现QQ拖拽泡泡

    这两天学习了使用Path绘制贝塞尔曲线相关,然后自己动手做了一个类似QQ未读消息可拖拽的小气泡,效果图如下: 最终效果图 接下来一步一步的实现整个过程. 基本原理 其实就是使用Path绘制三点的二次方贝塞尔曲线来完成那个妖娆的曲线的.然后根据触摸点不断绘制对应的圆形,根据距离的改变改变原始固定圆形的半径大小.最后就是松手后返回或者爆裂的实现. Path介绍: 顾名思义,就是一个路径的意思,Path里面有很多的方法,本次设计主要用到的相关方法有 moveTo() 移动Path到一个指定的点 qua

  • Android Flutter利用贝塞尔曲线画一个小海豚

    目录 前言 效果图 实现步骤 总结 前言 贝塞尔曲线的应用填补了计算机绘制与手绘之前的差距,更能表达人想画出的曲线,为了更好的理解万能的贝塞尔曲线,而海豚是我认为在海洋生物中身体曲线最完美的海洋生物,在海洋中游泳速度最高可达80km/h;比驱逐舰速度还快,学习绘制正好学到了贝塞尔曲线,那么我们今天就用贝塞尔曲线画看看能不能画一只可爱的小海豚呢. 效果图 先上效果图: 实现步骤 path路径绘制贝塞尔曲线的方法非常简单,只需要传入控制点即可,二阶就传1个控制点1个终点,三阶就传2个控制点和1个终点

  • Android利用贝塞尔曲线绘制动画的示例代码

    目录 彩虹系列 弹簧动画 复杂立体感动画 总结 前面我们花了几篇介绍了贝塞尔曲线的原理和绘制贝塞尔曲线,着实让我们见识到了贝塞尔曲线的美.好奇心驱使我想看看贝塞尔曲线动起来会是什么样?本篇就借由动画驱动贝塞尔曲线绘制看看动起来的贝塞尔曲线什么效果. 彩虹系列 通过动画控制绘制的结束点,就可以让贝塞尔曲线动起来.例如下面的动图展示的效果,看起来像搭了一个滑滑梯一样.实际上就是用7条贝塞尔曲线实现的,我们使用了 Animation 对象的值来控制绘制的结束点,从而实现了对应的动画效果. 具体源码如下

  • Android 贝塞尔曲线绘制一个波浪球

    目录 前言 一.绘制 backgroundColor 文本 二.构建 circlePath 三.绘制波浪线 四.取交集 五.绘制 foregroundColor 文本 六.添加动画 七.使用 前言 当 flutter 的现有组件无法满足产品要求的 UI 效果时,我们就需要通过自绘组件的方式来进行实现了.本篇文章就来介绍如何用 flutter 自定义实现一个带文本的波浪球,效果如下所示: 先来总结下 WaveLoadingWidget 的特点,这样才能归纳出实现该效果所需要的步骤: widget

  • Android Flutter利用CustomPaint绘制基本图形详解

    目录 绘制矩形 绘制圆形 绘制椭圆 绘制任意形状 绘制弧形 总结 上一篇我们介绍了 CustomPaint 的基本概念和使用,可以看到 CustomPaint 其实和 前端的 Canvas基本上是一样的,实际上前端 Canvas 支持的绘制方法 CustomPaint 都支持,毕竟 CustomPaint 其实也是基于 Canvas 实现的.本篇我们来介绍 CustomPaint 基本图形的绘制. 绘制矩形 绘制矩形比较简单,方法定义如下: void drawRect(Rect rect, Pa

  • Android自定义view贝塞尔曲线

    本文实例为大家分享了Android自定义view贝塞尔曲线,供大家参考,具体内容如下 贝塞尔曲线 以一个简单的贝塞尔曲线为例,二阶曲线原理 贝塞尔曲线很多功能都会用到,比如小火箭发射,再比如淘宝的购物车功能 所幸的是Android有封装好的贝塞尔曲线,我们直接拿过来用就可以了: //二阶贝赛尔  public void quadTo(float x1, float y1, float x2, float y2)  public void rQuadTo(float dx1, float dy1,

  • 微信小程序使用二次贝塞尔曲线画波浪

    这两周做一个新的项目,人员比较紧张,除了需求和UI,前端后端一个人来干. 在项目需求确定后,UI隔了几天设计出了UI界面,拿到UI效果图后见有一个界面有波浪效果的我当时就蒙圈了,这都啥玩意啊?转念想到了最近在IT圈挺火的那个事件:产品要求安卓程序员实现根据用户手机壳颜色自动更换APP主题的需求后,顿时觉得画个波浪这个压根就不是事啊. 二次贝塞尔曲线 在微信官方的二次贝塞尔曲线画法连接 画波浪 思路: 在屏幕左边画一个波,然后让它一直向屏幕右边平移过去.其X的值由负数变为正数依次增大:然后一直重复

  • iOS贝塞尔曲线画哆啦A梦的代码实例

    看到这张图,是不是觉得挺萌的,那是如何实现的呢?在iOS中有一个类叫UIBezierPath(贝塞尔曲线),这两天研究了一下UIBezierPath和CAShapeLayer,根据别人分享的教程,画了这个萌萌的哆啦A梦. UIBezierPath: UIBezierPath是在 UIKit 中的一个类,继承于NSObject,可以创建基于矢量的路径.此类是Core Graphics框架关于path的一个OC封装.使用此类可以定义常见的圆形.多边形等形状 .我们使用直线.弧(arc)来创建复杂的曲

  • 利用three.js画一个3D立体的正方体示例代码

    简介 three.js 是一款WebGL框架,WebGL可以让我们在canvas上实现3D效果.实现3D效果在国内来说还算是比较新的东西,可供查阅的资料也不多.这篇文章仅是一个入门篇,介绍如何绘制一个3D正方体. Three.js中的基本概念 Three.js包含3个基本概念:场景(Scene).相机(Camera)和渲染器(Renderer). 场景就是需要绘制的对象,相机代表取景的视角,渲染器是绘制的载体(可以挂靠到浏览器的DOM元素中), 也就是我们通过相机拍摄场景然后绘制到目标介质中去.

  • Android贝塞尔曲线初步学习第二课 仿QQ未读消息气泡拖拽黏连效果

    上一节初步了解了Android端的贝塞尔曲线,这一节就举个栗子练习一下,仿QQ未读消息气泡,是最经典的练习贝塞尔曲线的东东,效果如下 附上github源码地址:https://github.com/MonkeyMushroom/DragBubbleView 欢迎star~ 大体思路就是画两个圆,一个黏连小球固定在一个点上,一个气泡小球跟随手指的滑动改变坐标.随着两个圆间距越来越大,黏连小球半径越来越小.当间距小于一定值,松开手指气泡小球会恢复原来位置:当间距超过一定值之后,黏连小球消失,气泡小球

随机推荐