Flutter給控件實現鉆石般的微光特效
效果圖
使用方法
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控件微光特效內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Flutter Component動畫的顯和隱最佳實踐
- Flutter折疊控件使用方法詳解
- flutter實現一個列表下拉抽屜的示例代碼
- Flutter CustomPaint自定義繪畫示例詳解
- Flutter懸浮按鈕FloatingActionButton使用詳解