Flutter-水印偏移
项目中可能会对水印进行应用偏移效果,为此针对水印自绘再进行一次优化。
方案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,
)
),
],
),
);
}
}
效果:
可以看到,偏移是偏移了,但是最左边的并没有开始偏移,并没有达到整体偏移的效果。
方案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,
),
),
),
效果:
整体上是达到了偏移效果,但是最右边出现了空白,效果依旧有所欠缺。原因是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,
),
)
),
);
},
)
),
可以看到基本上实现了我们的期望效果。需要注意的是由于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,
),
),
);
},
)
),
效果:
控制台提示: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,
),
),
);
},
)
),
效果:
可以看到效果已经达到预期,且没有任何溢出提示。但是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,
),
);
},
)
),
效果
可以看到效果达到了预期,而且也没有额外的开销。应该是比较理想的方案。
总结
- 方案1是基础的效果,并不理想;方案3和5虽然都能达到理想的效果,但是却伴随额外的开销;方案6是效果的最佳答案。
- 通过这些尝试,也能更进一步理解FittedBox、SingleChileScrollView、UnconstrainedBox、OverflowBox的各个属性,巩固所学知识。
转载自:https://juejin.cn/post/7224927106131116087