小菜在做 Android 开发时,常常需要 帧动画 来作为作为 loading 动画;而 Flutter 没有直接提供类似于 帧动画 的组件,小菜简单尝试一个简单的 ACEFrameAnimated 帧动画小组件;
小菜理解的 帧动画 其实一系列图片在一段时间内的叠加展示,以达到连贯的动画效果;
ACEFrameAnimated
小菜认为,帧动画最重要的两个元素,分别是图片资源和间隔时间;之后便可对图片根据间隔时间来循环展示;为了适配网络图片和本地图片,小菜设置了一个 ACEFramePicType 资源类型;
enum ACEFramePicType { asset, network }
final List<Map<ACEFramePicType, String>> picList;
final Duration duration;
ACEFrameAnimated(this.picList, this.duration);
小菜计划返回一个基本的 Widget,并通过 Future 延迟加载图片资源,其中需要注意的是循环加载,注意当前数组下标;其中在 initState() 中更新图片 _framePicList() 时,需要在 Future.delayed 之前先加载第一张图片,否则会出现短暂空白的情况;
class _ACEFrameAnimatedState extends State<ACEFrameAnimated> {
List<Map<ACEFramePicType, String>> _picList;
Duration _duration;
int _index = 0;
Widget _buildWid = Container();
_ACEFrameAnimatedState(this._picList, this._duration);
@override
void initState() {
super.initState();
_framePicList();
}
@override
Widget build(BuildContext context) => _buildWid;
_framePicList() async {
setState(() {
_index = _index % _picList.length;
_buildWid = Container(
child: _picList[_index].keys.toList()[0] == ACEFramePicType.asset
? Image.asset(_picList[_index].values.toList()[0])
: Image.network(_picList[_index].values.toList()[0]));
_index++;
});
await Future.delayed(_duration, () => _framePicList());
}
}
Tips
小菜在退出页面时出现内存溢出,导致原因有两个,第一个是未清除 Widget 中的资源列表;第二个是 Future.delayed 发送消息后,await 导致消息未返回;
E/flutter (13298): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
E/flutter (13298): The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
E/flutter (13298): This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
E/flutter (13298): #0 State.setState.<anonymous closure> (package:flutter/src/widgets/framework.dart:1112:9)
E/flutter (13298): #1 State.setState (package:flutter/src/widgets/framework.dart:1147:6)
E/flutter (13298): #2 _ACEFrameAnimatedState._framePicList (package:flutter_app/widget/ace_frame_animated.dart:32:5)
E/flutter (13298): #3 _ACEFrameAnimatedState._framePicList.<anonymous closure> (package:flutter_app/widget/ace_frame_animated.dart:40:43)
E/flutter (13298): #4 new Future.delayed.<anonymous closure> (dart:async/future.dart:316:39)
E/flutter (13298): #5 _rootRun (dart:async/zone.dart:1122:38)
E/flutter (13298): #6 _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (13298): #7 _CustomZone.runGuarded (dart:async/zone.dart:925:7)
E/flutter (13298): #8 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:965:23)
E/flutter (13298): #9 _rootRun (dart:async/zone.dart:1126:13)
E/flutter (13298): #10 _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (13298): #11 _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:949:23)
E/flutter (13298): #12 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:23:15)
E/flutter (13298): #13 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:384:19)
E/flutter (13298): #14 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:418:5)
E/flutter (13298): #15 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)
小菜根据提示在使用 setState 时先判断当前 State 是否已绑定在 View 中;同时在 dispose 中清空资源;
@override
void dispose() {
super.dispose();
if (_picList != null) {
_picList.clear();
}
if (widget != null && widget.picList != null) {
widget.picList.clear();
}
}
小菜仅实现了最基本的帧动画效果,对于效果的优化还未涉及;如有错误请多多指导!
来源: 阿策小和尚