- 原文地址:Make shimmer effect in Flutter: Learn Flutter from UI challenges
- 原文做者:Hung HD
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:geniusq1981
經過挑戰 UI 製做來學習 Flutter前端
我是一個狂熱的移動開發者,Android 平臺和 iOS 平臺開發都有涉及。過去我不相信任何跨平臺的開發框架(Ionic,Xamarin,ReactNative),但如今我要講一下我遇到跨平臺開發框架 Flutter 以後的故事。android
做爲一名原生應用開發人員,我深深感到 UI 定製開發是多麼痛苦,即便是使用跨平臺開發框架去進行開發,這種痛苦也不能獲得緩解,有時甚至會更糟糕。但 Flutter 的出現讓我看到改善這種痛苦的但願。ios
Flutter 從無到有構建全部 UI 元素(稱爲 Widget)。沒有去封裝原生視圖,沒有使用基於 web 的 UI 元素。如同遊戲框架在遊戲中構建遊戲世界的方式(角色、敵人、宮殿…)那樣,Flutter 基於 Skia 圖形渲染引擎來繪製本身的 UI。這樣作真的頗有意義,由於你能夠徹底控制你在屏幕上繪製的東西。這是否讓你在腦海中想到點什麼?對我來講,這聽起來彷佛是在告訴我我能夠更加容易地進行 UI 定製開發了。我嘗試挑戰一些 UI 效果實現來證實這一點。git
我想到的一個挑戰是微光閃爍效果。這是一個很是常見的效果,若是你不熟悉這個名字,那麼想一下你喚醒手機時所顯示的「滑動解鎖」動畫。github
基本思路很簡單。動畫效果由從左到右移動的漸變所組成。web
關鍵是我不想僅僅爲文本內容來作這個效果。這種效果在現代的移動應用中做爲加載動畫是很是流行的。canvas
第一個初始想法是在內容佈局的頂部繪製一個不透明的漸變區域。雖然這能夠實現,但不是一個好方法。咱們不但願動畫效果弄髒咱們的整個白色背景。效果須要僅適用在給定的內容佈局上。後端
如今是時候參考一下 Flutter 文檔和示例代碼去了解如何實現這種效果了。bash
通過研究我發現一個名爲 SingleChildRenderObjectWidget 的基類,該基類露出一個 Canvas 對象。Canvas 是一個對象,它負責在屏幕上繪製內容,它有一個有趣的方法稱爲 saveLayer,它用來「在保存堆棧上保存當前變換和片斷的副本,而後建立一個新的組,用於保存後續調用」(摘自官方文檔)。這正是我須要的特性,它讓我能夠在特定內容佈局上實現微光閃爍效果。框架
在 Flutter 中,有一個很不錯的小練習能夠參考。一個 widget 一般包含一個名爲 child 或 children 的參數,它能夠幫助咱們將變換應用到後代 widget。咱們的 Shimmer widget 也有一個 child,它可讓咱們建立任何咱們想要的佈局,而後將它做爲 Shimmer 的 child 進行傳遞,Shimmer widget 反過來只會對那個 child 起做用。
import 'package:flutter/material.dart';
class Shimmer extends StatefulWidget {
final Widget child;
final Duration period;
final Gradient gradient;
Shimmer({Key key, this.child, this.period, this.gradient}): super(key: key);
@override
_ShimmerState createState() => _ShimmerState();
}
class _ShimmerState extends State<Shimmer> {
@override
Widget build(BuildContext context) {
return _Shimmer();
}
}
複製代碼
_Shimmer
是負責效果繪畫的內部類。它從 SingleChildRenderObjectWidget 擴展而來並重寫了 paint 方法來執行繪製任務。咱們使用 Canvas 對象的 saveLayer 和 paintChild 方法來捕捉咱們的 child 做爲一個圖層並在上面繪製漸變效果(帶上一點 BlendMode 的魔法)。
import 'package:flutter/rendering.dart';
class _Shimmer extends SingleChildRenderObjectWidget {
final Gradient gradient;
_Shimmer({Widget child, this.gradient})
: super(child: child);
@override
_ShimmerFilter createRenderObject(BuildContext context) {
return _ShimmerFilter(gradient);
}
}
class _ShimmerFilter extends RenderProxyBox {
final _clearPaint = Paint();
final Paint _gradientPaint;
final Gradient _gradient;
_ShimmerFilter(this._gradient)
: _gradientPaint = Paint()..blendMode = BlendMode.srcIn;
@override
bool get alwaysNeedsCompositing => child != null;
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
assert(needsCompositing);
final rect = offset & child.size;
_gradientPaint.shader = _gradient.createShader(rect);
context.canvas.saveLayer(rect, _clearPaint);
context.paintChild(child, offset);
context.canvas.drawRect(rect, _gradientPaint);
context.canvas.restore();
}
}
}
複製代碼
剩下的就是添加一個動效,讓咱們的效果動起來。這裏沒什麼特別的,咱們將建立一個動效來在繪製漸變以前從左到右移動 Canvas,這樣就能產生漸變移動的效果。
咱們在 _ShimmerState
中爲動效建立一個新的 AnimationController。咱們的 _Shimmer
類和 _ShimmerFilter
類還須要一個新變量(稱之爲 percent)來存儲該動畫執行的進度結果,並在每次 AnimationController 發出新值時調用 markNeedsPaint(這會讓 widget 從新繪製)。Canvas 的移動位移量能夠根據 percent 的值計算出來。
class _ShimmerState extends State<Shimmer> with TickerProviderStateMixin {
AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: widget.period)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.repeat();
}
});
controller.forward();
}
@override
Widget build(BuildContext context) {
return _Shimmer(
child: widget.child,
gradient: widget.gradient,
percent: controller.value,
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
複製代碼
class _Shimmer extends SingleChildRenderObjectWidget {
...
final double percent;
_Shimmer({Widget child, this.gradient, this.percent})
: super(child: child);
@override
_ShimmerFilter createRenderObject(BuildContext context) {
return _ShimmerFilter(percent, gradient);
}
@override
void updateRenderObject(BuildContext context, _ShimmerFilter shimmer) {
shimmer.percent = percent;
}
}
class _ShimmerFilter extends RenderProxyBox {
...
double _percent;
_ShimmerFilter(this._percent, this._gradient)
: _gradientPaint = Paint()..blendMode = BlendMode.srcIn;
...
set percent(double newValue) {
if (newValue != _percent) {
_percent = newValue;
markNeedsPaint();
}
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
assert(needsCompositing);
final width = child.size.width;
final height = child.size.height;
Rect rect;
double dx, dy;
dx = _offset(-width, width, _percent);
dy = 0.0;
rect = Rect.fromLTWH(offset.dx - width, offset.dy, 3 * width, height);
_gradientPaint.shader = _gradient.createShader(rect);
context.canvas.saveLayer(offset & child.size, _clearPaint);
context.paintChild(child, offset);
context.canvas.translate(dx, dy);
context.canvas.drawRect(rect, _gradientPaint);
context.canvas.restore();
}
}
double _offset(double start, double end, double percent) {
return start + (end - start) * percent;
}
}
複製代碼
還不錯。咱們剛剛只用了大約 100 行代碼就實現了微光閃爍效果。這就是 Flutter 的美妙之處。
這只是個開始。接下來我會使用 Flutter 來挑戰更多更復雜的 UI 效果。我將在下一篇文章中分享個人成果。感謝你的閱讀!
備註:我已經將個人代碼發佈爲一個名爲 shimmer 的包.
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。