公司业务需要做可以超出屏幕的水印效果,但是网上找的一些都不符合条件,所以决定自己来做一个。
因为flutter普通控件是不允许超出屏幕的,如果用listview这些布局会在屏幕内平铺无法超出屏幕,如果使用其他控件强行超出屏幕会报警告。
所以转换思路,使用一个纵向的SingleChildScrollView来保证纵向可以超出屏幕,然后再在里面添加一行一行的横向SingleChildScrollView来保证横向可以超出屏幕。
/**
* 水印样式
* rowCount: 当前屏幕宽度中 展示多少列水印
* columnCount: 当前屏幕高度中,展示多少行水印
* text: 水印展示的文字
* textStyle: 文字的样式
*/
class DisableScreenshotsWaterMark extends StatelessWidget {
final int rowCount; //水印需要展示的列数
final int columnCount;//水印需要展示的行数
final String text;//水印文字
final TextStyle? textStyle;//水印文字样式
final BuildContext parentContext;//父控件上下文
final ScrollController? scrollController;//纵向scroll初始化控制器
final bool layOutBottomNav;//是否需要考虑底部导航
const DisableScreenshotsWaterMark(
this.text,
this.parentContext, {
Key? key,
required this.rowCount,
required this.columnCount,
this.textStyle,
this.scrollController,
this.layOutBottomNav = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return IgnorePointer(//忽略交互
child: Container(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
scrollDirection: Axis.vertical,//最外层的一定是纵向的scroll
child: createColumnWidgets(context),
controller: scrollController ?? ScrollController(),
//根据有无外部传入进行初始化,可以解决想要初始纵向偏移量的问题
//展示图中就是传入了一个ScrollController(initialScrollOffset: 20),否则顶部会留白
);
},
),
),
);
}
//创建每一行的widget
List createRowWidgets(BuildContext context) {
//纵向水印最后一列宽度,根据需要自行更改
final double lastWidth = 35.0;
//纵向水印非最后一列的宽度,根据最后一列宽度自动计算
//这样计算是参照了网上说的精度丢失问题
final double normalWidth =
((screenWidth(context) * 100).floor() - (lastWidth * 100).floor()) / 100 / (rowCount - 1);
List list = [];
for (var i = 0; i < rowCount; i++) {
//根据传入的列数,添加旋转效果的控件,实现某一行的效果
final widget = Transform.rotate(
angle: 611 / pi,//角度,根据需要自行修改
child: Container(
color: Colors.transparent,//注意要透明
clipBehavior: Clip.none,
padding: EdgeInsets.all(12),//每个水印widget内边距,根据需要调整
alignment: Alignment.topLeft,
width: normalWidth,
child: Text(
text,
style: textStyle ??
TextStyle(
color: Color.fromRGBO(150, 150, 150, 0.25),
fontSize: 12,
decoration: TextDecoration.none,
fontWeight: FontWeight.w800,
height: 1.0,
),
),
),
);
list.add(widget);
}
return list;
}
//创建每一列的widget
Column createColumnWidgets(BuildContext context) {
//最后一行高度,因为要保证最后一行有个显示一半的效果才故意加了一个这个高度,如果不需要该效果,可以不要这个高度,让每一行都等高,自然布局
final double lastHeight = layOutBottomNav ? 40.0 : 0;
MediaQueryData mediaQueryData = MediaQuery.of(this.parentContext);
double topNavHeight = mediaQueryData.padding.top + 56;
double bottomNavHeight = layOutBottomNav ? mediaQueryData.padding.bottom + 56 : 0;
//水印显示区域高度
double waterMarkAreaHeight = ((screenHeight(context) * 100).floor() -
(topNavHeight * 100).floor() -
(bottomNavHeight * 100).floor()) /
100;
//自动计算非最后一行高度
double normalHeight =
((waterMarkAreaHeight * 100).floor() - (lastHeight * 100).floor()) / 100 / (columnCount - 1);
List list = [];
//根据传入的行数创建每一行
for (var i = 0; i < columnCount; i++) {
final widget = Container(
height: normalHeight,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: createRowWidgets(context),
),
);
},
),
);
list.add(widget);
}
Column column = Column(
children: list,
);
return column;
}
}
这套代码在iOS上没有问题,但是在某些小屏幕安卓机上,水印文本超过3行时可能会有文本被截断的问题,如遇到需要进行调整。
使用的时候只需要在需要添加水印的widegt外部包一个stack然后把该水印widget添加到stack最后即可
使用样例
return Scaffold(
appBar: _getAppBar(),
body: Stack(
children: [
GestureDetector(
onTap: () {
},
child: Container(),
),
DisableScreenshotsWaterMark(
AppData.instance.account!.waterMarkStr,
context,
rowCount: 4,
columnCount: 10,
scrollController: ScrollController(initialScrollOffset: 20),
layOutBottomNav: true,
)
: Container(),
],
),
);