亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android Flutter利用貝塞爾曲線畫一個(gè)小海豚

 更新時(shí)間:2022年04月13日 08:33:12   作者:老李code  
貝塞爾曲線的應(yīng)用填補(bǔ)了計(jì)算機(jī)繪制與手繪之前的差距,更能表達(dá)人想畫出的曲線。本文就將利用貝塞爾曲線繪制一個(gè)可愛(ài)的小海豚,需要的可以參考一下

前言

貝塞爾曲線的應(yīng)用填補(bǔ)了計(jì)算機(jī)繪制與手繪之前的差距,更能表達(dá)人想畫出的曲線,為了更好的理解萬(wàn)能的貝塞爾曲線,而海豚是我認(rèn)為在海洋生物中身體曲線最完美的海洋生物,在海洋中游泳速度最高可達(dá)80km/h;比驅(qū)逐艦速度還快,學(xué)習(xí)繪制正好學(xué)到了貝塞爾曲線,那么我們今天就用貝塞爾曲線畫看看能不能畫一只可愛(ài)的小海豚呢。

效果圖

先上效果圖:

實(shí)現(xiàn)步驟

path路徑繪制貝塞爾曲線的方法非常簡(jiǎn)單,只需要傳入控制點(diǎn)即可,二階就傳1個(gè)控制點(diǎn)1個(gè)終點(diǎn),三階就傳2個(gè)控制點(diǎn)和1個(gè)終點(diǎn),但是要找到合適控制的點(diǎn)就沒(méi)那么容易了,這時(shí)候我們?nèi)绻梢杂檬种冈谄聊簧喜粩嗾{(diào)試尋找合適的點(diǎn)豈不是非常方便,接下來(lái)我們就先實(shí)現(xiàn)下面的功能,通過(guò)手指不斷調(diào)試控制點(diǎn)位并將多個(gè)貝塞爾曲線進(jìn)行連接。

可以看到一個(gè)三階貝塞爾需要1個(gè)起點(diǎn)、2個(gè)控制點(diǎn)和1個(gè)終點(diǎn)組成,首先我們需要通過(guò)手勢(shì)識(shí)別將這些控制點(diǎn)存儲(chǔ)起來(lái)然后賦值給繪制組件進(jìn)行更新就可以了,這里我們需要用到狀態(tài)管理ChangeNotifier類,它繼承Listenable,因?yàn)樵诶L制組件的構(gòu)造方法里有一個(gè)參數(shù)repaint接受Listenable類型來(lái)控制是否重新繪制,數(shù)據(jù)變化就重新繪制。

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

因?yàn)?code>CustomPainter的構(gòu)造方法里的repaint參數(shù)就是負(fù)責(zé)更新繪制的,所以我們先要定義一個(gè)類繼承ChangeNotifier來(lái)存儲(chǔ)這些數(shù)據(jù)。

代碼:

class TouchController extends ChangeNotifier {
  List<Offset> _points = []; //點(diǎn)集合
  int _selectIndex = -1;// 選中的點(diǎn) 更新位置用

  int get selectIndex => _selectIndex;

  List<Offset> get points => _points;

  // 選擇某一個(gè)點(diǎn) 保存index
  set selectIndex(int value) {
    if (_selectIndex == value) return;
    _selectIndex = value;
    notifyListeners();// 通知刷新
  }
   // 選中的點(diǎn)標(biāo)記
  Offset? get selectPoint => _selectIndex == -1 ? null : _points[_selectIndex];

  // 添加一個(gè)點(diǎn)
  void addPoint(Offset point) {
    points.add(point);
    notifyListeners();
  }
   // 手指移動(dòng)時(shí)更新當(dāng)前點(diǎn)的位置
  void updatePoint(int index, Offset point) {
    points[index] = point;
    notifyListeners();
  }
    // 刪除最后一個(gè)點(diǎn) 相當(dāng)于撤回上一步操作
  void removeLast() {
    points.removeLast();
    notifyListeners();
  }

}

有了存儲(chǔ)數(shù)據(jù)的空間之后,我們就需要通過(guò)手勢(shì)去獲取這些點(diǎn),通過(guò)手勢(shì)在畫布上的操作獲取當(dāng)前的位置進(jìn)行存儲(chǔ)以及更新。

 GestureDetector(
  child: CustomPaint(
    painter:
        _DolphinPainter(widget.touchController, widget.image),
  ),
  onPanDown: (d) {
    // 按壓
    judgeZone(d.localPosition);
  },
  onPanUpdate: (d) {
    // 移動(dòng)
    if (widget.touchController.selectIndex != -1) {
      widget.touchController.updatePoint(
          widget.touchController.selectIndex, d.localPosition);
    }
  },
)
///判斷出是否在某點(diǎn)的半徑為r圓范圍內(nèi)
bool judgeCircleArea(Offset src, Offset dst, double r) =>
    (src - dst).distance <= r;
///手指按下觸發(fā)
void judgeZone(Offset src) {
  /// 循環(huán)所有的點(diǎn)
  for (int i = 0; i < widget.touchController.points.length; i++) {
    // 判斷手指按的位置有沒(méi)有按過(guò)的點(diǎn)
    if (judgeCircleArea(src, widget.touchController.points[i], 20)) {
      // 有點(diǎn) 不添加更新選中的點(diǎn)
      widget.touchController.selectIndex = i;
      return;
    }
  }
  // 無(wú)點(diǎn) 添加新的點(diǎn) 并將選中的點(diǎn)清空
  widget.touchController.addPoint(src);
  widget.touchController.selectIndex = -1;
}

到這里我們的手勢(shì)按壓和移動(dòng)就會(huì)將數(shù)據(jù)存儲(chǔ)到我們剛才定義的類中,接下來(lái)我們需要將這些數(shù)據(jù)賦予真正的繪制組件 CustomPainter。

class _DolphinPainter extends CustomPainter {
  final TouchController touchController;// 存儲(chǔ)數(shù)據(jù)類
//  final ui.Image image;

  _DolphinPainter(this.touchController, this.image)
    // 這個(gè)地方傳入需要更新的 Listenable
      : super(repaint: touchController);

  List<Offset>? pos; //存儲(chǔ)手勢(shì)按壓的點(diǎn)

  @override
  void paint(Canvas canvas, Size size) {
    // 畫布原點(diǎn)平移到屏幕中央
    canvas.translate(size.width / 2, size.height / 2);
    // ,因?yàn)槭謩?shì)識(shí)別的原點(diǎn)是左上角,所以這里將存儲(chǔ)的點(diǎn)相對(duì)的原點(diǎn)進(jìn)行偏移到跟畫布一致 負(fù)值向左上角偏移
    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);

    // 如果點(diǎn)小于4個(gè) 那么就只繪制點(diǎn) 如果>=4個(gè)點(diǎn) 那么就繪制貝塞爾曲線
    if (pos != null && pos!.length >= 4) {
      var path = Path();
      // 設(shè)置起點(diǎn) 手指第一個(gè)按壓的點(diǎn)
      path.moveTo(pos![0].dx, (pos![0].dy));
      // path添加第一個(gè)貝塞爾曲線
      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);
      // 繪制首個(gè)貝塞爾曲線
      canvas.drawPath(path, paint..color = Colors.purple);
      
      // for循環(huán) 繪制第2個(gè)以后的曲線 以上個(gè)終點(diǎn)為下一個(gè)的起點(diǎn)
      for (int i = 1; i < (pos!.length - 1) ~/ 3; i++) {
          //之后貝塞爾曲線的起點(diǎn)都是上一個(gè)貝塞爾曲線的終點(diǎn)
          // 比如第一個(gè)曲線 1,2,3,4.第二個(gè)就是4,5,6,7...以此類推,這樣我們才能把線連接起來(lái)繪制圖案
        // 這里把繪制之前的顏色覆蓋
      // 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);
      }

      // 繪制輔助點(diǎn)
      _drawHelpPoint(canvas, paint);
      // 選中點(diǎn)
      _drawHelpSelectPoint(canvas, size, paint);
    } else {
      // 繪制輔助點(diǎn)
      _drawHelpPoint(canvas, paint);
    }


    // 畫眼睛 眼睛位于起點(diǎn)的左側(cè),所以中心點(diǎn)向左偏移
    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);
}

最終在我們的手指的控制以及輔助線的幫助下,圖案就慢慢的繪制出來(lái)了。

去掉輔助線和點(diǎn)

然后將畫筆改為填充,那么就得到我們一開(kāi)始那副可愛(ài)的小海豚了。

總結(jié)

通過(guò)這個(gè)小海豚圖案我們可以更加的理解貝塞爾曲線的繪制機(jī)制,通過(guò)你的手勢(shì)控制,你也可以畫出任何曲線和任何圖案,可以說(shuō)貝塞爾曲線就是繪制中的靈魂,掌握了貝塞爾曲線就相當(dāng)于掌握了所有繪制組件,因?yàn)槔碚撋蟻?lái)說(shuō),所有的二維圖形都可以被貝塞爾曲線畫出來(lái),只要我們能準(zhǔn)確的找到控制的點(diǎn),就可以繪制無(wú)限可能的圖案。

以上就是Android Flutter利用貝塞爾曲線畫一個(gè)小海豚的詳細(xì)內(nèi)容,更多關(guān)于Android Flutter海豚的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論