網站首頁 編程語言 正文
前言
貝塞爾曲線的應用填補了計算機繪制與手繪之前的差距,更能表達人想畫出的曲線,為了更好的理解萬能的貝塞爾曲線,而海豚是我認為在海洋生物中身體曲線最完美的海洋生物,在海洋中游泳速度最高可達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); }
最終在我們的手指的控制以及輔助線的幫助下,圖案就慢慢的繪制出來了。
去掉輔助線和點
然后將畫筆改為填充,那么就得到我們一開始那副可愛的小海豚了。
總結
通過這個小海豚圖案我們可以更加的理解貝塞爾曲線的繪制機制,通過你的手勢控制,你也可以畫出任何曲線和任何圖案,可以說貝塞爾曲線就是繪制中的靈魂,掌握了貝塞爾曲線就相當于掌握了所有繪制組件,因為理論上來說,所有的二維圖形都可以被貝塞爾曲線畫出來,只要我們能準確的找到控制的點,就可以繪制無限可能的圖案。
原文鏈接:https://juejin.cn/post/7085739301970903047
相關推薦
- 2022-08-20 python?memory_profiler庫生成器和迭代器內存占用的時間分析_python
- 2022-05-28 Golang空接口與類型斷言的實現_Golang
- 2022-06-02 解決Spring Boot數據庫多數據源配置報jdbcUrl is required with dr
- 2022-09-03 Python標準庫sys庫常用功能詳解_python
- 2021-12-01 Android之小球自由碰撞動畫示例_Android
- 2022-05-27 解析OpenSSL1.1.1?centos7安裝編譯aes的c++調用_C 語言
- 2022-10-18 AJAX跨域問題解決方案詳解_AJAX相關
- 2022-08-23 C++詳解使用floor&ceil&round實現保留小數點后兩位_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支