網(wǎng)站首頁 編程語言 正文
引言
剛開始接觸flutter的時候,Container
組件是用得最多的。它就像HTML中的div一樣普遍,專門用來布局頁面的。
但是使用Container嵌套布局的時候,經(jīng)常出現(xiàn)一些令人無法理解的問題。就如下面代碼,在一個固定的容器中,子組件卻鋪滿了全屏。
/// 例一 @override Widget build(BuildContext context) { return Container( width: 300, height: 300, color: Colors.amber, child: Container(width: 50, height: 50, color: Colors.red,), ); }
然后要加上alignment
屬性,子組件正常顯示了,但容器還是鋪滿全屏。
/// 例二 @override Widget build(BuildContext context) { return Container( width: 300, height: 300, color: Colors.amber, alignment: Alignment.center, child: Container(width: 50, height: 50, color: Colors.red,), ); }
而在容器外層添加一個Scaffold
組件,它就正常顯示了。
/// 例三 @override Widget build(BuildContext context) { return Scaffold( body: Container( width: 300, height: 300, color: Colors.amber, alignment: Alignment.center, child: Container(width: 50, height: 50, color: Colors.red,), ), ); }
這一切的怪異行為困擾了我很久,直到我深入了flutter布局的學習,才漸漸解開這些疑惑。
1、flutter的widget類型
flutter的widget可以分為三類,組合類ComponentWidget、代理類ProxyWidget和繪制類RenderObjectWidget
組合類:如Container
、Scaffold
、MaterialApp
還有一系列通過繼承StatelessWidget和StatefulWidget的類。組合類是我們開發(fā)過程中用得最多的組件。
代理類:InheritedWidget
,功能型組件,它可以高效快捷的實現(xiàn)共享數(shù)據(jù)的跨組件傳遞。如常見的Theme
、MediaQuery
就是InheritedWidget的應(yīng)用。
繪制類:屏幕上看到的UI幾乎都會通過RenderObjectWidget
實現(xiàn)。通過繼承它,可以進行界面的布局和繪制。如Align
、Padding
、ConstrainedBox
等都是通過繼承RenderObjectWidget,并通過重寫createRenderObject方法來創(chuàng)建RenderObject對象,實現(xiàn)最終的布局(layout)和繪制(paint)。
2、Container是個組合類
顯而易見Container繼承StatelessWidget,它是一個組合類,同時也是一個由DecoratedBox
、ConstrainedBox
、Transform
、Padding
、Align
等組件組合的多功能容器。可以通過查看Container類,看出它實際就是通過不同的參數(shù)判斷,再進行組件的層層嵌套來實現(xiàn)的。
@override Widget build(BuildContext context) { Widget? current = child; if (child == null && (constraints == null || !constraints!.isTight)) { current = LimitedBox( maxWidth: 0.0, maxHeight: 0.0, child: ConstrainedBox(constraints: const BoxConstraints.expand()), ); } else if (alignment != null) { current = Align(alignment: alignment!, child: current); } final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration; if (effectivePadding != null) { current = Padding(padding: effectivePadding, child: current); } if (color != null) { current = ColoredBox(color: color!, child: current); } if (clipBehavior != Clip.none) { assert(decoration != null); current = ClipPath( clipper: _DecorationClipper( textDirection: Directionality.maybeOf(context), decoration: decoration!, ), clipBehavior: clipBehavior, child: current, ); } if (decoration != null) { current = DecoratedBox(decoration: decoration!, child: current); } if (foregroundDecoration != null) { current = DecoratedBox( decoration: foregroundDecoration!, position: DecorationPosition.foreground, child: current, ); } if (constraints != null) { current = ConstrainedBox(constraints: constraints!, child: current); } if (margin != null) { current = Padding(padding: margin!, child: current); } if (transform != null) { current = Transform(transform: transform!, alignment: transformAlignment, child: current); } return current!; }
組合類基本不參與ui的繪制,都是通過繪制類的組合來實現(xiàn)功能。
3、flutter布局約束
flutter中有兩種布局約束BoxConstraints盒約束和SliverConstraints線性約束,如Align、Padding、ConstrainedBox使用的是盒約束。
BoxConstraints盒約束是指flutter框架在運行時遍歷整個組件樹,在這過程中 「向下傳遞約束,向上傳遞尺寸」,以此來確定每個組件的尺寸和大小。
BoxConstraints類由4個屬性組成,最小寬度minWidth、最大寬度maxWidth、最小高度minHeight、最大高度maxHeight。
BoxConstraints({ this.minWidth, this.maxWidth, this.minHeight, this.maxHeight, });
根據(jù)這4個屬性的變化,可以分為“緊約束(tight)”、“松約束(loose)”、“無界約束”、“有界約束”。
緊約束:最小寬(高)度和最大寬(高)度值相等,此時它是一個固定寬高的約束。
BoxConstraints.tight(Size size) : minWidth = size.width, maxWidth = size.width, minHeight = size.height, maxHeight = size.height;
松約束:最小寬(高)值為0,最大寬(高)大于0,此時它是一個約束范圍。
BoxConstraints.loose(Size size) : minWidth = 0.0, maxWidth = size.width, minHeight = 0.0, maxHeight = size.height;
無界約束:最小寬(高)和最大寬(高)值存在double.infinity(無限)。
BoxConstraints.expand({double? width, double? height}) : minWidth = width ?? double.infinity, maxWidth = width ?? double.infinity, minHeight = height ?? double.infinity, maxHeight = height ?? double.infinity;
有界約束:最小寬(高)和最大寬(高)值均為固定值。
BoxConstraints(100, 300, 100, 300)
4、Container布局行為解惑
了解了BoxConstraints布局約束,回到本文最開始的問題。
/// 例一 @override Widget build(BuildContext context) { return Container( width: 300, height: 300, color: Colors.amber, child: Container(width: 50, height: 50, color: Colors.red,), ); }
例一中,兩個固定寬高的Container,為什么子容器鋪滿了全屏?
根據(jù)BoxConstraints布局約束,遍歷整個組件樹,最開始的root是樹的起點,它向下傳遞的是一個緊約束。因為是移動設(shè)備,root即是屏幕的大小,假設(shè)屏幕寬414、高896。于是整個布局約束如下:
這里有個問題,就是Container分明已經(jīng)設(shè)置了固定寬高,為什么無效?
因為父級向下傳遞的約束,子組件必須嚴格遵守。這里Container容器設(shè)置的寬高超出了父級的約束范圍,就會自動被忽略,采用符合約束的值。
例一兩上Container都被鋪滿屏幕,而最底下的紅色Container疊到了最上層,所以最終顯示紅色。
/// 例二 @override Widget build(BuildContext context) { return Container( width: 300, height: 300, color: Colors.amber, alignment: Alignment.center, child: Container(width: 50, height: 50, color: Colors.red,), ); }
例二也同樣可以根據(jù)布局約束求證,如下圖:
這里Container為什么是ConstrainedBox
和Align
組件?前面說過Container是一個組合組件,它是由多個原子組件組成的。根據(jù)例二,它是由ConstrainedBox和Align嵌套而成。
Align提供給子組件的是一個松約束,所以容器自身設(shè)置50寬高值是在合理范圍的,因此生效,屏幕上顯示的就是50像素的紅色方塊。ConstrainedBox受到的是緊約束,所以自身的300寬高被忽略,顯示的是鋪滿屏幕的黃色塊。
/// 例三 @override Widget build(BuildContext context) { return Scaffold( body: Container( width: 300, height: 300, color: Colors.amber, alignment: Alignment.center, child: Container(width: 50, height: 50, color: Colors.red,), ), ); }
例三中Scaffold
向下傳遞的是一個松約束,所以黃色Container的寬高根據(jù)自身設(shè)置的300,在合理的范圍內(nèi),有效。Container再向下傳遞的也是松約束,最終紅色Container寬高為50。
這里還有個問題,怎么確定組件向下傳遞的是緊約束還是松約束?
這就涉及到組件的內(nèi)部實現(xiàn)了,這里通過Align舉個例。
Align
是一個繪制組件,它能夠進行界面的布局和繪制,這是因為Align的繼承鏈為:
Align -> SingleChildRenderObjectWidget -> RenderObjectWidget
Align需要重寫createRenderObject方法,返回RenderObject的實現(xiàn),這里Align返回的是RenderPositionedBox,所以核心內(nèi)容就在這個類中
class Align extends SingleChildRenderObjectWidget { /// ... @override RenderPositionedBox createRenderObject(BuildContext context) { return RenderPositionedBox( alignment: alignment, widthFactor: widthFactor, heightFactor: heightFactor, textDirection: Directionality.maybeOf(context), ); } /// ... }
而RenderPositionedBox類中,重寫performLayout方法,該方法用于根據(jù)自身約束條件,計算出子組件的布局,再根據(jù)子組件的尺寸設(shè)置自身的尺寸,形成一個至下而上,由上到下的閉環(huán),最終實現(xiàn)界面的整個繪制。
RenderPositionedBox -> RenderAligningShiftedBox -> RenderShiftedBox -> RenderBox
class RenderPositionedBox extends RenderAligningShiftedBox { /// ... @override void performLayout() { final BoxConstraints constraints = this.constraints; // 自身的約束大小 final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity; final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity; /// 存在子組件 if (child != null) { /// 開始布局子組件 child!.layout(constraints.loosen(), parentUsesSize: true); /// 根據(jù)子組件的尺寸設(shè)置自身尺寸 size = constraints.constrain(Size( shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity, shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity, )); /// 計算子組件的位置 alignChild(); } else { /// 不存在子組件 size = constraints.constrain(Size( shrinkWrapWidth ? 0.0 : double.infinity, shrinkWrapHeight ? 0.0 : double.infinity, )); } } /// ... }
根據(jù)Align中performLayout方法的實現(xiàn),可以確定該組件最終會給子組件傳遞一個怎么樣的約束。
/// constraints.loosen提供的是一個松約束 child!.layout(constraints.loosen(), parentUsesSize: true);
/// loosen方法 BoxConstraints loosen() { assert(debugAssertIsValid()); /// BoxConstraints({double minWidth = 0.0, double maxWidth = double.infinity, double minHeight = 0.0, double maxHeight = double.infinity}) return BoxConstraints( maxWidth: maxWidth, maxHeight: maxHeight, ); }
其它繪制類的組件基本跟Align大同小異,只要重點看performLayout方法的實現(xiàn),即可判斷出組件提供的約束條件。
總結(jié)
1、flutter的widget分為,組合類、代理類和繪制類。
2、Container是一個組合類,由DecoratedBox、ConstrainedBox、Transform、Padding、Align等繪制組件組合而成。
3、flutter中有兩種布局約束BoxConstraints盒約束和SliverConstraints線性約束。
4、BoxConstraints的約束原理是:?「向下傳遞約束,向上傳遞尺寸」。
5、BoxConstraints的約束類型為:緊約束、松約束、無界約束、有界約束。
6、判斷一個繪制組件的約束行為可以通過查看performLayout方法中l(wèi)ayout傳入的約束值。
原文鏈接:https://juejin.cn/post/7183549888406224955
相關(guān)推薦
- 2022-04-05 Python+Opencv答題卡識別用例詳解_python
- 2022-02-05 Tableau:如何處理Excel中一個sheet中有多張表的問題?
- 2022-08-26 go-micro開發(fā)RPC服務(wù)以及運行原理介紹_Golang
- 2022-05-21 Unity利用XML制作一個簡易的登錄系統(tǒng)_C#教程
- 2022-09-20 ?C++模板template原理解析_C 語言
- 2022-11-02 Go?語言簡單實現(xiàn)Vigenere加密算法_Golang
- 2022-05-19 C++實現(xiàn)教師管理系統(tǒng)_C 語言
- 2022-09-27 Android內(nèi)置的OkHttp用法介紹_Android
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支