likes
comments
collection
share

Flutter-水印偏移

作者站长头像
站长
· 阅读数 4

项目中可能会对水印进行应用偏移效果,为此针对水印自绘再进行一次优化。

方案1:

class TestTextWaterState extends State<TestTextWater>{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: const Text("SSL Water Mark"),
      ),
      body:
        Stack(
          children: [
            Center(
              child: ElevatedButton(onPressed: (){
                debugPrint("Chick Button");
              }, child: const Text("Chick Button")
              ),
            ),
            IgnorePointer(
              child: SSLWaterMark(
                painter: TextWaterMarkPainter(
                  text: "SSL white monkey",
                  textStyle: const TextStyle(color: Colors.purple,fontSize: 5),
                  padding: const EdgeInsets.only(right: 50),
                  rotate: -10
                ),
                repeat: ImageRepeat.repeat,
              )
            ),
          ],
        ),
    );
  }
}

效果: Flutter-水印偏移 可以看到,偏移是偏移了,但是最左边的并没有开始偏移,并没有达到整体偏移的效果。

方案2

使用Transform组件对Widget进行偏移。 修改代码:

IgnorePointer(
  child: Transform.translate(
    offset: const Offset(- 50,0),
    child: SSLWaterMark(
      painter: TextWaterMarkPainter(
          text: "SSL white monkey",
          textStyle: const TextStyle(color: Colors.purple,fontSize: 5),
          // padding: const EdgeInsets.only(right: 50),
          rotate: -10
      ),
      repeat: ImageRepeat.repeat,
    ),
  ),
),

效果: Flutter-水印偏移 整体上是达到了偏移效果,但是最右边出现了空白,效果依旧有所欠缺。原因是WaterMark占用空间本来和屏幕等宽,所以绘制时的区域也就和屏幕一样大,而Transform.translate的作用相当于是在绘制时将绘制点做平移了50dpt,所以右边出现了空白。

如果这样的话,那如果让WaterMark的绘制区域超过屏幕宽度30像素,平移后是不是就可以了,理论上应该可行,但是WaterMark时通过DecoratedBox去绘制背景,我们不能去修改DecoratedBox的绘制逻辑,所以方案不可取。

方案3:

使用可滚动组件加大应用偏移区。 大多数组件的绘制区和自身布局大小是相同的,那么强制让WaterMark的宽度超出屏幕宽度50dpt是不是也可以?理论上似乎可行,滚动组件的原理就是这样实现的,那么肯定有一个方法能强制指定WaterMark的宽度比屏幕宽度大50,然后用SingleChildScrollView包裹:

IgnorePointer(
  child: LayoutBuilder(builder: (context, constraints){

   return SingleChildScrollView(
     scrollDirection: Axis.horizontal,
     child: Transform.translate(
       offset: const Offset(- 50,0),
       child: SizedBox(
         width: constraints.maxWidth + 50,
         height: constraints.maxHeight,
         child: SSLWaterMark(
           painter: TextWaterMarkPainter(
               text: "SSL white monkey",
               textStyle: const TextStyle(color: Colors.purple,fontSize: 5),
               // padding: const EdgeInsets.only(right: 50),
               rotate: -10
           ),
           repeat: ImageRepeat.repeat,
         ),
       )
     ),
   );
  },
  )
),

Flutter-水印偏移 可以看到基本上实现了我们的期望效果。需要注意的是由于SingleChildScrollView被IgnorePointer包裹,所以它不能接收事件,不会受用户滑动。

缺点:SingleChildScrollView内部需要创建Scrollable和Viewport对象,在这个场景下SingleChildScrollView是不会响应事件的,创建Scrollable属于多余的开销。

方案4:

先通过UnconstrainedBox取消组件对子组件大小的约束,然后通过SizedBox指定WaterMark宽比屏幕长30来实现。

IgnorePointer(
  child: LayoutBuilder(builder: (context, constraints){
      return UnconstrainedBox(
        alignment: Alignment.topRight,
        child: SizedBox(
          width: constraints.maxWidth + 30,
          height: constraints.maxHeight,
          child: SSLWaterMark(
            painter: TextWaterMarkPainter(
                text: "SSL white monkey",
                textStyle: const TextStyle(color: Colors.purple,fontSize: 5),
                // padding: const EdgeInsets.only(right: 50),
                rotate: -10
            ),
            repeat: ImageRepeat.repeat,
          ),
        ),
      );
  },
  )
),

效果: Flutter-水印偏移

控制台提示:A RenderConstraintsTransformBox overflowed by 30 pixels on the left.

左边出现了溢出提示条,这是因为UnconstrainedBox虽然其子组件布局时可以取消约束(子组件可以为无限大),但是UnconstrainedBox自身是受其父组件约束的,所以当UnconstrainedBox随其子组件变大后,如果UnconstrainedBox的大小超过其父组件大小时,就会导致溢出。 如果没有溢出,效果就已经失效了,超出的宽度就会在父组件的左边界之外,从而就实现了期望的效果,大家需要注意的是在Release模式下是不会有绘制溢出提示条的,溢出条绘制逻辑是在assert函数中,比如:

// Display the overflow indicator.
assert(() {
  paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect);
  return true;
}());

所以在Release模式下上面的代码也可以实现预期的效果,但是应该遵守代码规则,不应该使用这种方法。既然有提示,则代表UnconstrainedBox子元素溢出不是预期的行为。 解决思路:在取消约束的同时不要让组件大小超出父组件的空间即可。之前有使用FittedBox,它可以取消父组件对子组件的约束并同时让其子组件适配FittedBox父组件的大小,正符合我们的需求。

方案5:

IgnorePointer(
  child: LayoutBuilder(builder: (context, constraints){
    return FittedBox(
      alignment: Alignment.topRight,
      fit: BoxFit.none,
      child: SizedBox(
        width: constraints.maxWidth + 50,
        height: constraints.maxHeight,
        child: SSLWaterMark(
          painter: TextWaterMarkPainter(
              text: "SSL white monkey",
              textStyle: const TextStyle(color: Colors.purple,fontSize: 5),
              // padding: const EdgeInsets.only(right: 50),
              rotate: -10
          ),
          repeat: ImageRepeat.repeat,
        ),
      ),
    );
    },
  )
),

效果: Flutter-水印偏移 可以看到效果已经达到预期,且没有任何溢出提示。但是FittedBox的主要使用场景是对子组件进行一些缩放、拉伸等以适配父组件的空间,而本例中并没有用到这个功能,适配方式指定的:BoxFit.none,还是有点杀鸡用牛刀。

方案6:

使用OverflowBox来应用偏移。 OverflowBox和UnconstrainedBox相同的是可以取消父组件对子组件的约束,但不同的是OverflowBox自身的大小不会随子组件大小而变化,它的大小只取决于其父组件的约束(约束为constraints.binggest),即在满足父组件约束的前提下会尽可能的大。封装一个TrabslateWithExpandedPaintingArea组件来包裹WaterMark组件:

class SSLTranslateWithExpandedPaintingArea extends StatelessWidget{
  final Widget? child;
  final Offset offset;
  final Clip clipBehavior;
  const SSLTranslateWithExpandedPaintingArea({
    Key? key,
    required this.offset,
    this.clipBehavior = Clip.none,
    this.child
  }):super(key: key);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return LayoutBuilder(
        builder: (context, constraints){
          final dx = offset.dx.abs();
          final dy = offset.dy.abs();

          Widget widget = OverflowBox(
            //平移多少,则子组件相应轴的长度增加多少
            minWidth: constraints.minWidth + dx,
            minHeight: constraints.minHeight + dy,
            maxWidth: constraints.maxWidth + dx,
            maxHeight: constraints.maxHeight + dy,
            alignment: Alignment(
              //不同方向的平移,要指定不同的对其方式
              offset.dx <= 0 ? 1 : -1,
              offset.dy <= 0 ? 1 : -1,
            ),
            child: child,
          );
          if (clipBehavior != Clip.none){
            widget = ClipRect(clipBehavior: clipBehavior, child: widget,);
          }
          return widget;
        }
    );
  }
}

需要注意:

  • 会根据用户指定的偏移来动态给子组件宽高增加对应的值
  • 需要根据用户指定的偏移来动态调整OverflowBox的对齐方式,比如要向左平移时,OverflowBox就必须右对齐,因为右对齐超出父容器部分会在左边界之外。
  • 超出边界之外的内容默认会显示,测试中水印组件大小和屏幕剩余显示空间一样大,所以超出会不会显示,但是如果水印指定小于屏幕大小的尺寸,就可以看到超出之后的内容,因此,定义了一个裁剪的配置参数,可以根据实际情况决定是否进行裁剪。

测试:

IgnorePointer(
  child: LayoutBuilder(builder: (context, constraints){
    return SSLTranslateWithExpandedPaintingArea(
      offset: const Offset(-50,0),
      child: SSLWaterMark(
        painter: TextWaterMarkPainter(
            text: "SSL white monkey",
            textStyle: const TextStyle(color: Colors.purple,fontSize: 5),
            // padding: const EdgeInsets.only(right: 50),
            rotate: -10
        ),
        repeat: ImageRepeat.repeat,
      ),
    );
  },
 )
),

效果 Flutter-水印偏移

可以看到效果达到了预期,而且也没有额外的开销。应该是比较理想的方案。

总结

  1. 方案1是基础的效果,并不理想;方案3和5虽然都能达到理想的效果,但是却伴随额外的开销;方案6是效果的最佳答案。
  2. 通过这些尝试,也能更进一步理解FittedBox、SingleChileScrollView、UnconstrainedBox、OverflowBox的各个属性,巩固所学知识。