前言:
这是我参与8月更文挑战的第 7 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战
,我准备在本月挑选 31
个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录
的重要素材。希望可以坚持下去,你的支持将是我最大的动力~
一、认识 Opacity 组件
Opacity
组件定义在框架中的 widgets/basic.dart
中,说明它是一个比较基础的组件。其作用是:将一个组件
以指定透明度 opacity
进行透明处理
。
1. Opacity 基本信息
下面是 Opacity
组件类的定义
和 构造方法
,可以看出它继承自 SingleChildRenderObjectWidget
。实例化时必须传入 opacity
入参,还可以传入一个 child
组件。
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 组件
的使用方式到这里就介绍完毕,虽然是个简单的小组件,但通过其源码实现,我们还是能学到一点东西的。那本文到这里就结束了,谢谢观看,明天见~