扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
状态可变的 widget 。
成都网络公司-成都网站建设公司成都创新互联十多年经验成就非凡,专业从事成都网站设计、成都做网站,成都网页设计,成都网页制作,软文发稿,广告投放平台等。十多年来已成功提供全面的成都网站建设方案,打造行业特色的成都网站建设案例,建站热线:13518219792,我们期待您的来电!
通过其类的定义能够看到 StatefulWidget 配置 StatefulElement 。
State 是 StatefulWidget 的内部逻辑与状态,由 StatefulWidget 的 createState 创建。
StatefulWidget 实例本身是不可变的, 但是 StatefulWidget 将其可变的状态,存储在与之关联的 State 对象中。
不管什么时候,只要在树中 mount 一个新的 StatefulElement ,必然需要注入一个 StatefulWidget ,注入一个 StatefulWidget 时, framework 都会调用一次 createState 方法。
其实,在 StatefulElement 构造的时候,就会调用 createState ,创建 _state 对象,( _state 是 StatefulElement 的变量)并且在 StatefulElement 的初始化方法中为 _state 关联当前的 StatefulElement 和用以配置 StatefulElement 的 StatefulWidget 。
StatefulElement 初始化方法如下:
这意味着如果 StatefulWidget 被插入到树中的多个位置,则会有多个 State 对象分别与它们关联。
关于此类的定义如下:
描述: 重写此方法以执行初始化。
场景: 如果 State 的 build 方法依赖于本身可以改变状态的对象时。(例如 ChangeNotifier 或 Stream ,或者可以订阅并接收通知的其他对象)正确的方式是:
注意点: 此方法中不能使用 BuildContext.dependOnInheritedWidgetOfExactType 。但是此方法被调用后会立即调用 didChangeDependencies ,在 didChangeDependencies 可以使用 BuildContext.dependOnInheritedWidgetOfExactType 。
调用时机: StatefulElement ,首次插入树中时会调用此方法,在 build 方法调用之前调用。
描述: StatefulElement 通过此方法返回的 widget 并通过调用 updateChild 来更新自己。
调用时机: framework 调用此方法的几个不同的场景如下:
描述: StatefulElement 存在,并且符合 Widget.canUpdate 的情况下对 StatefulWidget 进行更新。
调用时机: 不论何时只要 StatefulElement 的配置 widget 改变的时候就会调用。
注意: didUpdateWidget 方法最终会调用 build 方法,因此在此方法中调用 setState 是多余的。如果重写此方法,请确保调用 super.didUpdateWidget(oldWidget) 。
调用时机: 当此 State 对象的依赖项( InheritedWidget )更改时调用。
描述: 用于开发阶段 hot reload 。
调用时机: hot reload 时调用,调用后 build 方法也将被调用。无需在此方法中做任何操作。
调用时机: 当 StatefulElement 从树中移除的时候会调用。
调用时机: 当 StatefulElement 从树中 unmount 的时候会调用。
StatefulWidget 用以配置 StatefulElement ,但在这两者之间的 State 承接了 StatefulElement 的生命周期,而 StatefulWidget 仅仅只是连接了 State 与 StatefulElement 的不可变的实例,因此 StatefulWidget 的生命周期,依赖于 StatefulElement ,而 State 却是其最简单直接的体现形式。
为了能更好的理解 StatefulWidget 的生命周期,我画了一张关于 State 、 StatefulElement 、 Component 、 Element 的关系图。
ListView的基础创建使用有三种方式:
通过默认构造函数来创建列表,应用场景 = 短列表
这种方式创建的列表存在一个问题:对于那些长列表或者需要较昂贵渲染开销的子组件,即使还没有出现在屏幕中但仍然会被ListView所创建,这将是一项较大的开销,使用不当可能引起性能问题甚至卡顿。
长列表
列表子项之间需要分割线
ListView的进阶使用主要包括:下拉刷新 上拉加载
在Flutter中,ListView结合RefreshIndicator组件实现下拉刷新
通过包裹一层RefreshIndicator,自定义onRefresh回调方法实现
方式有两种:
通过ListView.controller属性可以判断ListView是否滑动到了底部,再进行上拉加载
NotificationListener是一个Widget,可监听子Widget发出的Notification
ListView在滑动时中会发出ScrollNotification类型的通知,可通过监听该通知得到ListView的滑动状态,判断是否滑动到了底部,从而进行上拉加载
NotificationListener有一个onNotification属性,定义了监听的回调方法,通过它来处理加载更多逻辑
不定期分享关于 安卓开发 的干货,追求 短、平、快 ,但 却不缺深度 。
APP 启动页在国内是最常见也是必备的场景,其中启动页在 iOS 上算是强制性的要求,其实配置启动页挺简单,因为在 Flutter 里现在只需要:
一般只要配置无误并且图片尺寸匹配,基本上就不会有什么问题, 那既然这样,还有什么需要适配的呢?
事实上大部分时候 iOS 是不会有什么问题, 因为 LaunchScreen.storyboard 的流程本就是 iOS 官方用来做应用启动的过渡;而对于 Andorid 而言,直到 12 之前 windowBackground 这种其实只能算“民间”野路子 ,所以对于 Andorid 来说,这其中就涉及到一个点:
所以下面主要介绍 Flutter 在 Android 上为了这个启动图做了哪些骚操作~
在已经忘记版本的“远古时期” , FlutterActivity 还在 io.flutter.app.FlutterActivity 路径下的时候,那时启动页的逻辑相对简单,主要是通过 App 的 AndroidManifest 文件里是否配置了 SplashScreenUntilFirstFrame 来进行判断。
在 FlutterActivity 内部 FlutterView 被创建的时候,会通过读取 meta-data 来判断是否需要使用 createLaunchView 逻辑 :
是不是很简单,那就会有人疑问为什么要这样做?我直接配置 Activity 的 android:windowBackground 不就完成了吗?
这就是上面提到的时间差问题, 因为启动页到 Flutter 渲染完第一帧画面中间,会出现概率出现黑屏的情况,所以才需要这个行为来实现过渡 。
经历了“远古时代”之后, FlutterActivity 来到了 io.flutter.embedding.android.FlutterActivity , 在到 2.5 版本发布之前,Flutter 又针对这个启动过程做了不少调整和优化,其中主要就是 SplashScreen 。
自从开始进入 embedding 阶段后, FlutterActivity 主要用于实现了一个叫 Host 的 interface ,其中和我们有关系的就是 provideSplashScreen 。
默认情况下它会从 AndroidManifest 文件里是否配置了 SplashScreenDrawable 来进行判断 。
默认情况下当 AndroidManifest 文件里配置了 SplashScreenDrawable ,那么这个 Drawable 就会在 FlutterActivity 创建 FlutterView 时被构建成 DrawableSplashScreen 。
DrawableSplashScreen 其实就是一个实现了 io.flutter.embedding.android.SplashScreen 接口的类,它的作用就是:
之后 FlutterActivity 内会创建出 FlutterSplashView ,它是个 FrameLayout。
FlutterSplashView 将 FlutterView 和 ImageView 添加到一起, 然后通过 transitionToFlutter 的方法来执行动画,最后动画结束时通过 onTransitionComplete 移除 splashScreenView 。
所以整体逻辑就是:
当然这里也是分状态:
当然这个阶段的 FlutterActivity 也可以通过 override provideSplashScreen 方法来自定义 SplashScreen 。
看到没有,做了这么多其实也就是为了弥补启动页和 Flutter 渲染之间, 另外还有一个优化,叫 NormalTheme 。
通过该配置 NormalTheme ,在 Activity 启动时,就会首先执行 switchLaunchThemeForNormalTheme(); 方法将主题从 LaunchTheme 切换到 NormalTheme 。
大概配置完就是如下样子, 前面分析那么多其实就是为了告诉你,如果出现问题了,你可以从哪个地方去找到对应的点 。
讲了那么多, Flutter 2.5 之后 provideSplashScreen 和 io.flutter.embedding.android.SplashScreenDrawable 就被弃用了,惊不喜惊喜,意不意外,开不开心 ?
通过源码你会发现,当你设置了 splashScreen 的时候,会看到一个 log 警告:
为什么会弃用?
其实这个提议是在 这个 issue 上,然后通过 这个 pr 完成调整。
大概意思就是: 原本的设计搞复杂了,用 OnPreDrawListener 更精准,而且不需要为了后面 Andorid12 的启动支持做其他兼容,只需要给 FlutterActivity 等类增加接口开关即可 。
也就是2.5之后 Flutter 使用 ViewTreeObserver.OnPreDrawListener 来实现延迟直到加载出 Flutter 的第一帧。
为什么说默认情况? 因为这个行为在 FlutterActivity 里,是在 getRenderMode() == RenderMode.surface 才会被调用,而 RenderMode 又和 BackgroundMode 有关心 。
所以在 2.5 版本后, FlutterActivity 内部创建完 FlutterView 后就会执行一个 delayFirstAndroidViewDraw 的操作。
这里主要注意一个参数: isFlutterUiDisplayed 。
当 Flutter 被完成展示的时候, isFlutterUiDisplayed 就会被设置为 true。
所以当 Flutter 没有执行完成之前, FlutterView 的 onPreDraw 就会一直返回 false ,这也是 Flutter 2.5 开始之后适配启动页的新调整。
看了这么多,大概可以看到其实开源项目的推进并不是一帆风顺的,没有什么是一开始就是最优解,而是经过多方尝试和交流,才有了现在的版本,事实上开源项目里,类似这样的经历数不胜数:
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流