小菜前幾天整理了以下 Canvas 的部分方法,今天小菜繼續學習 Canvas 第二部分。
drawXXX
drawShadow 繪制陰影
drawShadow 用於繪制陰影,第一個參數時繪制一個圖形 Path,第二個是設置陰影顏色,第三個為陰影范圍,最后一個陰影范圍是否填充滿;
canvas.drawShadow(
Path()
..moveTo(30.0, 30.0)..lineTo(120.0, 30.0)
..lineTo(120.0, 60.0)..lineTo(30.0, 60.0)
..close(),
Colors.blue, 3, false); canvas.drawShadow( Path() ..moveTo(30.0, 90.0)..lineTo(120.0, 90.0) ..lineTo(120.0, 120.0)..lineTo(30.0, 120.0), Colors.blue, 10, false); canvas.drawShadow( Path() ..moveTo(30.0, 150.0)..lineTo(120.0, 150.0) ..lineTo(120.0, 180.0)..lineTo(30.0, 180.0) ..close(), Colors.blue, 3, true); canvas.drawPath( Path() ..moveTo(30.0, 210.0)..lineTo(120.0, 210.0) ..lineTo(120.0, 240.0)..lineTo(30.0, 240.0), Paint()..color = Colors.blue..strokeWidth = 2..style = PaintingStyle.stroke);
drawImage 繪制圖片
drawImage 用於繪制圖片,繪制圖片是重點,此時的 Image 並非日常所用的圖片加載,而是用的 dart.ui 類中的 ui.Image 並轉換成字節流 ImageStream 方式傳遞,包括本地圖片或網絡圖片
// 獲取圖片 本地為false 網絡為true Future<ui.Image> _loadImage(var path, bool isUrl) async { ImageStream stream; if (isUrl) { stream = NetworkImage(path).resolve(ImageConfiguration.empty); } else { stream = AssetImage(path, bundle: rootBundle).resolve(ImageConfiguration.empty); } Completer<ui.Image> completer = Completer<ui.Image>(); void listener(ImageInfo frame, bool synchronousCall) { final ui.Image image = frame.image; completer.complete(image); stream.removeListener(listener); } stream.addListener(listener); return completer.future; } // 加載圖片 _prepareImg() { _loadImage('images/icon_hzw02.jpg', false).then((image1) { _image1 = image1; }).whenComplete(() { _loadImage('https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=703702342,1604162245&fm=26&gp=0.jpg', true) .then((image2) { _image2 = image2; }).whenComplete(() { _prepDone = true; if (this.mounted) { setState(() {}); } }); }); } canvas.drawImage(this.image, ui.Offset(120.0, 540.0), Paint()); canvas.drawImage(this.image2, ui.Offset(60.0, 60.0), Paint());
Tips:
小菜在嘗試過程中總是加載失敗,后來理解為繪制過程需要時間,可以通過 setState(() {}); 和 whenComplete 判斷狀態后進行刷新,這種方式並非最佳,希望有更好方式的朋友多指導。
drawImageRect 繪制矩形圖片
drawImageRect 除了可以正常繪制圖片之外,還可以繪制圖片部分內容,如下:第一個參數為 ui.Image,第二個參數為需要原圖繪制矩形范圍,第三個參數為本次繪制矩形范圍,最后一個為畫筆;
小菜繪制原圖與部分圖進行對比,drawImageRect 繪制的原圖粉色圈出的范圍;且 drawImageRect 效率更好,推薦使用;
canvas.drawImage(this.image, ui.Offset(60.0, 540.0), Paint()); canvas.drawImageRect( this.image, Rect.fromLTWH(0, 0, 60, 60), Rect.fromLTWH(60, 60, image.width.toDouble(), image.height.toDouble()), Paint());
drawImageNine 繪制九圖
drawImageNine 同樣用來繪制圖片,與原圖繪制方式不同在於,drawImageNine 中第二個參數矩形變長延伸將原圖分割為九部分,划為中心區域,第三個參數矩形即繪制整體矩形范圍,包括四個頂點位置;小菜繪制原圖與部分圖進行對比,drawImageNine 繪制的原圖綠色圈出的范圍,小菜感覺類似於裁剪了原圖;
canvas.drawImage(this.image, ui.Offset(60.0, 540.0), Paint()); canvas.drawImageNine( this.image, Rect.fromLTWH(0, 0, 120, 60), Rect.fromLTWH( 60, 1020, image.width.toDouble() - 120, image.height.toDouble()), Paint());
drawParagraph 繪制文字段落
小菜以前認為 Canvas 不支持繪制文字,現在學習了 drawParagraph 完全可以繪制文字效果與 TextPainter 效果相同;
文字段落 Paragraph 是 dart.ui 中的類,用構造器方式進行內容綁定;ParagraphStyle 用來設置文字的樣式屬性,包括文字位置/方向/字體粗細/文字樣式/行數等;其中 ellipsis 用來設置內容超出范圍截取時最后展示內容,可隨意編輯;
ParagraphBuilder pb = ParagraphBuilder(ParagraphStyle(
textAlign: TextAlign.center,
fontWeight: FontWeight.w600,
fontStyle: FontStyle.normal,
fontSize: 18,
))
..pushStyle(ui.TextStyle(color: Colors.blue))
..addText(
'Flutter是谷歌的移動UI框架,可以快速在iOS和Android上構建高質量的原生用戶界面。 Flutter可以與現有的代碼一起工作。在全世界,Flutter正在被越來越多的開發者和組織使用,並且Flutter是完全免費、開源的。');
ParagraphConstraints pc = ParagraphConstraints(width: Screen.width - 60);
Paragraph paragraph = pb.build()..layout(pc);
canvas.drawParagraph(paragraph, Offset(30, 30));
pb = ParagraphBuilder(ParagraphStyle(
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w300,
fontSize: 18,
))
..pushStyle(ui.TextStyle(color: Colors.red))
..addText(
'Flutter是谷歌的移動UI框架,可以快速在iOS和Android上構建高質量的原生用戶界面。 Flutter可以與現有的代碼一起工作。在全世界,Flutter正在被越來越多的開發者和組織使用,並且Flutter是完全免費、開源的。');
pc = ParagraphConstraints(width: Screen.width - 60);
paragraph = pb.build()..layout(pc);
canvas.drawParagraph(paragraph, Offset(30, 180));
pb = ParagraphBuilder(ParagraphStyle(
fontStyle: FontStyle.normal,
fontSize: 18,
maxLines: 3,
ellipsis: '...',
))
..pushStyle(ui.TextStyle(color: Colors.green))
..addText(
'Flutter是谷歌的移動UI框架,可以快速在iOS和Android上構建高質量的原生用戶界面。 Flutter可以與現有的代碼一起工作。在全世界,Flutter正在被越來越多的開發者和組織使用,並且Flutter是完全免費、開源的。');
pc = ParagraphConstraints(width: Screen.width - 60);
paragraph = pb.build()..layout(pc);
canvas.drawParagraph(paragraph, Offset(30, 340));
clipXXX
以上介紹的都是繪制方法,接下來小菜簡單介紹幾種裁剪方法。
clipRect 裁剪矩形
clipRect 可以在規定的矩形內進行繪制,超出范圍不繪制;
canvas.clipRect(Rect.fromLTWH(30, 1200, Screen.width / 0.3 - 60, 300),
doAntiAlias: false); canvas.drawImage(this.image, ui.Offset(260, 1200), Paint());
clipRRect 裁剪圓角矩形
clipRRect 可以在規定的圓角矩形內進行繪制,超出范圍不繪制;
canvas.clipRRect( RRect.fromRectXY( Rect.fromLTWH(300, 1600, image.width - 60.0, 300), 20, 20), doAntiAlias: false); canvas.drawImage(this.image, ui.Offset(260, 1200), Paint());
clipPath 裁剪由線圍成區域
clipPath 可以在規定的點連線范圍內進行繪制,默認終點與始點連接,當然可以繪制圓或貝塞爾曲線等,超出范圍不繪制;
canvas.clipPath(Path() ..moveTo(300, 100)..lineTo(900, 100) ..lineTo(800, 600)..lineTo(400, 600)); canvas.drawImage(this.image, ui.Offset(260, 90), Paint());
Canvas 真的非常強大,還有很多研究不透徹的地方,小菜還在不斷學習,有錯誤的地方煩請多多指點!