前言:
这是我参与8月更文挑战的第 10 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战
,我准备在本月挑选 31
个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录
的重要素材。希望可以坚持下去,你的支持将是我最大的动力~
| 本系列 | 组件文章 | 列表 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | 1.NotificationListener | 2.Dismissible | 3.Switch | | 4.Scrollbar | 5.ClipPath | 6.CupertinoActivityIndicator | | 7.Opacity | 8.FadeTransition | 9. AnimatedOpacity | | 10. FadeInImage | 11. Offstage | 12. TickerMode | | 13. Visibility | 14. Padding | 15. AnimatedContainer | | 16.CircleAvatar | 17.PhysicalShape | 18.Divider | | 19.Flexible、Expanded 和 Spacer | 20.Card | |
一、认识 FadeInImage 组件
我们都知道,图片无论是从资源、文件、网络加载,都不会立刻完成,这样会出现短暂的空白,尤其是网络图片
。自己处理默认占位图也比较麻烦。FadeInImage
的作用就是:在目标图片
加载完成前使用默认图片
占位,加载完成后,目标图片
会渐变淡入,默认图片
会渐变淡出,这样可以既解决图片加载占位问题,渐变的动画在视觉上也不显突兀。本文,就来全面介绍一下 FadeInImage
组件的使用以及简单的源码实现。
1. FadeInImage 基本信息
首先,它是一个 StatelessWidget
,就说明它本身不会维护复杂的状态类,只是在 build
方法中负责组件的构建。
在普通构造中,必须传入两个 ImageProvider
对象,image
表示待加载的目标图片资源,placeholder
表示目标图片加载过程中显示的占位图片资源。另外还有很多用于配置图片和动画的属性,后面再一一介绍。
dart final ImageProvider placeholder; final ImageProvider image;
2.FadeInImage 的简单使用
只有知道两个图片资源就能最简单地使用 FadeInImage
,另外可以通过 width
和 height
限制图片的大小。下面头像是使用网络图片,黑色的是占位图,效果如下:
| 属性名 | 类型 | 默认值 | 用途 | | ----------- | ------------- | -------- | ------------ | | placeholder | ImageProvider | required | 占位图片资源 | | image | ImageProvider | required | 目标图片资源 | | width | double | null | 图片宽 | | height | double | null | 图片高 |
dart class FadeInImageDemo extends StatelessWidget{ final headUrl = 'https://sf1-ttcdn-tos.pstatp.com/img/user-avatar/5b2b7b85d1c818fa71d9e2e8ba944a44~300x300.image'; @override Widget build(BuildContext context) { return FadeInImage( width: 100, height: 100, placeholder: AssetImage( 'assets/images/default_icon.png', ), image: NetworkImage(headUrl), ); } }
3.FadeInImage 动画配置
淡出动画 fadeOut
是针对占位图
而言的,淡入动画 fadeIn
是针对目标图
而言的,我们可以配置两个动画的时长和曲线来达到期望的动画效果,如下是测试案例的效果:
| 属性名 | 类型 | 默认值 | 用途 | | ----------- | ------------- | -------- | ------------ | | fadeOutDuration | Duration | 300 ms | 占位图淡出时长 | | fadeOutCurve | Curves | Curves.easeOut | 占位图淡出动画曲线 | | fadeInDuration | Duration | 700 ms | 目标图淡入时长 | | fadeInCurve | Curves | Curves.easeIn | 目标图淡入动画曲线 |
dart FadeInImage( width: 100, height: 100, fadeOutDuration:Duration(seconds: 1), fadeOutCurve: Curves.easeOutQuad, fadeInDuration: Duration(seconds: 2), fadeInCurve: Curves.easeInQuad, placeholder: AssetImage( 'assets/images/default_icon.png', ), image: NetworkImage(headUrl), );
4.FadeInImage 的图片错误构建器
既然是图片加载,就可能出错,这两个 XXXErrorBuilder
就是用来处理当图片加载错误时应该如何显示。如果不处理,就会像下面这样:
我们可以指定 XXXErrorBuilder
回调来构建错误时显示的组件,如下当占位符错误,显示蓝色 Container
示意一下,你可以指定任意的 Widget
。
| 属性名 | 类型 | 默认值 | 用途 | | ----------- | ------------- | -------- | ------------ | | placeholderErrorBuilder | ImageErrorWidgetBuilder | null | 占位图加载错误时构建器 | | imageErrorBuilder | ImageErrorWidgetBuilder | null | 目标图加载错误时构建器 |
```dart class FadeInImageDemo extends StatelessWidget{
final headUrl = 'https://sf1-ttcdn-tos.pstatp.com/img/user-avatar/5b2b7b85d1c818fa71d9e2e8ba944a44~300x300.image';
@override Widget build(BuildContext context) { return FadeInImage( width: 100, height: 100, fadeOutDuration:Duration(seconds: 1), fadeOutCurve: Curves.easeOutQuad, fadeInDuration: Duration(seconds: 2), fadeInCurve: Curves.easeInQuad, placeholderErrorBuilder: placeholderErrorBuilder, placeholder: AssetImage( 'assets/images/defaulticon2.png', ), image: NetworkImage(headUrl), ); }
Widget _placeholderErrorBuilder(BuildContext context, Object error, StackTrace? stackTrace) { return Container( width: 100, height: 100, color: Colors.blue, ); } } ```
5.FadeInImage 其他属性
剩下的几个属性都是传给 Image
的,也就是说作用和 Image
中的属性一致,这里就不展开了。
6.FadeInImage 的其他构造
除了普通构造之外,FadeInImage
还有 assetNetwork
和 memoryNetwork
,这两者只是占位组件是 asset 路径
还是 Uint8List
字节数组的区别。这两个构造的目的是便于使用,可以指定缩放以及宽高。
可以看到两个 ImageProvider
成员对象会通过 ResizeImage
进行处理,通过 ResizeImage
可以更改图片资源的大小,通常用于减少 ImageCache
的内存占用。
到这里,FadeInImage
的使用方面就介绍完了。下面来看一下,作为一个 StatelessWidget
, FadeInImage
为什么可以执行这么复杂的组件内容变化。
二、 FadeInImage 组件的源码实现
1. FadeInImage 组件的构建
对于 StatelessWidget
而言,逻辑基本上只在 build
方法中如何构建组件。如下是 FadeInImage#build
,会通过 _image
方法创建 result
组件,并且一个 frameBuilder
的构建回调,使用了 _AnimatedFadeOutFadeIn
组件。
如果 excludeFromSemantics=false
会套上一个语义组件,Semantics
。这就是 FadeInImage
构造的全部内容。
_image
方法就是根据入参和成员属性构建 Image
组件而已,也没什么特别的。现在核心就是 frameBuilder
的回调会构建 _AnimatedFadeOutFadeIn
。那 Image#frameBuilder
是什么时候会调用呢?先让子弹飞一会,现在看一下 _AnimatedFadeOutFadeIn
的实现。
2. _AnimatedFadeOutFadeIn
组件的实现
它继承自 ImplicitlyAnimatedWidget
,表示其是一个 隐式动画组件
,在 AnimatedOpacity 一文中介绍过隐式组件的特性:外界只需要改变相关配置属性,进重构组件就能触发动画,无需操作动画控制器。
在 _AnimatedFadeOutFadeInState#build
中可以看出,淡入淡出的动画实现是通过两个 FadeTransition
完成的,两者通过 Stack
叠合。这样看来是不是豁然开朗。
3. 渐变动画如何触发
在 AnimatedOpacity 一文中也说过,对于隐式组件,动画的启动是通过改变属性和重建组件,来触发 State#didUpdateWidget
,开启动画。
那问题来了,作为 StatelessWidget
的FadeInImage
,如何重构 _AnimatedFadeOutFadeIn
。现在再来看 frameBuilder
就正是时候。Image
组件的 frameBuilder
是一个回调的构建,它会在 _ImageState
构建时触发。
第一次是图片没有加载:
第二次是图片加载完成:
属性变化 + 组件重构,从而触发隐式组件的动画启动,完成需求。可以看出 FadeInImage
是非常巧妙的。FadeInImage
的使用方式到这里就介绍完毕,那本文到这里就结束了,谢谢观看,明天见~