Flutter中的三棵树是如何完成绘制的

定义 Widget:存放渲染内容、它只是一个配置数据结构,创建是非常轻量的,在页面刷新的过程中随时会重建。 其中Widget根据功能可以分为三类: 组合类StatelessWidget/StatefulWidget:比如我们常见的Container就是一个组合类的控件,它主要负责组合封装多个其他负责绘制的原子组件。 代理类inheritedwidget:它的父类是ProxyWidget,顾名思义。简单来说,InheritedWidget 的作用是向它的子 Widget 有效地传播和分享数据,当 InheritedWidget 作为一个Parent Widget时,它下面的Widget tree的所有Widget都可以去和 InheritedWidget 发生数据传递和交互。当数据发生改变时,一部分控件需要 rebuild,另外的控件不需要 rebuild 的时候,可以使用 InheritedWidget。 绘制类RenderObjectWidget:RenderObjectWidget会负责创建出负责实际渲染的RenderObject对象,RenderObject负责实际的layout()和paint()。后期有空,我会手动写一个RenderObject。 Element: 是分离 WidgetTree 和真正的渲染对象的中间层,可以看成flutter的骨架。 Widget 用来描述对应的Element 属性,同时持有Widget和RenderObject,存放上下文信息,通过它来遍历视图树,支撑UI结构。 其中Element可以根据功能分成两大类: 组合类ComponentElement:主要包括如下 StatelessElement / StatefulElement / ProxyElement 子类;其中各Element都是与 Widget 对应的。 绘制类RenderObjectElement:RenderObjectElement对应的是RenderObjectWidget。 RenderObject用于应用界面的布局和绘制,负责真正的渲染,保存了元素的大小,布局等信息。 当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget树,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element 树。最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 RenderObject 树。 Widget => Element 如何通过Widget更新Element: Element/ComponentElement: 单个Element时候通过updateChild方法更新; E } l e i } f i } } r m / f i f e e 如 i r n i } } } e n t n 果 ( f e a ( / f l e u t n n d t l c 新 i n e i c n e d n s o w r ? e e ( e u h 旧 ( f e l / f h e l / e e e l C n w w c a r E i w c u w s 可 u i w s 其 a w d h u W W h c n l l i h ( p C e 以 ( p l C e 他 c C { i n p i i i t e d d i c d h u c d d h 情 t h c l e d d d l i n m g l h a i i p h a . i { 况 i i h d w a g g d v u e ! e d i t l f d i t u l , v l i C t e e a l n = t . l e d a l e p d 移 a d l = h e t t ! t l t 相 w d S ( t d S d 除 t d i C 是 = e ; n 同 i . l = W e . l a = 旧 e = 是 i l h n = C n u 的 d s o i 的 s o t 的 C n n d i u = n h e l 情 g l t c d 情 l t e c , h i u f ; l l u i w l 况 e o F h g 况 o F ( h 重 i n l l d l n l l C ) t t o i e , t o n i 新 l f l a ( , u l d h r l t 也 r e l i d l , t E 并 l ) ( i { = ! C d . 就 ! C w d n ( a 这 e l 且 l c l = = h ; c 是 = h W ; f c t 里 W e o ) h d i a r i i l h e 直 i m l i ; n n l n u n l d a i W 接 d e d { l e e d U n e d g t l i i g n d w w ( p t w ( e e d d n e t c ) W S c d i S c t W ) g f t ? h ; i l h a m l h ) i ; e l ( i d o i t e o i ; d t a n c l g t l e t t l g ( t e h d e ) d ( y ) d e n e w i 非 t , c p , t e 新 W l n ) h e 新 w 的 i d u n i 和 n 的 W w d , l { e l k e w i i g l w d e w i d d e W , S . y S d g g t i 直 l w 相 l g e e , d 接 o i 同 o e t t g d t d t t , , n e e ) g ) , 会 e t a ; e ; 会 n 创 w ? c t 创 e 建 S t , 建 w e l n i e S l o e v n l l e t w a e e o m ) W t w m t e ; i e W e ) n d C i n ; t g h d t e i g t l e , d t ) O ) b j { e c t ? n e w S l o t ) { 大致归纳一下,如下表: ...

2021-09-29 · 22 min · 4639 words · CubeGao

如何在ListView中优雅的嵌套ListView

前言 日常的开发工作中,经常会遇到列表中嵌套ListView的需求。具体情况也分为两种。 1.竖向ListView嵌套横向ListView 常见的需求:竖向ListView行数不限制,横向ListView的列数不限制。具体情况如下图。 代码如下: L i s t V i e w ( c ] h , i T S ) l e t , d x a c ] r t c h , e ( k i I ) S P ) n ' ( l g , i o , : L d n c ) z s c ) i r o h , e i h , [ s e r i o c d t i s i t n e l p h B i l c t V : P d a i o o d r e i o : c l x n : o m e [ i i d ( e l B w n O t : w d L l u ' t p y i . i D i ) e a : I d f s i l , r c t t i t r d ( i 0 e h l V e e t . m : l i c r y 0 ( ( e t : ( , ) d w i , o . o u b n _ b u : , l i e l A i . d x n i e i d n r s e f ( . x i h ) n o i r = t i > y z ) o I , n t t e a m l ( , ) , Stack存在无Positioned包裹的子组件,决定Stack尺寸的是无位置的组件中的最大的尺寸,Positioned不会影响Stack的尺寸。(当然如果子类全部是Positioned包裹的子组件,Stack会将自身尺寸设置为父级布局约束所允许的最大尺寸,为对齐子组件创造条件。)。 此处用Positioned.fill,让ListView尽量占满整个Stack。 接下来设置宽度,利用SizedBox的向下传递约束,向上传递尺寸的特性。用SizedBox(width: double.infinity)把宽度撑开到整个屏幕的宽度。 接下来算出Item的高度,用Item的高度撑开Stack的高度。 此处用Item的初衷只是为了定制高度,不能让Item显示在屏幕上,所以用Opacity隐藏Item,为了忽略掉事件响应,最后用上IgnorePointer。 ...

2021-07-06 · 3 min · 531 words · CubeGao

如何解决Flutter中的依赖冲突

前言 今天更新第三方依赖库,执行flutter pub get后,报错了 Because project depends on path_provider 1.6.8 which doesn’t match any versions, version solving failed. pub get failed (1; Because project depends on path_provider 1.6.8 which doesn’t match any versions, version solving failed.) Process finished with exit code 1 分析为什么会出现冲突 查看文件pubspec.yaml是这个样子的 d e p p e a n t d h e _ n p c r i o e v s i : d e r : ^ 1 . 6 . 8 我们在开发Flutter项目中,经常会依赖第三方库。如果两个第三方库,同时依赖了某个库。比如库A要求path_provider最低版本为1.5.0,库B要求path_provider最低版本为1.6.8,当path_provider的版本更新为1.6.8时,虽然符合库B的要求,但是不符合库A的要求,这个时候就冲突报错了。 ...

2021-04-22 · 2 min · 276 words · CubeGao

Flutter中的BuildContext到底是什么

前言 首先我们看看官方的定义: [BuildContext] objects are actually [Element] objects. The [BuildContext] interface is used to discourage direct manipulation of [Element] objects. [BuildContext] 对象实际上是 [Element] 对象。 [BuildContext] 接口用于阻止直接操作 [Element] 对象。 根据官方这段注释,可以了解到BuildContext 实际上就是 Element 对象,产生的意义主要是为了防止开发者直接操作 Element 对象。 如何使用BuildContext 写一段大家很熟悉的代码 c } l a i } s n o s t i s } d e ) _ _ t _ ; M c _ S c y o i t o H u n a u o n c t n m t r e t e e e ( e P r m ( r a e ) + g = n + e t { ; S 0 C t ; o a u t n e t e e r x ( t ) e n { d s S t a t e < M y H o m e P a g e > { 这里有同学好奇,_counter++;不写在setState的回调方法里可以生效吗?答案是可以的。但是我更推荐把和状态更新相关的操作,放在回调里面。这样更加的工程化管理。因为一个项目经过长时期的更新,你可能无法分清,这个方法里面的setState,是不是实际上起作用了,会造成代码冗余。 ...

2021-03-11 · 7 min · 1413 words · CubeGao

给AsyncDisplayKit(Texture)替换网络图片下载缓存框架

前言 为了方便管理图片缓存,统一缓存文件夹和缓存方法,将AsyncDisplayKit自带的PINRemoteImage插件, 替换为项目在用的Kingfisher、SDWebImage。 1import Kingfisher 2 3extension ASNetworkImageNode { 4 static func imageNode() -> ASNetworkImageNode { 5 return ASNetworkImageNode(cache: ASImageManager.shared, downloader: ASImageManager.shared) 6 } 7} 8 9class ASImageManager: NSObject, ASImageDownloaderProtocol, ASImageCacheProtocol { 10 11 static let shared = ASImageManager() 12 private override init() {} 13 14 func downloadImage(with url: URL, callbackQueue: DispatchQueue, downloadProgress: ASImageDownloaderProgress?, completion: @escaping ASImageDownloaderCompletion) -> Any? { 15 16 ImageDownloader.default.downloadTimeout = 30.0 17 var operation: DownloadTask? 18 operation = ImageDownloader.default.downloadImage(with: url, options: nil, progressBlock: { (received, expected) in 19 if downloadProgress != nil { 20 callbackQueue.async(execute: { 21 let progress = expected == 0 ? 0 : received / expected 22 downloadProgress?(CGFloat(progress)) 23 }) 24 } 25 }) { (result) in 26 switch result { 27 case .success(let value): 28 callbackQueue.async(execute: { completion(value.image, nil, nil, nil) }) 29 ImageCache.default.store(value.image, original: value.originalData, forKey: url.cacheKey, toDisk: true) 30 case .failure(let error): 31 callbackQueue.async(execute: { completion(nil, error, operation, nil) }) 32 dPrint("Job failed: \(error.localizedDescription)") 33 } 34 } 35 36 return operation 37 } 38 39 func cancelImageDownload(forIdentifier downloadIdentifier: Any) { 40 if let task = downloadIdentifier as? DownloadTask { 41 task.cancel() 42 } 43 } 44 45 func cachedImage(with url: URL, callbackQueue: DispatchQueue, completion: @escaping ASImageCacherCompletion) { 46 ImageCache.default.retrieveImage(forKey: url.cacheKey) { (result) in 47 switch result { 48 case .success(let value): 49 callbackQueue.async { completion(value.image) } 50 case .failure(let error): 51 dPrint("Job failed: \(error.localizedDescription)") 52 } 53 } 54 } 55} 1import SDWebImage 2 3extension ASNetworkImageNode { 4 static func imageNode() -> ASNetworkImageNode { 5 return ASNetworkImageNode(cache: ASNetImageManage.shared, downloader: ASNetImageManage.shared) 6 } 7} 8 9class ASNetImageManage: NSObject, ASImageDownloaderProtocol, ASImageCacheProtocol { 10 11 static let shared = ASNetImageManage() 12 13 func downloadImage(with URL: URL, callbackQueue: DispatchQueue, downloadProgress: ASImageDownloaderProgress?, completion: @escaping ASImageDownloaderCompletion) -> Any? { 14 15 weak var weakOperation: SDWebImageOperation? 16 let operation = SDWebImageManager.shared.loadImage(with: URL, options: .retryFailed, progress: { (received, expected, url) in 17 if downloadProgress != nil { 18 callbackQueue.async(execute: { 19 let progress = expected == 0 ? 0 : received / expected 20 downloadProgress?(CGFloat(progress)) 21 }) 22 } 23 }) { (cachedImage, data, error, type, unknow, url) in 24 if let image = cachedImage { 25 callbackQueue.async(execute: { completion(image, nil, nil, nil) }) 26 return 27 } 28 callbackQueue.async(execute: { completion(nil, error, nil, nil) }) 29 } 30 weakOperation = operation 31 return weakOperation 32 } 33 34 func cancelImageDownload(forIdentifier downloadIdentifier: Any) { 35 if let downloadIdentifier = downloadIdentifier as? SDWebImageOperation { 36 downloadIdentifier.cancel() 37 } 38 } 39 40 func cachedImage(with URL: URL, callbackQueue: DispatchQueue, completion: @escaping ASImageCacherCompletion) { 41 42 if let key = SDWebImageManager.shared.cacheKey(for: URL) { 43 SDWebImageManager.shared.imageCache.queryImage(forKey: key, options: .allowInvalidSSLCertificates, context: nil) { (cachedImage, data, type) in 44 if let image = cachedImage { 45 callbackQueue.async(execute: { completion(image) }) 46 return 47 } 48 callbackQueue.async(execute: { completion(nil) }) 49 } 50 }else { 51 callbackQueue.async { 52 completion(nil) 53 } 54 } 55 56 } 57}

2020-08-03 · 3 min · 474 words · CubeGao

Flutter 嵌套过深的解决方案

写代码容易,读代码难。功能是都实现了,但是对维护人员来说,简直就是灾难。 ##背景 Flutter 注重组合而非继承,要想搭建出 UI,需要组合不同功能的 Widget,如布局 Widget、响应 Widget、控件 Widget 等才能搭建出一个功能完善的UI界面,这便导致了嵌套地狱: 在顶级Widget的构造器中内嵌众多 Widget。 像下面这个例子,其实都不是最多层的,只要你卖力,可以超乎想象的))))))))))))))))))..... c } l a @ W } s o i s v d r ) e g e ; F r e t b ) r r t u o , o i r d c ] s d b n y h , t e u : i n n ) e i n l e e , d l e n d w w c ) G d w e r h , l ( w e C c c C i c ) a B S n o o h e l h , s u c S : n n i n d i f c ) s i a t s s l t : l i h , l f a < t t d e d l i o c ) e d f c W r r : r n : t l p h , x C o k i a a ( e e d a i w h d ) c ) t o l ( d i i n w n r : c l i e e , h , e n d g n n e e : i d d i c c i c n t ( e e t w C w O t : t g o o l h d e t d s l I p y h h r l d i s x > B : F i B m a : n : t a o : l t [ o l p a a c e : t r d s S x c u R c g i 0 w 2 i : n : t t c ( o t e k e t . 0 2 o e y a o n t c d F y 5 C 0 0 n C w n l t n s e t r i ( , o . 0 : o e e e t t r ( o l n 0 . l C w : l e L p t t , 0 n o e e x B o F e a , e r n T T s t o g i r i w s t e h s ) x o l . n . e x e W C ( t b e B g r t m i { o ) e l r o r ( ( e d n ) r u ( x e ' . g s , ( r D y F o e t ( e . r f t r s c s o ( a i o h s c { i g r a t o n m a d e n t a t e d t s X i 2 ' e . : o 0 , x e n 0 t x 1 ( , ) p 0 . a . t n 0 e d , x ( t ) s T , i h g e m m a e Y . : h e 1 a 0 d . l 0 i ) n , e 2 ) , 在IDE上面看到是这样的。 ...

2020-01-03 · 37 min · 7793 words · CubeGao

给斐讯 K3 安装 Merlin 系统

给K3改造好散热之后,打算把系统从官改系统升级成梅林系统。 主要是因为在改造散热的过程,我发现这台K3的闪存是mxic闪存,而并非是常见的三星闪存。 因为三星闪存对梅林的兼容性不好,容易导致坏块的产生。 ##开始刷机 如果固件版本很老,像我这台的固件版本 V21.6.12.66,那就可以直接刷了。 如果固件版本很新,并且不能使用漏洞降级,那你可能要拆机,然后用TTL刷机了。 首先是激活Telnet,使用这个工具。 原理就是利用web注入的方式。 重点来了,第一步备份路由器固件,防止刷机失败了,可以恢复原本的固件。 t m c m c c c c e o d k d a a a l u d t t t n n / i b e t t r a / / / t m c d d d p b k e e e 1 # / a u v 9 s c p / / / 2 查 h k m m m . 看 a u t t t 1 当 r p d d d 6 前 e b b b 8 挂 / l l l . 载 s o o o 2 的 d c c c . a k k k 1 U 1 0 5 6 / 盘 > > > m m m t t t d d d b b b l l l o o o c c c k k k 0 5 6 . . . b b b i i i n n n # # # 备 备 备 份 份 份 设 固 C 备 件 F 信 , E 息 备 份 固 件 时 间 可 能 有 些 长 1 m i n 左 右 K3固件分区如下: ...

2019-12-03 · 7 min · 1466 words · CubeGao

容易被人忽略的 NSCache

第一次见到NSCache,是在SDWebImage中。SDWebImage的内存缓存机制就是通过NSCache完成的。所以可能你不太了解这个类,但是其实一直在使用它。 为什么要使用NSCache? 我们通常用缓存来临时存储短时间使用但创建昂贵的对象。重用这些对象可以优化性能,因为它们的值不需要重新计算。另外一方面,这些对象对于程序来说不是紧要的,在内存紧张时会被丢弃。如果对象被丢弃了,则下次使用时需要重新计算。 NSCache是一个可变的集合,主要用来存储key-value对。它有着和NSMutableDictionary类似的API。实际上,NSCache就像是一个会自动移除对象来释放内存的NSMutableDictionary。 ( i d o o o o ) i i i i o d d d d b ) ) ) ) j s s r r e e e e e c t t m m t O O o o F b b v v o j j e e r e e O A K c c b l e t t j l y : : e O : ( ( c b ( i i t j i d d F e d ) ) o c ) o o r t k b b K s e j j e y y f f : o o ( r r i K K d e e ) y y k : : e ( ( y i i d d ) ) k k e e y y c o s t : ( N S U I n t e g e r ) n u m NSCahce与可变集合不同之处: ...

2019-11-11 · 2 min · 231 words · CubeGao

通过 SSH 连接 iOS 设备的几种方法

必备工具 一台已经越狱的iPhone 一台PC 方案一 打开Cydia,搜索OpenSSH,安装。 保证PC和iPhone在同一局域网下,然后连接SSH。默认的账户是root,密码是alpine。地址是设备的局域网地址。 $ s s h r o o t @ 1 9 2 . 1 6 9 . 5 0 . 9 9 方案二 打开Cydia,卸载掉OpenSSH(如果安装了),然后添加源:http://cydia.ichitaso.com/test。(如果iPhone重启了,打开Cydia闪退,记得先Re-Jailbreak) 在Cydia中搜索dropbear,安装。 ![d51c54fa7d0b45e28ee73ac6e7124417.png][2] 然后重复方案一的2.,直接SSH连接即可。 遇到的问题 如果wifi连接iPhone设备一直失败怎么办? 我们可以借助usbmuxd这个工具通过USB连接。 usbmuxd 是苹果的一个服务,这个服务主要用于在USB协议上实现多路TCP连接,将USB通信抽象为TCP通信。苹果的iTunes、Xcode,都直接或间接地用到了这个服务。它提供了一个USB - TCP的转换服务。 安装usbmuxd。 $ b r e w i n s t a l l u s b m u x d 建立映射关系。 $ $ i w p a r i o t x i y n g 1 2 f 3 o 4 r 2 c 2 o n n e c t i o n 这样当前连接设备的22端口(SSH端口)映射到电脑的1234端口,因此想和设备通信,直接和本地端口1234通信就可以了。 ...

2019-10-20 · 1 min · 121 words · CubeGao

给斐讯 K3 改造散热

天气越来越热,路由器温度也越来越高,由于K3的劣质硅脂垫在高温下容易出油,加上在硬件设计中,无线模块放在主板下方,硅脂垫流出的油会滴到无线模块上,然后导致无线模块丢失,最常见的表现就是2.4G信号或者5G信号丢失。 ##购买配件 本身K3硬件设计就是没有风扇的被动散热,所以我预想的改动也就是增强被动散热。如果加风扇,散热效果会更加明显,但是风扇耗电,可能会导致主机电源不稳定,从而造成网速波动。 这套配件主要包含铜片,硅脂垫,硅脂。 ###拆机 由于是卡扣式的安装,拆机只能用巧劲来拆,实际上花了半个多小时才拆开,然后卡扣还断了一根,算及格吧。 ![IMG_4286.JPG][5] 我这台K3是刚从某转上淘来的。是全新的,但是由于气温高,可以看一下,无线板子上都已经油迹斑斑了。 ![IMG_4287.JPG][6] 然后开始给CPU和无线板开始涂硅脂,涂硅脂有个小技巧就是不要涂太厚,然后中间留个小坨,压下去后,就会更均匀了。 ![IMG_4285.JPG][4] 硅脂涂好后,开始安装铜片。 大铜片是安装到无线板和散热片中间的,主要是增加接触面积来达到散热功能。 ![IMG_4283.JPG][2] 最后开始组装了。 ![IMG_4284.JPG][3]

2019-10-15 · 1 min · 16 words · CubeGao