1.在Flutter中使用setState时的源码6个简单技巧
2.Flutter系统网络加载流程
在Flutter中使用setState时的6个简单技巧
setState函数是在Flutter应用程序中管理状态的最基本方法。以下是源码一些保持应用可维护性的最佳实践。StatefulWidget的源码setState函数是一种在Flutter应用程序中管理状态的简单方法。但是源码,当您希望您的源码应用程序正常工作和高性能时,您需要避免几个陷阱。源码黑客模拟终端源码以下是源码您应该坚持的一些最佳实践。
setState有什么用?setState是源码Flutter发出rebuild(重建)当前widget及其后代的方式。在rebuild过程中,源码最新的源码变量值将被用于创建用户界面。比方说,源码一个用户将一个开关从打开切换到关闭。源码该开关有一个存储该值的源码支持变量,所以在改变之后,源码它被设置为false。源码开关本身并不反映这一变化,直到它被重建为新的支持字段值。
更改值
调用setState()
用户界面已更新
?技巧1:保持##widgets小!setState触发了对你当前所在的小组件的重建。如果你的整个应用程序只包含一个widget,那么整个widget将被重建,这将使你的应用程序变得缓慢。请看下面的例子。
import'package:flutter/material.dart';classHomeextendsStatefulWidget{ constHome({ Key?雀巢没有溯源码key}):super(key:key);@overrideState<Home>createState()=>_State();}class_StateextendsState<Home>{ bool_tile1=false;bool_tile2=false;bool_tile3=false;bool_tile4=false;bool_tile5=false;@overrideWidgetbuild(BuildContextcontext){ print("builtmethodHome");//<--setStatetriggersbuildhere!returnScaffold(appBar:AppBar(title:constText("Demo")),body:Center(child:Column(crossAxisAlignment:CrossAxisAlignment.center,mainAxisAlignment:MainAxisAlignment.center,children:<Widget>[SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile1?"on":"off"}"),value:_tile1,onChanged:(_){ setState((){ _tile1=!_tile1;});}),SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile2?"on":"off"}"),value:_tile2,onChanged:(_){ setState((){ _tile2=!_tile2;});}),SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile3?"on":"off"}"),value:_tile3,onChanged:(_){ setState((){ _tile3=!_tile3;});}),SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile4?"on":"off"}"),value:_tile4,onChanged:(_){ setState((){ _tile4=!_tile4;});}),SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile5?"on":"off"}"),value:_tile5,onChanged:(_){ setState((){ _tile5=!_tile5;});})])));}}这里我们在一个Column中有5个SwitchListTile小部件,它们都是同一个小部件的一部分。
如果您切换任何控件,整个屏幕都会被重建。Scaffold,AppBar,Column,...但只要重建已更改的小部件就足够了。让我们看下一个代码示例:
import'package:flutter/material.dart';classHome2extendsStatefulWidget{ constHome2({ Key?key}):super(key:key);@overrideState<Home2>createState()=>_State();}class_StateextendsState<Home2>{ @overrideWidgetbuild(BuildContextcontext){ print("builtmethodHome2");returnScaffold(appBar:AppBar(title:constText("Demo")),body:Center(child:Column(crossAxisAlignment:CrossAxisAlignment.center,mainAxisAlignment:MainAxisAlignment.center,children:const<Widget>[Switch(),Switch(),Switch(),Switch(),Switch()])));}}classSwitchextendsStatefulWidget{ constSwitch({ Key?key}):super(key:key);@overrideState<StatefulWidget>createState()=>_SwitchState();}class_SwitchStateextendsState<Switch>{ bool_value=false;@overrideWidgetbuild(BuildContextcontext){ print("buildmethodSwitch");//<--setStatetriggersbuildhere!returnSwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _value?"on":"off"}"),value:_value,onChanged:(_){ setState((){ _value=!_value;});});}}在这里,我们将SwitchListTile包装在单个StatefulWidget中。页面看起来相同,但如果您单击此示例中的任何开关,则只有单击的小部件将重建。
?技巧2:不要在构建方法中调用setState来自FlutterAPI文档
这个方法有可能在每一帧中被调用,除了建立一个小部件外,不应该有任何副作用。
build方法旨在构建小部件树,因此我们应该保持这种方式。不要在这里做花哨的事情,它会减慢你的应用程序。对setState的调用可能会触发额外的重建,在最坏的情况下,你可能最终会出现一个异常,告诉你目前有一个重建正在进行。
?技巧3:不要在initState方法中调用setStateinitState将在完成后触发重建,因此无需在此方法中调用setState。此方法旨在初始化与状态相关的超级加倍指标源码属性,例如设置默认值或订阅流。不要在这里做任何其他事情!
?技巧4:setState()和setState(...)是相等的像这样使用setState没关系
setState((){ _text=“Hello”;});或者像这样
_text=“Hello”;setState((){ });结果是一样的。
?技巧5:setState(...)代码必须很小不要在setState内做任何大的计算,因为它将阻止你的应用程序重绘。请看下面的示例代码:
setState((){ for(vari=0;i<;i++)print(i);_value=true;});只有在打印语句之后,小部件才会重建。在这段时间里,你的应用程序不会对用户的操作做出反应,它将在之后执行这些操作。因此,如果用户因为没有视觉反馈而多次点击一个控件,多次重建就会堆积起来,会使应用程序的速度更慢。
一个更好的方法是在执行一个长期运行的操作时显示一个进度指示器,这样用户就知道正在发生一些事情,他需要等待完成。
?技巧6:setState(...)代码不能是异步的运行代码时
setState(()async{ awaitFuture.delayed(constDuration(seconds:5));});你最终会得到一个类似这样的异常信息:
在方法之外执行异步操作,然后调用它。
结束我希望这些见解能帮助你更好地理解Flutter中setState的机制。坚持这些技巧,你会有更少的问题和更快的应用程序。源代码例子可以在GitHub上找到。源码搭建矩阵系统
原文:/post/
Flutter系统网络加载流程
Flutter原生支持在Image组件上显示网络,最简单的使用方式如下,调用Image的命名构造方法Image.network即可实现网络的下载显示。Widgetimage=Image.network(imageUrl);那么,它内部是如何实现的呢?是否有做缓存处理或其他优化操作呢?带着疑问,我们一起来看下它的底层究竟是如何实现的。
一、从构造函数开始我们以最简单的调用方式举例,当我们使用Image.network(imageUrl)这种方式来显示时,Image组件内部image属性就会被赋值NetworkImage。
//此为简化过的Image组件类结构classImageextendsStatefulWidget{ Image.network(Stringsrc,):image=NetworkImage(src);//数据处理的基类finalImageProviderimage;}这里引出了一个类叫NetworkImage,它是ImageProvider的子类,专门实现网络的下载和解析逻辑。当然你直接点进去看到的其实是个抽象类,并不是真正实现下载逻辑的地方,真正实现网络下载解析的在'_network_image_io.dart’这个文件下。构造函数知道这些就够了。接下来就看Image是在何时触发网络的下载的。
二、下载入口Image是一个StatefulWidget,它又一个对应的State叫_ImageState。在这个_ImageState的生命周期中,控制着的多头买点指标源码下载过程。
State的生命周期可以简单的分为:构造函数→initState→didChangeDependencies→build
因此,我们顺着这个顺序找,很快看到一个可疑的地方,didChangeDependencies中的_resolveImage方法。而TickerMode则是用于控制动画的,在这里被用于判断是否禁用了动画。关于TickerMode的相关介绍,可以看下这篇文章
//完整源码@overridevoiddidChangeDependencies(){ _updateInvertColors();//处理的入口_resolveImage();//当动画被禁用时,也是无法显示的,这个if(TickerMode.of(context))//添加流处理的监听_listenToStream();else_stopListeningToStream(keepStreamAlive:true);super.didChangeDependencies();}我们进入到_resolveImage方法中去。
void_resolveImage(){ //ScrollAwareImageProvider包装了我们的NetworkImagefinalScrollAwareImageProviderprovider=ScrollAwareImageProvider<Object>(context:_scrollAwareContext,imageProvider:widget.image,);//新建流finalImageStreamnewStream=provider.resolve(createLocalImageConfiguration(context,size:widget.width!=null&&widget.height!=null?Size(widget.width!,widget.height!):null,));assert(newStream!=null);//更新流_updateSourceStream(newStream);}_resolveImage方法就做了三件事。
1、用ScrollAwareImageProvider包装了NetworkImage
2、创建流对象ImageStream
3、更新流
2.1、ScrollAwareImageProviderScrollAwareImageProvider也是ImageProvider的子类,它的作用很简单,就是防止在快速滑动的时候加载,当存在快速滑动时,会将解析的工作放到下一帧处理。至于具体如何实现,我们放在后面再提。
2.2、ImageConfigurationImageConfiguration由方法createLocalImageConfiguration创建,保存了的基本配置信息,如Bundle,屏幕项目比devicePixelRatio,本地化local,尺寸size,平台platform等。
2.3、ImageStream表示一个流,可以添加观察者ImageStreamCompleter来监听是否处理完成。一个流可以添加多个观察者。
ImageStream由provider的resolve方法调用后创建。通过源码可知,此处的provider就是ScrollAwareImageProvider对象。但是它内部并没有实现resolve方法,因此此处调用的是父类ImageProvider的resolve方法。
三、流和Key以下代码截取自ImageProvider,并且删减了无关代码。
ImageStreamresolve(ImageConfigurationconfiguration){ //创建流,这里直接调用了ImageStream的构造函数,并没有用到configurationfinalImageStreamstream=createStream(configuration);//关键在这里,这里会根据configuration创建一个唯一key_createErrorHandlerAndKey(configuration,//成功的回调(Tkey,ImageErrorListenererrorHandler){ resolveStreamForKey(configuration,stream,key,errorHandler);},//下面是错误回调,可以不关注(T?key,Objectexception,StackTrace?stack)async{ awaitnull;//waitaneventturnincasealistenerhasbeenaddedtotheimagestream.InformationCollector?collector;if(stream.completer==null){ stream.setCompleter(_ErrorImageCompleter());}stream.completer!.reportError(exception:exception,stack:stack,context:ErrorDescription('whileresolvinganimage'),silent:true,//couldbeanetworkerrororwhatnotinformationCollector:collector,);},);returnstream;}resolve方法的作用是创建流对象ImageStream,并根据传入的配置信息configuration,创建对应的Key,这个Key用于缓存。
那么这个key到底是怎么创建的呢,我们进入到_createErrorHandlerAndKey方法中查看。关键代码如下,已删除无关代码。
Future<T>key;try{ key=obtainKey(configuration);}catch(error,stackTrace){ handleError(error,stackTrace);return;}key.then<void>((Tkey){ obtainedKey=key;try{ successCallback(key,handleError);}catch(error,stackTrace){ handleError(error,stackTrace);}}).catchError(handleError);可以看到方法实现中调用了ImageProvider的obtainKey方法,而这个方法在ImageProvider并没有具体实现,需要子类完成对应的实现。
Future<T>obtainKey(ImageConfigurationconfiguration);还记得上文的分析不,我们说传入的imageProvider实例是ScrollAwareImageProvider对象,因此对应的实现也要到这个类中去查找。很快,我们找到obtainKey方法的实现,可以看到它做了个透传,具体是由它包装的类也就是NetworkImage来实现的。
@overrideFuture<T>obtainKey(ImageConfigurationconfiguration)=>imageProvider.obtainKey(configuration);那么,我们就去NetworkImage找obtainKey。
注意下真正的NetworkImage实现是在_network_image_io.dart文件下的。
Future<NetworkImage>obtainKey(image_provider.ImageConfigurationconfiguration){ returnSynchronousFuture<NetworkImage>(this);}到这,我们就知道了NetworkImage的key为SynchronousFuture。
获取到key后的下一步就是调用_createErrorHandlerAndKey方法的successCallback回调。从而触发了下一个流程resolveStreamForKey。
_createErrorHandlerAndKey(configuration,(Tkey,ImageErrorListenererrorHandler){ //拿到Key之后的回调resolveStreamForKey(configuration,stream,key,errorHandler);})四、根据key来处理流还是回到子类ScrollAwareImageProvider中,它重写了父类的resolveStreamForKey方法,前文提到,ScrollAwareImageProvider是用来防止列表在快速滑动的时候来加载的,那么它是如何实现的?我们就从resolveStreamForKey这个方法中来一探究竟。
//此为简化过的Image组件类结构classImageextendsStatefulWidget{ Image.network(Stringsrc,):image=NetworkImage(src);//数据处理的基类finalImageProviderimage;}0Scrollable用于滑动组件,它有个方法叫recommendDeferredLoadingForContext,表示是否建议延迟加载。内部最终是根据滑动速度和当前设备的最大物理尺寸的边去比较,如果大于,表示速度过快,那么就建议延迟。具体逻辑在scroll_physics.dart文件下。这里不多做介绍。
一旦当前应用处于滑动状态,并且速度过快,那么,的加载将会被推迟到下一帧再进行尝试。因此我们说,当处于快速滑动时,是无法加载的。
当判断可以加载时,操作流将会被移交给被包装类imageProvider,这里是NetworkImage来处理。但是,NetworkImage没有实现resolveStreamForKey方法,因此最终还是跑到了ImageProvider类中的resolveStreamForKey方法下。
//此为简化过的Image组件类结构classImageextendsStatefulWidget{ Image.network(Stringsrc,):image=NetworkImage(src);//数据处理的基类finalImageProviderimage;}1当第一次加载网络图的时候,会直接走到下面这个逻辑中。这里涉及到一个很重要的类,ImageCache。它是做缓存用的。
//此为简化过的Image组件类结构classImageextendsStatefulWidget{ Image.network(Stringsrc,):image=NetworkImage(src);//数据处理的基类finalImageProviderimage;}.1、ImageCache缓存类,只做了内存缓存。它由PaintingBinding持有,是一个单利。它的内部通过三个Map来缓存。
//此为简化过的Image组件类结构classImageextendsStatefulWidget{ Image.network(Stringsrc,):image=NetworkImage(src);//数据处理的基类finalImageProviderimage;}3从缓存器中获取的逻辑集中在putIfAbsent方法中。以下代码已经去掉无关代码。
//此为简化过的Image组件类结构classImageextendsStatefulWidget{ Image.network(Stringsrc,):image=NetworkImage(src);//数据处理的基类finalImageProviderimage;}.2、load一旦在ImageCache中找不到缓存的,就会通过loader回调出来,走真正的下载流程。
//此为简化过的Image组件类结构classImageextendsStatefulWidget{ Image.network(Stringsrc,):image=NetworkImage(src);//数据处理的基类finalImageProviderimage;}5还是先看ScrollAwareImageProvider类,里面实现了load方法,并透传给了NetworkImage来实现。
//此为简化过的Image组件类结构classImageextendsStatefulWidget{ Image.network(Stringsrc,):image=NetworkImage(src);//数据处理的基类finalImageProviderimage;}6在NetworkImage下,可以找到对应的load方法实现。里面有个_loadAsync方法,它就是我们要找的下载核心代码。
//此为简化过的Image组件类结构classImageextendsStatefulWidget{ Image.network(Stringsrc,):image=NetworkImage(src);//数据处理的基类finalImageProviderimage;}7五、下载饶了一大圈,终于来到了下载的地方了。可以看到下载的逻辑很简单,创建一个下载的/post/