效果图
使用方法
Shimmer( baseColor: const Color(0x08ffffff), // 背景颜色 highlightColor: Colors.white, // 高光的颜色 loop: 2, // 闪烁循环次数,不传默认一直循环 child: Image.asset('assets/images/watermelon.png',width: 325, height: 260, fit: BoxFit.contain), )
实现原理
① 特效控件分为两层:底层显示调用方传入的控件;上层覆盖一层渐变着色器。
② 启动动画,根据动画的进度,对渐变着色器的区域进行绘制,当区域变大变小时,着色器高光的地方也在相应进行偏移。
③ 同时着色器不能超出底层控件的绘制范围,底层控件的形状是不规则的,渐变层不能超出底层控件的layer对象。这样才能实现完全贴合 底层控件形状 的微光闪烁。
控件分层显示
@override Widget build(BuildContext context) { return Stack( children: [ // 底层控件 widget.child, // 覆盖闪烁微光 AnimatedBuilder( animation: _controller, child: widget.child, builder: (BuildContext context, Widget? child) => _Shimmer( child: child, percent: _controller.value, direction: widget.direction, gradient: widget.gradient, ), ) ], );
开启动画
late AnimationController _controller; int _count = 0; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: widget.duration) ..addStatusListener((AnimationStatus status) { if (status != AnimationStatus.completed) { return; } _count++; if (widget.loop != 0 && _count < widget.loop) { _controller.forward(from: 0.0); } }); if (widget.loop == 0) { _controller.repeat(); } else { _controller.forward(); } }
重点:着色器该如何绘制,又该如何通过AnimationController的进度进行偏移?由于着色器不能超出底层控件的绘制范围,所以必须拿到底层控件的绘制上下文【即 PaintingContext】,调用其pushLayer方法,让引擎把着色器绘制上去。
需要用到PaintingContext,自然就需要去管理RenderObject,所以着色器的编写使用RenderProxyBox进行计算并绘制出layer对象,计算的过程根据上面的AnimationController的进度进行计算。
class _ShimmerFilter extends RenderProxyBox { ShimmerDirection _direction; Gradient _gradient; double _percent; _ShimmerFilter(this._percent, this._direction, this._gradient); @override ShaderMaskLayer? get layer => super.layer as ShaderMaskLayer?; set percent(double newValue) { if (newValue != _percent) { _percent = newValue; markNeedsPaint(); } } set gradient(Gradient newValue) { if (newValue != _gradient) { _gradient = newValue; markNeedsPaint(); } } set direction(ShimmerDirection newDirection) { if (newDirection != _direction) { _direction = newDirection; markNeedsLayout(); } } @override void paint(PaintingContext context, Offset offset) { if (child != null) { final double width = child!.size.width; final double height = child!.size.height; Rect rect; double dx, dy; if (_direction == ShimmerDirection.rtl) { dx = _offset(width, -width, _percent); dy = 0.0; rect = Rect.fromLTWH(dx - width, dy, 3 * width, height); } else if (_direction == ShimmerDirection.ttb) { dx = 0.0; dy = _offset(-height, height, _percent); rect = Rect.fromLTWH(dx, dy - height, width, 3 * height); } else if (_direction == ShimmerDirection.btt) { dx = 0.0; dy = _offset(height, -height, _percent); rect = Rect.fromLTWH(dx, dy - height, width, 3 * height); } else { dx = _offset(-width, width, _percent); dy = 0.0; rect = Rect.fromLTWH(dx - width, dy, 3 * width, height); } layer ??= ShaderMaskLayer(); layer! ..shader = _gradient.createShader(rect) ..maskRect = offset & size ..blendMode = BlendMode.srcIn; context.pushLayer(layer!, super.paint, offset); } else { layer = null; } } double _offset(double start, double end, double percent) { return start + (end - start) * percent; } }
Render对象绘制出来后,需要封装成widget使用,由于是单一组件,用SingleChildRenderObjectWidget即可。
class _Shimmer extends SingleChildRenderObjectWidget { @override _ShimmerFilter createRenderObject(BuildContext context) { return _ShimmerFilter(percent, direction, gradient); } @override void updateRenderObject(BuildContext context, _ShimmerFilter shimmer) { shimmer.percent = percent; shimmer.gradient = gradient; shimmer.direction = direction; } }
写在最后
这种闪烁动画,应用场景多种多样。可以作为对重要视图的着重显示,例如:勋章;也可以作为加载中骨架屏的加载动画。自己灵活使用即可。
作为一个大前端开发者,我希望把UI尽善尽美的展现给用户;此时你不仅需要一个集能力、审美、高标准于一体的设计师配合,更需要自己对所写界面有着极高的追求。而Flutter作为一个UI框架,玩到最后其实就是特效动画的高性能编写,这势必离不开其绘制原理,不要停留在widget、element的学习,Render、layer甚至再底层的C++才是我们学习路径。
参考文档:
- api.flutter-io.cn/flutter/ren…
- https://www.jb51.net/article/220450.htm
- github.com/hnvn/flutte…
总结
到此这篇关于Flutter给控件实现钻石般的微光特效的文章就介绍到这了,更多相关Flutter控件微光特效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!