W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
對于一些復雜或不規(guī)則的 UI,我們可能無法通過組合其它組件的方式來實現(xiàn),比如我們需要一個正六邊形、一個漸變的圓形進度條、一個棋盤等。當然,有時候我們可以使用圖片來實現(xiàn),但在一些需要動態(tài)交互的場景靜態(tài)圖片也是實現(xiàn)不了的,比如要實現(xiàn)一個手寫輸入面板,這時,我們就需要來自己繪制 UI 外觀。
幾乎所有的 UI 系統(tǒng)都會提供一個自繪 UI 的接口,這個接口通常會提供一塊 2D 畫布Canvas
,Canvas
內部封裝了一些基本繪制的 API,開發(fā)者可以通過Canvas
繪制各種自定義圖形。在 Flutter 中,提供了一個CustomPaint
組件,它可以結合畫筆CustomPainter
來實現(xiàn)自定義圖形繪制。
我們看看CustomPaint
構造函數(shù):
CustomPaint({
Key key,
this.painter,
this.foregroundPainter,
this.size = Size.zero,
this.isComplex = false,
this.willChange = false,
Widget child, //子節(jié)點,可以為空
})
painter
: 背景畫筆,會顯示在子節(jié)點后面;foregroundPainter
: 前景畫筆,會顯示在子節(jié)點前面size
:當 child 為 null 時,代表默認繪制區(qū)域大小,如果有 child 則忽略此參數(shù),畫布尺寸則為 child 尺寸。如果有 child 但是想指定畫布為特定大小,可以使用 SizeBox 包裹 CustomPaint 實現(xiàn)。isComplex
:是否復雜的繪制,如果是,F(xiàn)lutter 會應用一些緩存策略來減少重復渲染的開銷。willChange
:和isComplex
配合使用,當啟用緩存時,該屬性代表在下一幀中繪制是否會改變。
可以看到,繪制時我們需要提供前景或背景畫筆,兩者也可以同時提供。我們的畫筆需要繼承CustomPainter
類,我們在畫筆類中實現(xiàn)真正的繪制邏輯。
如果CustomPaint
有子節(jié)點,為了避免子節(jié)點不必要的重繪并提高性能,通常情況下都會將子節(jié)點包裹在RepaintBoundary
組件中,這樣會在繪制時就會創(chuàng)建一個新的繪制層(Layer),其子組件將在新的 Layer 上繪制,而父組件將在原來 Layer 上繪制,也就是說RepaintBoundary
子組件的繪制將獨立于父組件的繪制,RepaintBoundary
會隔離其子節(jié)點和CustomPaint
本身的繪制邊界。示例如下:
CustomPaint(
size: Size(300, 300), //指定畫布大小
painter: MyPainter(),
child: RepaintBoundary(child:...)),
)
CustomPainter
中提定義了一個虛函數(shù)paint
:
void paint(Canvas canvas, Size size);
paint
有兩個參數(shù):
Canvas
:一個畫布,包括各種繪制方法,我們列出一下常用的方法:API名稱 | 功能 |
---|---|
drawLine | 畫線 |
drawPoint | 畫點 |
drawPath | 畫路徑 |
drawImage | 畫圖像 |
drawRect | 畫矩形 |
drawCircle | 畫圓 |
drawOval | 畫橢圓 |
drawArc | 畫圓弧 |
Size
:當前繪制區(qū)域大小。
現(xiàn)在畫布有了,我們最后還缺一個畫筆,F(xiàn)lutter 提供了Paint
類來實現(xiàn)畫筆。在Paint
中,我們可以配置畫筆的各種屬性如粗細、顏色、樣式等。如:
var paint = Paint() //創(chuàng)建一個畫筆并配置其屬性
..isAntiAlias = true //是否抗鋸齒
..style = PaintingStyle.fill //畫筆樣式:填充
..color=Color(0x77cdb175);//畫筆顏色
更多的配置屬性讀者可以參考Paint類定義。
下面我們通過一個五子棋游戲中棋盤和棋子的繪制來演示自繪UI的過程,首先我們看一下我們的目標效果,如圖10-3所示:
代碼:
import 'package:flutter/material.dart';
import 'dart:math';
class CustomPaintRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: CustomPaint(
size: Size(300, 300), //指定畫布大小
painter: MyPainter(),
),
);
}
}
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
double eWidth = size.width / 15;
double eHeight = size.height / 15;
//畫棋盤背景
var paint = Paint()
..isAntiAlias = true
..style = PaintingStyle.fill //填充
..color = Color(0x77cdb175); //背景為紙黃色
canvas.drawRect(Offset.zero & size, paint);
//畫棋盤網(wǎng)格
paint
..style = PaintingStyle.stroke //線
..color = Colors.black87
..strokeWidth = 1.0;
for (int i = 0; i <= 15; ++i) {
double dy = eHeight * i;
canvas.drawLine(Offset(0, dy), Offset(size.width, dy), paint);
}
for (int i = 0; i <= 15; ++i) {
double dx = eWidth * i;
canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint);
}
//畫一個黑子
paint
..style = PaintingStyle.fill
..color = Colors.black;
canvas.drawCircle(
Offset(size.width / 2 - eWidth / 2, size.height / 2 - eHeight / 2),
min(eWidth / 2, eHeight / 2) - 2,
paint,
);
//畫一個白子
paint.color = Colors.white;
canvas.drawCircle(
Offset(size.width / 2 + eWidth / 2, size.height / 2 - eHeight / 2),
min(eWidth / 2, eHeight / 2) - 2,
paint,
);
}
//在實際場景中正確利用此回調可以避免重繪開銷,本示例我們簡單的返回true
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
繪制是比較昂貴的操作,所以我們在實現(xiàn)自繪控件時應該考慮到性能開銷,下面是兩條關于性能優(yōu)化的建議:
shouldRepaint
返回值;在 UI 樹重新 build 時,控件在繪制前都會先調用該方法以確定是否有必要重繪;假如我們繪制的 UI 不依賴外部狀態(tài),那么就應該始終返回false
,因為外部狀態(tài)改變導致重新 build 時不會影響我們的 UI 外觀;如果繪制依賴外部狀態(tài),那么我們就應該在shouldRepaint
中判斷依賴的狀態(tài)是否改變,如果已改變則應返回true
來重繪,反之則應返回false
不需要重繪。shouldRepaint
回調值為false
,然后將棋盤組件作為背景。然后將棋子的繪制放到另一個組件中,這樣每次落子時只需要繪制棋子。
自繪控件非常強大,理論上可以實現(xiàn)任何2D圖形外觀,實際上 Flutter 提供的所有組件最終都是通過調用 Canvas 繪制出來的,只不過繪制的邏輯被封裝起來了,讀者有興趣可以查看具有外觀樣式的組件源碼,找到其對應的RenderObject
對象,如Text
對應的RenderParagraph
對象最終會通過Canvas
實現(xiàn)文本繪制邏輯。下一節(jié)我們會再通過一個自繪的圓形背景漸變進度條的實例來幫助讀者加深印象。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: