如下图所示,最近经过群友的问题在 codepen.io 上看到了一个文本「抽动」的动画完成,看起来就像是生活中常见的「霓虹灯招牌」毛病时的「抽动」作用,而本篇的目标经过「抄袭」这个完成,协助咱们理解 Flutter 里的一些完成小技巧。
这个作用在 codepen 上是经过 CSS 完成的,完成思路 codepen 上的 Glitch Walkthrough 大致有提示,可是 Flutter 没有强壮的 CSS,那么如何将它「复刻」到 Flutter 上便是本篇的中心要点。
不得不说 CSS 很强壮,要在 Flutter 上完成相似的作用仍是比较「折腾」。
而要在 Flutter 上完成相似 Glitch Walkthrough 的作用,大致上咱们需求处理:
- 相似霓虹灯作用的文本
- 文本内容撕裂的作用
- 文本变形闪烁的作用
那么接下来咱们就依照这个流程来完成一个 Flutter 上的 Glitch Walkthrough 。
霓虹灯文本
这一步其实相对简略,Flutter 的 TextStyle
供给了 shadows
装备,经过它能够快速完成一个「会发光」的文本。
咱们这儿经过两个 Shadow
来完成「发光」的视觉作用,中心便是运用 Shadow
的 blurRadius
来让布景出现必定程度的含糊发散,然后两个 Shadow
构成不一样的色彩深度和发散作用,然后达到看起来「发亮」的作用。
如下图是没有填充文本色彩时
Shadow
的作用。
最终,如下代码所示,咱们只需求经过 foreground
给文本弥补下色彩,就能够看到如下图所示的相似「霓虹灯」作用的文本。
当然这儿你不想用
foreground
,只用简略的color
也能够。
Text(
widget.text,
style:TextStyle(
fontSize:48,
fontWeight:FontWeight.bold,
foreground:Paint()
..style=PaintingStyle.fill
..strokeWidth=5
..color=Colors.white,
shadows: [
Shadow(
blurRadius:10,
color:Colors.white,
offset:Offset(0,0),
),
Shadow(
blurRadius:20,
color:Colors.white30,
offset:Offset(0,0),
),
],
),
)
这儿提个题外话,其实相似的思路用在图片上也能够完成「发光」的作用,如下代码所示,经过 Stack 嵌套两个 Image
,然后中心经过 BackdropFilter
的 ImageFilter
做一层含糊,让底下的图片含糊后发散发生相似「发光」的作用。
varchild=Image.asset(
'static/test_logo.png',
width:250,
);
returnStack(
children: [
child,
Positioned.fill(
child:BackdropFilter(
filter:ImageFilter.blur(
sigmaX:blurRadius,
sigmaY:blurRadius,
),
child:Container(color:Colors.transparent),
),
),
child,
],
)
);
如下图所示,图片终究能够经过自己的色彩发生相似「发光」的作用,当然这部分仅仅额定的拓宽内容,和咱们要完成的作用无关。
文本撕裂
这部分能够说是需求作用的中心,这儿咱们需求用到 ClipPath
和 Polygon
,经过 Polygon
来完成随机的多边形途径,然后运用 ClipPath
对文本内容进行随机的途径裁剪。
尽管说用 Polygon
, 可是 Flutter 官方并没有直接供给相似前端 CSS 的 Polygon
多边形 API 支撑,可是社区总有「好心人」,咱们能够直接运用 Flutter 上相似的第三方库: polygon: ^0.1.0
。
简略说
Polygon
便是依照 step 对Path
的moveTo
和quadraticBezierTo
等 API 进行了封装。
Flutter 上的 Polygon
取值规模是 -1 ~ 1 ,也便是依照比例决议方位,比方 – 1 便是起始点, 1 便是最大宽高, 更具体如下面的代码所示,这儿运用 Polygon
添加了三个点,终究这三个点构成的 Path 会制作出一个三角形。
List<Offset>generatePoint() {
List<Offset>points=[];
points.add(Offset(-1,-1));
points.add(Offset(-1,0));
points.add(Offset(0,-1));
returnpoints;
}
如下代码所示,那如果如果 point 的数量多了,就能够构成一系列不规则的形状,比方下面代码随机添加了 60 个点的方位,能够看到此时屏幕上的白色 Container
被裁剪成「杂乱」的形状。
List<Offset>generatePoint() {
List<Offset>points=[];
points.add(Offset(-1.00,-0.76));
points.add(Offset(0.06,-0.76));
points.add(Offset(0.06,-0.48));
points.add(Offset(-0.50,-0.48));
points.add(Offset(-0.50,0.72));
points.add(Offset(-0.38,0.72));
points.add(Offset(-0.38,-1.00));
points.add(Offset(0.06,-1.00));
points.add(Offset(0.06,0.67));
points.add(Offset(0.84,0.67));
points.add(Offset(0.84,0.63));
points.add(Offset(0.39,0.63));
points.add(Offset(0.39,-0.42));
points.add(Offset(0.56,-0.42));
points.add(Offset(0.56,0.30));
points.add(Offset(0.37,0.30));
points.add(Offset(0.37,0.32));
points.add(Offset(0.54,0.32));
points.add(Offset(0.54,-0.09));
points.add(Offset(0.70,-0.09));
points.add(Offset(0.70,-0.48));
points.add(Offset(0.94,-0.48));
points.add(Offset(0.94,-0.43));
points.add(Offset(0.67,-0.43));
points.add(Offset(0.67,-0.31));
points.add(Offset(0.08,-0.31));
points.add(Offset(0.08,0.78));
points.add(Offset(-0.40,0.78));
points.add(Offset(-0.40,0.15));
points.add(Offset(0.65,0.15));
points.add(Offset(0.65,0.00));
points.add(Offset(0.36,0.00));
points.add(Offset(0.36,-0.28));
points.add(Offset(0.24,-0.28));
points.add(Offset(0.24,-0.80));
points.add(Offset(-0.76,-0.80));
points.add(Offset(-0.76,-0.31));
points.add(Offset(0.19,-0.31));
points.add(Offset(0.19,0.13));
points.add(Offset(0.96,0.13));
points.add(Offset(0.96,0.65));
points.add(Offset(-0.80,0.65));
points.add(Offset(-0.80,0.06));
points.add(Offset(0.82,0.06));
points.add(Offset(0.82,0.67));
points.add(Offset(0.60,0.67));
points.add(Offset(0.60,0.65));
points.add(Offset(-0.19,0.65));
returnpoints;
}
如果这时候把白色 Container
换成文本内容,那么咱们就能够如下图所示的作用,看起来像不像一帧状态下文本的「紊乱」作用?后边咱们只需求每次生成一帧这样的 Path ,就能够完成文本动态「撕裂」的需求。
咱们只需求把这个完成做成随机输出,然后每次生成一个
Path
就能够了。
如下代码所示,咱们经过 generatePoint
办法,每次随机生成 60 个点,然后将这些点经过 computePath
转化为 Path,然后继承 CustomClipper
装备到 getClip
办法里,在需求的时候(tear
)对 child 按 Path 进行裁剪。
注意这儿的
i % 2
,为的是让上次的 x 或者 y 能够是同一个方位,在连接上能接连。
classRandomTearingClipperextendsCustomClipper<Path>{
booltear;
RandomTearingClipper(this.tear);
List<Offset>generatePoint() {
List<Offset>points=[];
varx=-1.0;
vary=-1.0;
for(vari=0;i<60;i++) {
if(i%2!=0) {
x=Random().nextDouble()*(Random().nextBool()?-1:1);
}else{
y=Random().nextDouble()*(Random().nextBool()?-1:1);
}
points.add(Offset(x,y));
}
returnpoints;
}
@override
PathgetClip(Sizesize) {
varpoints=generatePoint();
varpolygon=Polygon(points);
if(tear)
returnpolygon.computePath(rect:Offset.zero&size);
else
returnPath()..addRect(Offset.zero&size);
}
@override
boolshouldReclip(RandomTearingClipperoldClipper)=>true;
}
接着,咱们只需求设置一个定期器,然后将前面的「霓虹灯文本」和「毛病裁剪作用」装备到 ClipPath
上,如下图所示,咱们就能够看到文本的随机撕裂作用。
timer=Timer.periodic(Duration(milliseconds:400), (timer) {
tearFunction();
});
returnClipPath(
child:Center(
child:Text(
widget.text,
style:TextStyle(
fontSize:48,
fontWeight:FontWeight.bold,
foreground:Paint()
..style=PaintingStyle.fill
..strokeWidth=1
..color=Colors.white,
shadows: [
Shadow(
blurRadius:10,
color:Colors.white,
offset:Offset(0,0),
),
Shadow(
blurRadius:20,
color:Colors.white30,
offset:Offset(0,0),
),
],
),
),
),
clipper:RandomTearingClipper(tear),
);
此时看起来还不行形象。
变形闪烁
为了达到咱们预期的作用,最终咱们还需求做一些特别处理,比方再完成两个形状、色彩和方位不一样「霓虹灯文本」,为的便是完成「变形和闪烁」的作用替换。
比方如下代码所示,经过 ShaderMask
能够完成一个突变作用的的文本,这是用来在闪烁的时候,供给一个短暂替换和色彩加深的作用。
ShaderMask(
blendMode:BlendMode.srcATop,
shaderCallback: (bounds) {
returnLinearGradient(
colors: [Colors.blue,Colors.green,Colors.red],
stops: [0.0,0.5,1.0],
).createShader(bounds);
},
child:
相似的咱们还能够完成一个「变形」的文本,在之前的白色「霓虹灯」文本基础上添加「斜体」和「色彩变淡」等处理,用来闪烁的时候供给「变形」的作用。
最终咱们再将之前的 ClipPath
添加到它们上面,并添加一个 transform
完成文本四周随意移动的作用支撑,如下图所示,此时的作用已经肉眼可见的接近咱们的需求。
transform:
Matrix4.translationValues(randomPosition(4),randomPosition(4),0),
doublerandomPosition(position) {
returnRandom().nextInt(position).toDouble()*
(Random().nextBool()?-1:1);
}
最终咱们将这几个文本作用用 Stack
组合起来,然后再在定时器里不停去切换「毛病」和「正常」的文本状态,并且随机挑选展现不同的 「毛病」状态。
timer = Timer.periodic(Duration(milliseconds: 400), (timer) {
tearFunction();
});
timer2 = Timer.periodic(Duration(milliseconds: 600), (timer) {
tearFunction();
});
tearFunction() {
count++;
tear = count % 2 == 0;
if (tear == true) {
setState(() {});
Future.delayed(Duration(milliseconds: 150), () {
setState(() {
tear = false;
});
});
}
}
@override
Widget build(BuildContext context) {
var status = Random().nextInt(3);
return Stack(
children: [
if (tear && (status == 1)) renderTearText1(RandomTearingClipper(tear)),
if (!tear || (tear && status != 2))
renderMainText(RandomTearingClipper(tear)),
if (tear && status == 2) renderTearText2(RandomTearingClipper(tear)),
],
);
}
终究作用如下图所示,这儿还额定对后边两个文本做了一个 ClipRect
处理,闪烁切换的时候只展现部分内容,这样在「毛病」时的切换不会显得过分僵硬,能够看到简略的 CSS 作用在 Flutter 上的完成成本其实并不低。
当然,这儿的完成没考虑功能问题,所以代码也比较糙,不过这儿主要是为了展现了 ClipPath
和 Shadow
的运用技巧,相信经过这个例子,能够协助咱们更好地发掘 Flutter 里对于途径制作和阴影的运用场景,这才是本篇的主要目的。
那么本篇小技巧到这儿就结束了,如果你还有什么想说的,欢迎留言评论。
完整代码可见:github.com/CarGuo/gsy_…