11

Flutter 组件 | Opacity 透明度

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI0NjU3MDA4NQ%3D%3D&%3Bmid=2247484520&%3Bidx=1&%3Bsn=fa7eb182f972d3dd414354c655441b0d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、认识 Opacity 组件

1. Opacity 基本信息

Opacity 作为一个 SingleChildRenderObjectWidget ,说明其可以传入一个子组件,且需要承担 创建和更新 RenderObject 的任务。其功能是由 RenderOpacity 对象在绘制中添加一个 OpacityLayer 的透明度层实现的。

IzaaeuV.png!mobile

Opacity 组件功能很单一,但将 widget 透明还有其他的手段,如   颜色的 alpha 值 。那什么时候需要使用 Opacity ,什么时候不需要呢? Opacity 组件究竟会带来什么样的负担?就通过本文来看一下吧。

2.Opacity 的使用

Opacity 组件的用法非常简单,只要指定 opacity 即可,下面是 0.1 ~ 0.8 透明度效果。

jIJzIb3.png!mobile

class OpacityTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 10,
      runSpacing: 20,
      children: [0.1,0.2,0.30.40.5,0.60.70.8]
          .map((opacity) => Opacity(
                opacity: opacity,
                child: Image.asset(
                  'assets/images/icon_head.webp',
                  width: 80,
                  height: 80,
                ),
              ))
          .toList(),
    );
  }
}

3.基于颜色的透明度

对于 图片的透明度 ,我们可以使用 color + colorBlendMode 实现,效果如下。

mmYveyQ.png!mobile

class OpacityTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 10,
      runSpacing: 20,
      children: [0.1,0.2,0.30.40.5,0.60.70.8]
          .map((opacity) =>
              Image.asset(
                  'assets/images/icon_head.webp',
                  width: 80,
                  height: 80,
                  color: Color.fromRGBO(255255255, opacity),
                  colorBlendMode: BlendMode.modulate
                ),
              )
          .toList(),
    );
  }
}

那这两种方式有什么差异呢?在此之前,先看一下 Opacity 的源码是如何实现的。

二、 Opacity 的源码实现

1. Opacity 组件属性

最主要的属性是 double 型的 opacity ,通过断言可以看出 opacity 不可为空,且取值范围在 [0.0~1.0] 之间。

vYj2Ujv.png!mobile

2.Opacity 维护的 RenderObject

作为 RenderObjectWidget 一族,有着创建和维护 RenderObject 的使命。 Opacity#createRenderObject 返回的是 RenderOpacity ,也就是说透明化的功能,是由 RenderOpacity 类对象实现的。

jmAB3y3.png!mobile

3.认识 RenderOpacity

RenderOpacityRenderObject 的后代。在构造时会根据传入的 opacity_alpha 成员进行初始化。

7juQzqe.png!mobile

getAlphaFromOpacityColor 类的静态方法,作用就是将 0~1opacity 转换为 0~255alpha 值。

---->[Color]----
static int getAlphaFromOpacity(double opacity) {
  assert(opacity != null); // ignore: unnecessary_null_comparison
  return (opacity.clamp(0.01.0) * 255).round();
}

4.RenderOpacity#paint 方法

如下是 RenderOpacity#paint 的逻辑。当 child 非空时,如果 _alpha = 0 就什么都不需要画。如果 _alpha = 255 ,则直接绘制 child 。这里可以看出,如果想实现一个组件的显隐,使用 Opacity 是比较好的。虽然对于 children 中的 Widget 列表的显示与否, if 是永远的神,但对于 child 的单一组件是否显隐, Opacity 要更好些,如果不显示,透明度设为 0 ,是什么都不画的,组件的占位依旧保持,而用 if 需要在 else 给占位组件,而无论什么组件,都没有透明度为 0 时的 Opacity 更纯净。

I3MB7b2.png!mobile

最后,如果有透明度时,会通过 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 = 77oldLayer = null

qyYjUzy.png!mobile

PaintingContext#pushOpacity 入栈后,如果 oldLayer 为空,就会创建 OpacityLayer ,并为其设置 alphaoffset ,然后执行 pushLayer 方法。

IZ7Jfe2.png!mobile

childLayer 有孩子的话,会移除其孩子,这里刚创建的 OpacityLayer ,没有孩子。

6z2uaay.png!mobile

然后通过 appendlayer 将过程创建的 OpacityLayer 添加到 _containerLayer 中。

jQVvyyE.png!mobile

2.层的合成

drawFrame 中的 flushLayoutflushCompositingBitsflushPaint 完成后,此时屏幕并不会出现内容。还需要通过 renderView.compositeFrame() 将数据传送给 GPU

Y3IZBnN.png!mobile

之前的方法的处理完后,元素树 和 渲染树都已经形成。作为渲染树最顶层的节点, renderView 对象会通过 compositeFrame 来合成帧。 RenderObject 对象中有   Layer 成员,如下   renderViewlayerTransformLayer ,它也有 child 和 parent 属性。

IZVJFj.png!mobile

上图的层中只有两个节点, TransformLayer 和它的孩子 OpacityLayer 。然后执行 layer!.buildScene(builder) ,创建 ui.Scene 对象。

EJJrAfE.png!mobile

其中 addToScene 方法会让孩子执行 addToScene ,这样所有的 Layer 就添加到了 Scene 中。

JNZfYbF.png!mobile

3. ui.Scene  的使用

通过 renderView 中持有的 TransformLayer 调用 buildScene 方法,就可以将其下所以的 Layer 合并到一个 Scene 中,并返回 ui.Scene 对象,最后通过 _window.render 方法进行渲染 ui.Scene 对象。

MjUza2q.png!mobile

渲染的方法是一个平台的 native 方法。

IRfMVfY.png!mobile

四、再看 Opacity 的优劣

Opacity 组件功能实现的方式是直接用 OpacityLayer 完成的,这样在合成帧的时候就会多一个层,会有些许的昂贵。但是 存在就有存在的价值 ,不能因为结婚贵,就不结婚。使用颜色进行透明是有 局限性 的,有些透明度必须要 Opacity 组件 。如果是 ImageColor 需要透明,那么 杀鸡焉用牛刀 ,颜色透明足以。

但当整个 item 或是整个页面需要透明化,使用 Opacity 组件就会很方便和简洁。不然你需要对所有关于颜色的地方进行透明度处理。这样处理会让代码很复杂,可读性差,而且这样的操作可能会让一些对象无法保持 const 常量,比如一个 const Text("XXX") ,为了让其颜色透明,需要使用 Texstyle 定义颜色。而这个透明度需要运行时获取,那么这个 Text 无法保持 const,还多出样式处理的代码。

Layer 的层级结构设计出来也是为了使用的,多一个 Layer ,也就是在合成时多一些方法的出入栈,并没有想象中的那么重。所以 Opacity 该用的时候还是要用的,但对于单一的图片、颜色的透明处理,能省则省,就不需要用了。

jayYFzF.png!mobile

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 实现的,所以这样反而会弄巧成拙。

ANnEZff.png!mobile

当你了解了这些,对 Opacity 这个组件就会有全面的认识,好与不好,只是使用的场景的优劣,工具本无罪。在使用的时候留意一下即可,问一下,“我是否可以很方便的通过颜色的透明度实现透明效果”。能则不用 Opacity ,反则用之。另外不要忘记 Opacity 对于单一组件显隐的优势。那本文就到这里,谢谢观看 ~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK