前言:
這是我參與8月更文挑戰的第 7 天,活動詳情檢視:8月更文挑戰。為應掘金的八月更文挑戰
,我準備在本月挑選 31
個以前沒有介紹過的元件,進行全面分析和屬性介紹。這些文章將來會作為 Flutter 元件集錄
的重要素材。希望可以堅持下去,你的支援將是我最大的動力~
- 1.【Flutter 元件集錄】NotificationListener| 8月更文挑戰
- 2.【Flutter 元件集錄】Dismissible| 8月更文挑戰
- 3.【Flutter 元件集錄】Switch 是怎樣煉成的| 8月更文挑戰
- 4.【Flutter 元件集錄】Scrollbar| 8月更文挑戰
- 5.【Flutter 元件集錄】ClipPath| 8月更文挑戰
- 6.【Flutter 元件集錄】CupertinoActivityIndicator| 8月更文挑戰
- 7.【Flutter 元件集錄】Opacity| 8月更文挑戰
[本文]
一、認識 Opacity 元件
Opacity
元件定義在框架中的 widgets/basic.dart
中,說明它是一個比較基礎的元件。其作用是:將一個元件
以指定透明度 opacity
進行透明處理
。
1. Opacity 基本資訊
下面是 Opacity
元件類的定義
和 構造方法
,可以看出它繼承自 SingleChildRenderObjectWidget
。例項化時必須傳入 opacity
入參,還可以傳入一個 chuild
元件。
Opacity
元件功能很單一,用起來也非常簡單。另外,將 widget
透明還有其他的手段,比如設定顏色的 alpha 值
。那使用Opacity
元件有什麼優劣,又有什麼必要性呢,何時該用,何時不該用呢?本文將從原始碼的角度去重新審視 Opacity
元件,讓你認識到一個更全面的 Opacity
。
2.Opacity 的使用
Opacity 元件的用法非常簡單,只要指定 opacity
即可,下面是 0.1 ~ 0.8
間隔 0.1
的透明度效果。
class OpacityTest extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 10,
runSpacing: 20,
children: [0.1,0.2,0.3, 0.4, 0.5,0.6, 0.7, 0.8]
.map((opacity) => Opacity(
opacity: opacity,
child: Image.asset(
'assets/images/icon_head.webp',
width: 80,
height: 80,
),
))
.toList(),
);
}
}
複製程式碼
3.基於顏色的透明度
對於 圖片的透明度
,我們可以使用 color + colorBlendMode
實現,效果和 Opacity
是一樣的。
class OpacityTest extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 10,
runSpacing: 20,
children: [0.1,0.2,0.3, 0.4, 0.5,0.6, 0.7, 0.8]
.map((opacity) =>
Image.asset(
'assets/images/icon_head.webp',
width: 80,
height: 80,
color: Color.fromRGBO(255, 255, 255, opacity),
colorBlendMode: BlendMode.modulate
),
)
.toList(),
);
}
}
複製程式碼
那這兩種方式有什麼差異呢?在此之前,先看一下 Opacity
的原始碼是如何實現的。
二、 Opacity 的原始碼實現
1. Opacity 元件屬性
Opacity
最主要的屬性是 double
型的 opacity
,可以傳入一個 child
元件。通過斷言可以看出 opacity
不可為空,且取值範圍在 [0.0~1.0]
之間。
2.Opacity 維護的 RenderObject
Opacity
繼承自 SingleChildRenderObjectWidget
,作為 RenderObjectWidget
一族,有著建立和維護 RenderObject
的使命。
其中Opacity#createRenderObject
返回的是 RenderOpacity
,也就是說透明化功能的具體實現,是由 RenderOpacity
類物件完成的。Opacity
最為元件,只是起到一個 配置作用
。
3.認識 RenderOpacity
RenderOpacity
是 RenderObject
的後代,也就是渲染物件,它承擔著佈局
和繪製
的任務。在構造方法
中時會根據傳入的 opacity
對 _alpha
成員進行初始化。
其中getAlphaFromOpacity
是 Color
類的靜態方法,作用就是將 0 ~ 1
的 opacity
轉換為 0 ~ 255
的 alpha
值。
---->[Color#getAlphaFromOpacity]----
static int getAlphaFromOpacity(double opacity) {
assert(opacity != null); // ignore: unnecessary_null_comparison
return (opacity.clamp(0.0, 1.0) * 255).round();
}
複製程式碼
4.RenderOpacity#paint 方法
RenderOpacity
最重要的是繪製的處理,如下是 RenderOpacity#paint
的邏輯。可以看出,當 child
非空時,如果 _alpha = 0
就什麼都不需要畫,直接返回。如果 _alpha = 255
,則直接繪製 child
。
從這裡可以瞭解到,通過 Opacity
元件,設定為 0
透明,可以既保留 child
元件的佔位,又可以避免繪製 child
,是一種非常好的隱藏元件
的方式。如果不需要佔位,通過 if 是更好的選擇,如果需要佔位 Opacity
會略勝一籌。比較 用 if 需要在 else 給佔位元件。
最後,如果有透明度時,會通過 context.pushOpacity
進行處理。那 pushOpacity
方法做了什麼呢?
三、認識 PaintingContext#pushOpacity
1.斷點除錯
下面通過一個最精簡的 demo
來除錯看看執行時的情況。
void main() => runApp(OpacityTest());
class OpacityTest extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Opacity(
opacity: 0.3,
child: Image.asset(
'assets/images/icon_head.webp',
width: 80,
height: 80,
));
}
}
複製程式碼
斷點資訊如下,左側棧幀顯示繪製一幀到RenderOpacity#paint
時,方法的入棧情況。RenderObject 有一個 ContainerLayer 型的 _layer
屬性,預設為 null。下一步將執行 context.pushOpacity
。這裡的 context
物件型別為 PaintingContext
,入參的 _alpha = 77
,oldLayer = null
。
PaintingContext#pushOpacity
入棧後,如果 oldLayer
為空,就會建立 OpacityLayer
,併為其設定 alpha
和 offset
,然後執行 pushLayer
方法。
childLayer
有孩子的話,會移除其孩子,這裡剛建立的 OpacityLayer
,沒有孩子,不會走入 if 程式碼塊。
然後通過 appendlayer
將過程建立的 OpacityLayer
新增到 _containerLayer
中。
2.層的合成
當 drawFrame
中的 flushLayout
、flushCompositingBits
、flushPaint
完成後,此時螢幕並不會出現內容。還需要通過 renderView.compositeFrame()
將資料傳送給 GPU
。
之前的方法的處理完後,元素樹
和 渲染樹
都已經形成。作為渲染樹最頂層的節點,renderView
物件會通過 compositeFrame
來合成幀。RenderObject
物件中有 Layer
成員,如下 renderView
的 layer
是 TransformLayer
,它也有 child
和 parent
屬性,形成樹狀結構。
上圖的層中只有兩個節點,TransformLayer
和它的孩子 OpacityLayer
。然後執行 layer!.buildScene(builder)
,建立 ui.Scene
物件。
其中 addToScene
方法會讓孩子執行 addToScene
,這樣所有的 Layer
就新增到了 Scene
中。
3. ui.Scene
的用途
通過 renderView
中持有的 TransformLayer
呼叫 buildScene
方法,就可以將其下所以的 Layer 合併到一個 Scene
中,並返回 ui.Scene
物件,最後通過 _window.render
方法進行渲染 ui.Scene
物件。
渲染的方法是一個平臺的 native
方法。總的來看,使用 Opacity
元件,最終是通過一個 OpacityLayer
層實現的透明功能。
四、再看 Opacity 的優劣
Opacity 元件
功能實現的方式是直接用 OpacityLayer
完成的,這樣在合成幀的時候就會多一個層,會有些許的昂貴,但是存在就有存在的價值
。使用顏色進行透明是有侷限性
的,有些透明度必須要 Opacity 元件
。 如果是 Image
或 Color
需要透明,那麼殺雞焉用牛刀
,顏色透明足以。
但當整個 item
或是整個頁面需要透明化,使用 Opacity
元件就會很方便和簡潔。不然你需要對所有關於顏色的地方進行透明度處理。這樣處理會讓程式碼很複雜,可讀性差,而且這樣的操作可能會讓一些物件無法保持 const 常量,比如一個 const Text("XXX")
,為了讓其顏色透明,需要使用 Texstyle
定義顏色。而這個透明度需要執行時獲取,那麼這個 Text
無法保持 const,還多出樣式處理的程式碼。
Layer
的層級結構設計出來也是為了使用的,多一個 Layer
,也就是在合成時多一些方法的出入棧,並沒有想象中的那麼重。所以 Opacity
該用的時候還是要用的,但對於單一的圖片、顏色的透明處理,能省則省,就不需要用了。
Widget buildItem() {
return Container(
height: 80,
margin: const EdgeInsets.all(10),
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(color: Colors.red),
borderRadius: BorderRadius.circular(10)
),
child: Row(
children: [
const SizedBox(width: 15),
Image.asset(
'assets/images/icon_head.webp',
width: 60,
height: 60,
),
const SizedBox(width: 15,),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text( '張風捷特烈',
style: TextStyle(fontSize: 16),
),
const Text( '程式設計之王',
style: TextStyle(fontSize: 14,color: Colors.grey),
),
],
),
const Spacer(),
const Icon(Icons.ac_unit_outlined, color: Colors.blue , size: 24),
const SizedBox(width: 5,),
const Icon(Icons.alt_route_rounded, color: Colors.blue , size: 24,),
const SizedBox(width: 20,),
],
),
);
}
複製程式碼
有人也許會靈光一現,不是有個 ColorFiltered
嗎,指定 ColorFilter
透明不就行了嗎。當你瞄一眼 ColorFiltered
的原始碼,就會發現,它的原理也是通過新增一個 ColorFilterLayer
實現的,所以這樣反而會弄巧成拙。
當你瞭解了這些,對 Opacity
這個元件就會有全面的認識,好與不好,只是使用的場景的優劣,工具本無罪。在使用時留意一下即可,問一下,“我是否可以很方便的通過顏色的透明度實現透明效果”
。能則不用 Opacity
,反則用之。另外不要忘記 Opacity
對於單一元件顯隱的優勢。那本文就到這裡,謝謝觀看 ~
Opacity 元件
的使用方式到這裡就介紹完畢,雖然是個簡單的小元件,但通過其原始碼實現,我們還是能學到一點東西的。那本文到這裡就結束了,謝謝觀看,明天見~