歡迎來到 Flutter 繪制實踐系列饲嗽,本文有文章版和視頻版野芒。視頻發(fā)布在 bilibli 同名賬號下扳剿,文章首發(fā)于掘金平臺。今天來的話題是: 繪制陰影
Flutter 繪制實踐系列視頻鏈接:
- Flutter 繪制實踐 | 第一集 · 畫板尺寸
- Flutter 繪制實踐 | 第二集 · 坐標系
- Flutter 繪制實踐 | 第三集 · 畫板更新
- Flutter 繪制實踐 | 第四集 · 動畫數(shù)值
- Flutter 繪制實踐 | 第五集 · 坐標軸范圍
- Flutter 繪制實踐 | 第六集 · 函數(shù)曲線
- Flutter 繪制實踐 | 路徑篇 · 雪花1
- Flutter 繪制實踐 | 路徑篇 · 雪花2
- Flutter 繪制實踐 | 路徑篇 · 變換中心
- Flutter 繪制實踐 | 路徑篇 · 陰影模糊
1.陰影的繪制
說起 Flutter 繪制陰影掂恕,很多朋友可能都知道 Canvas
本身有 drawShadow
方法〕诨保可以根據(jù)入?yún)⒌?Path 路徑竹海,繪制陰影。就像我們男人分不清口紅色號一樣丐黄,覺得口紅就是紅色斋配,很多編程者也會覺得陰影就是影子。如果一個方法就能繪制出完美的陰影灌闺,也不會單獨寫篇文章艰争。
Canvas 中提供的 drawShadow
方法是根據(jù) 影深 elevation
進行繪制陰影的。如下所示桂对,是 drawShadow
繪制圓角矩形路徑的效果甩卓,其中第四入是 bool
值,用于控制路徑內部是否填充:
transparentOccluder:true | transparentOccluder:false |
---|---|
@override
void paint(Canvas canvas, Size size) {
canvas.translate(size.width / 2, size.height / 2);
Path path = Path()
..addRRect(
RRect.fromRectAndRadius(Rect.fromPoints(
Offset.zero,
const Offset(100,60),
), const Radius.circular(8)),
);
canvas.drawShadow(path, Colors.blue.withOpacity(0.2), 5, false);
}
一般來說不會只是單繪制一個路徑陰影蕉斜,還要繪制影子的發(fā)出者逾柿。如下所示,在陰影上方繪制白色的 "實物"
宅此。這樣陰影的效果就有了机错,并且隨著 elevation 的增加,陰影會越深:
void drawBox(Canvas canvas,double elevation){
Path path = Path()
..addRRect(
RRect.fromRectAndRadius(Rect.fromPoints(
Offset.zero,
const Offset(100,60),
), const Radius.circular(8)),
);
canvas.drawShadow(path, Colors.blue.withOpacity(0.2), elevation, false);
Paint whitePaint = Paint()..color=Colors.white;
canvas.drawPath(path,whitePaint);
}
其實可以發(fā)現(xiàn) drawShadow
只能根據(jù)影深來控制陰影效果父腕,無法調節(jié)陰影偏移量弱匪、陰影半徑、陰影擴散等屬性璧亮。所以萧诫,我們需要尋找到一種途徑斥难,來繪制更加復雜的路徑陰影。比如 css 中的陰影樣式定義:
2. BoxDecoration 中陰影的使用
在日常開發(fā)中帘饶,我們或多或少使用過裝飾屬性哑诊,比如 Container
和 DecoratedBox
組件可以通過BoxDecoration
確定裝飾的效果。其中就包含對陰影的處理及刻,而且和 css 中的陰影樣式是一致的:
css 中 box-shadow 是一個列表搭儒,每組由五個參數(shù),分別表示:
x偏移量 | y偏移量 | 陰影模糊半徑 | 陰影擴散半徑 | 陰影顏色
而 Flutter 中的 BoxDecoration#boxShadow
也是一個列表提茁,其中配置參數(shù)由 BoxShadow
類記錄淹禾。參數(shù)的作用和 css 中的是一致的,offset 表示偏移量茴扁,blurRadius 表示陰影模糊半徑铃岔,spreadRadius 表示陰影擴散半徑。
這樣如果已知 css 中的陰影樣式峭火,就很容易將其在 Flutter 中展示出來毁习。如下是 Element UI
的全局彈框的陰影效果,在 Flutter 中的表現(xiàn):源碼詳見 box_decoration
// ElementUI 陰影
BoxDecoration element = BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color:const Color(0xffebeef5)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
offset: const Offset(0,2),
blurRadius: 12,
spreadRadius: 0,
)
],
);
如下是 Ant Design
全局彈框的陰影效果卖丸,其中的 box-shadow 由三個陰影確定:
對于 Flutter 而言纺且,就是在 boxShadow
中提供多個 BoxShadow
對象:
// Ant Design 陰影
BoxDecoration decoration = BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
offset: const Offset(0,6),
blurRadius: 16,
spreadRadius: 0,
),
BoxShadow(
color: Colors.black.withOpacity(0.12),
offset: const Offset(0,3),
blurRadius: 6,
spreadRadius: -4,
),
BoxShadow(
color: Colors.black.withOpacity(0.05),
offset: const Offset(0,9),
blurRadius: 28,
spreadRadius: 8,
)
],
);
3. BoxDecoration 是如何繪制的
既然 BoxDecoration 可以靈活地處理陰影樣式,那么問題來了稍浆,在 Canvas 的繪制中载碌,如何使用呢?其實仔細思考一下衅枫,F(xiàn)lutter 中的一切組件都是繪制出來的嫁艇,那么 BoxDecoration
自然也不例外。那么只要查閱源碼弦撩,看一下它的繪制邏輯步咪,自然可以知道這種陰影是如何實現(xiàn)的。那接下來開始探秘吧益楼。
首先來看 DecoratedBox
組件對應的渲染對象的繪制方法折柠。如下所示飒货,BoxDecoration 裝飾對象通過 createBoxPainter
方法創(chuàng)建繪制對象:
也就是說 BoxDecoration 只承載繪制的信息赌蔑,繪制的邏輯由繪制對象承擔拌蜘。 通過源碼可以看出創(chuàng)建的繪制對象是 _BoxDecorationPainter
, 也就是說該類中可能會是繪制陰影邏輯發(fā)生的場所:
果然不出所料,繪制陰影的核心邏輯就在這個類中俊扭,會遍歷 boxShadow
列表繪制陰影區(qū)域队橙。其中偏移量 offset 是矩形區(qū)域的移動;擴散半徑 spreadRadius 是矩形區(qū)域的擴大萨惑;最后剩下一個模糊長度 blurRadius 還未知:
在上面 447 行中捐康,畫筆是通過 boxShadow
對象會取的,進入其中可以發(fā)現(xiàn)畫筆會設置一個模糊的蒙版庸蔼。蒙版的模糊參數(shù)由 blurRadius
確定解总。到這里我們就清楚了,BoxDecoration 實現(xiàn)陰影的本質是通過 模糊蒙版
姐仅。所以我們也可以借鑒這種思路花枫,來處理繪制時的路徑陰影。
double get blurSigma => convertRadiusToSigma(blurRadius);
4. 通過 BoxShadow 繪制陰影
BoxDecoration 本身用于矩形類的陰影繪制掏膏,像路徑這種不規(guī)則的圖形不能直接使用劳翰。所以需要進行一些處理,特別是 spreadRadius
對陰影的擴散處理馒疹。如下是封裝的方法佳簸,路徑的偏移很簡單,可以通過 shift
進行處理颖变。難點在于 spreadRadius
的處理生均,這里對 path
沿矩形區(qū)域中心進行縮放來實現(xiàn)擴散的效果。關于 Path 的簡單變換腥刹,在上一篇已經(jīng)介紹過了马胧。
void drawShadows(Canvas canvas, Path path, List<BoxShadow> shadows) {
for (final BoxShadow shadow in shadows) {
final Paint shadowPainter = shadow.toPaint();
if (shadow.spreadRadius == 0) {
canvas.drawPath(path.shift(shadow.offset), shadowPainter);
} else {
Rect zone = path.getBounds();
double xScale = (zone.width + shadow.spreadRadius) / zone.width;
double yScale = (zone.height + shadow.spreadRadius) / zone.height;
Matrix4 m4 = Matrix4.identity();
m4.translate(zone.width / 2, zone.height / 2);
m4.scale(xScale, yScale);
m4.translate(-zone.width / 2, -zone.height / 2);
canvas.drawPath(path.shift(shadow.offset).transform(m4.storage), shadowPainter);
}
}
Paint whitePaint = Paint()..color = Colors.white;
canvas.drawPath(path, whitePaint);
}
這樣,只要指定路徑和 BoxShadow
列表即可繪制出路徑的陰影衔峰。
// 第一個
drawShadows(canvas, rect, [
BoxShadow(
color: Colors.black.withOpacity(0.1),
offset: const Offset(0, 2),
blurRadius: 6,
spreadRadius: 0,
)
]);
// 第二個
drawShadows(canvas, rect, [
BoxShadow(
color: Colors.redAccent.withOpacity(0.2),
offset: const Offset(0, 2),
blurRadius: 8,
spreadRadius: 0,
)
]);
// 第三個
drawShadows(canvas, rect, [
BoxShadow(
color: Colors.black.withOpacity(0.08),
offset: const Offset(0,6),
blurRadius: 16,
spreadRadius: 0,
),
BoxShadow(
color: Colors.black.withOpacity(0.12),
offset: const Offset(0,3),
blurRadius: 6,
spreadRadius: -4,
),
BoxShadow(
color: Colors.black.withOpacity(0.05),
offset: const Offset(0,9),
blurRadius: 28,
spreadRadius: 8,
)
]);
這樣佩脊,不止是矩形區(qū)域,對于任何路徑來說垫卤,都可以實現(xiàn)陰影效果邻吞。這種陰影可以設置偏移、模糊半徑葫男、擴散半徑抱冷,也可以設置多陰影,如下的第二片雪花是兩個陰影的疊加梢褐。另外旺遮,雪花路徑的繪制在之前視頻中介紹過,這里就不貼代碼了盈咳,詳見源碼 shadow_painter_v2耿眉。
drawShadows(
canvas,
path,
[BoxShadow(
color: Colors.blue.withOpacity(0.2),
offset: const Offset(0, 2),
blurRadius: 6,
spreadRadius: 0,
),
BoxShadow(
color: Colors.redAccent.withOpacity(0.2),
offset: const Offset(0, -2),
blurRadius: 6,
spreadRadius: 0,
)
]);
本文,我們探索了 Flutter 繪制中鱼响,如何用 Canvas 繪制復雜的陰影樣式鸣剪。期間通過 BoxDecoration 在源碼中的繪制邏輯,發(fā)現(xiàn)其本質是通過模糊遮罩來實現(xiàn)陰影效果。并借此思路筐骇,對 Path 的陰影繪制進行加強债鸡,使其可以根據(jù) BoxShadow
列表繪制陰影。那本文就到這里铛纬,以后還會帶來更多關于 Flutter 繪制的知識厌均,歡迎多多關注和支持,下次再見 ~
作者:張風捷特烈
鏈接:https://juejin.cn/post/7194250504518500409