摘要:但是好像反其道而行之,樣式糅合在結(jié)構(gòu)里面,這樣究竟有啥意思尼首先應(yīng)該是一個(gè)性能的考慮,瀏覽器解析其實(shí)也是一個(gè)性能消耗點(diǎn),沒(méi)有解析自然也可以加快頁(yè)面的顯示。
開(kāi)始
搞前端的同學(xué)可能都習(xí)慣了CSS局部的思維,過(guò)去也出現(xiàn)過(guò)一些跟布局或者樣式相關(guān)的標(biāo)簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經(jīng)不推薦使用。但是在Flutter里面,是沒(méi)有CSS這樣一個(gè)概念的,布局和樣式都可能會(huì)是一個(gè)組件或者是組件里面的屬性所定義和實(shí)現(xiàn)的,對(duì)于習(xí)慣寫(xiě)樣式的前端同學(xué)可能需要適應(yīng)一下。
個(gè)人思考現(xiàn)在可能要想一下,F(xiàn)lutter為啥沒(méi)有像瀏覽器一樣抽離出CSS?
我們知道在瀏覽器里面JS,CSS,HTML各司其職:行為,表現(xiàn)和結(jié)構(gòu),已經(jīng)深入人心,也被很多人所推崇。但是Flutter好像反其道而行之,樣式糅合在結(jié)構(gòu)里面,這樣究竟有啥意思尼?
首先應(yīng)該是一個(gè)性能的考慮,瀏覽器解析CSS其實(shí)也是一個(gè)性能消耗點(diǎn),沒(méi)有CSS解析自然也可以加快頁(yè)面的顯示。
其次再討論一下CSS,CSS確實(shí)非常適合描述樣式和布局,但是也有很明顯的缺點(diǎn):作用域全局性,代碼冗余,代碼難以重用,難以模塊化等;我們修修補(bǔ)補(bǔ),又創(chuàng)造了less,sass等工具幫助我們?nèi)ソ鉀Q問(wèn)題,但是自身的缺陷依然會(huì)存在,甚至有點(diǎn)鉆牛角尖,因?yàn)榇嬖诹薈SS,所以只能改進(jìn)CSS。
而在Flutter,沒(méi)有了CSS,以上的問(wèn)題自然蕩然無(wú)存,那么描述樣式會(huì)不會(huì)變得很麻煩?大道行之,我們的前輩們?cè)缇驮诖a上總結(jié)出很多設(shè)計(jì)模式或者技術(shù)去解決代碼重用,代碼冗余,模塊化的問(wèn)題,為什么我們不去用已經(jīng)存在很久而且行之有效的技術(shù)去解決問(wèn)題尼。自然把樣式糅合進(jìn)結(jié)構(gòu)會(huì)增加信息量,對(duì)我們閱讀代碼可能會(huì)是一個(gè)小小的挑戰(zhàn),但是應(yīng)該也會(huì)很快適應(yīng)下來(lái)的,我相信。
我們很多時(shí)候都在創(chuàng)造新的工具的解決問(wèn)題,其實(shí)也有可能創(chuàng)造出新的問(wèn)題,有時(shí)候回歸根本,不一定是一件壞事。
主要控制文字方向
Widget build(BuildContext context) { return new Container( color: Colors.white, child: new Text("我是一段文本") ); }
加入控件后
Widget build(BuildContext context) { return new Container( color: Colors.white, child: new Directionality( textDirection: TextDirection.rtl, child: new Text("我是一段文本") ) ); }DefaultTextStyle
跟文本相關(guān)的還有一個(gè)DefaultTextStyle控件,提供了更多的控制選項(xiàng):textAlign,softWrap,style和maxLines等,都是控制整體:換行,文字居中和多行省略等,相對(duì)style提供都是文字自身樣式相關(guān):字重,字體大小等
const TextStyle({ this.inherit: true, this.color, this.fontSize, this.fontWeight, this.fontStyle, this.letterSpacing, this.wordSpacing, this.textBaseline, this.height, this.decoration, this.decorationColor, this.decorationStyle, this.debugLabel, String fontFamily, String package, })
演示一下效果:
Widget build(BuildContext context) { return new Container( color: Colors.white, child: new Directionality( textDirection: TextDirection.ltr, child: new DefaultTextStyle( style: new TextStyle( fontSize: 14.0, color: Colors.blue, decoration: TextDecoration.underline ), maxLines: 2, softWrap: true, overflow: TextOverflow.ellipsis, child: new Text("我是一段超長(zhǎng)的文本啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦" "啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦" "啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦" "啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦") ) ) ); }
其實(shí)Text控件就已經(jīng)帶上這些屬性:
const Text(this.data, { Key key, this.style, this.textAlign, this.textDirection, this.softWrap, this.overflow, this.textScaleFactor, this.maxLines, })
為什么又要獨(dú)立出這些控件專(zhuān)門(mén)管理呢,我們知道CSS屬性里面有些屬性時(shí)繼承父元素的,例如:字體大小,顏色等;這樣的話,我們很容易統(tǒng)一一個(gè)模塊里面的樣式,并不需要每個(gè)元素都要去設(shè)置一遍,這里的這些控件也是起到這樣的功能,其實(shí)除了些字體樣式還有很多地方會(huì)有這種繼承關(guān)系,例如:主題顏色,語(yǔ)言文字等等。所以后面Text控件很容易從控件樹(shù)上找到這些父控件,獲取它們?cè)O(shè)置的屬性,就這樣就可以把父控件的樣式繼承下來(lái)。
怎么做到的呢,無(wú)論Directionality還是DefaultTextStyle都是InheritedWidget的子類(lèi),InheritedWidget實(shí)現(xiàn)了一個(gè)發(fā)布/訂閱的模式,當(dāng)子控件調(diào)用inheritFromWidgetOfExactType方法獲取父控件時(shí),同時(shí)也把自己加入到InheritedWidget的訂閱者列表里面,所以當(dāng)InheritedWidget屬性改變的時(shí)候,就會(huì)調(diào)起子組件didChangeDependencies方法去通知子組件。
這個(gè)控件感覺(jué)必須得介紹一下,因?yàn)樵谇岸宋覀冇幸粋€(gè)canvas元素,可以提供給我們直接去繪制元素,給了我們很大的靈活性,那么Flutter中對(duì)應(yīng)的應(yīng)該就是這個(gè)控件了。
如何使用:
先繼承CustomPainter
class CustomPainterSample extends CustomPainter { double progress; CustomPainterSample({this.progress: 0.0}); @override void paint(Canvas canvas, Size size) { Paint p = new Paint(); p.color = Colors.green; p.isAntiAlias = true; p.style = PaintingStyle.fill; canvas.drawCircle(size.center(const Offset(0.0, 0.0)), size.width / 2 * progress, p); } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } }
這里我畫(huà)了一個(gè)綠色的圓,然后把這個(gè)CustomPainterSample傳到CustomPaint控件。
Widget build(BuildContext context) { return new Container( color: Colors.white, child: new CustomPaint( painter: new CustomPainterSample(progress: this.progress), ) ); }
當(dāng)然了,既然可以隨便畫(huà)點(diǎn)東西,做點(diǎn)動(dòng)畫(huà)也是妥妥的,好再加個(gè)放大的動(dòng)畫(huà),完整代碼:
class SquareFragmentState extends Statewith TickerProviderStateMixin { double progress = 0.0; @override void initState() { AnimationController ac = new AnimationController( vsync: this, duration: const Duration(milliseconds: 10000) ); ac.addListener(() { this.setState(() { this.progress = ac.value; }); }); ac.forward(); } @override Widget build(BuildContext context) { return new Container( color: Colors.white, child: new CustomPaint( painter: new CustomPainterSample(progress: this.progress), ) ); } }
這里mixin了TickerProviderStateMixin,里面有一個(gè)createTicker方法,主要是監(jiān)聽(tīng)每一幀生成然后回調(diào),主要是由SchedulerBinding.instance.scheduleFrameCallback方法所驅(qū)動(dòng)的。
ClipRRect剪切元素的邊界,這里類(lèi)似CSS的border-radius屬性;
Widget build(BuildContext context) { return new Container( color: Colors.white, child: new Align( alignment: Alignment.center, child: new ClipRRect( borderRadius: const BorderRadius.all(const Radius.circular(30.0)), child: new Container( width: 180.0, height: 180.0, color: Colors.red, ), ), ), ); }
效果:
把radius值調(diào)到90,變成了圓形:
類(lèi)似的可以剪切元素的還有ClipOval,ClipPath,這里就不深入介紹了。
PhysicalModel先看效果:
Widget build(BuildContext context) { return new Container( color: Colors.white, child: new Align( alignment: Alignment.center, child: new PhysicalModel( color: Colors.black, elevation: 6.0, child: new Container( width: 180.0, height: 180.0, color: Colors.red, ), ), ), ); }
可以看到紅色方塊底下有一個(gè)陰影,讓紅色方塊有一種懸浮的感覺(jué),有material design的風(fēng)格。
Transform類(lèi)似于CSS的transform屬性,可以提供沿著X,Y或者Z軸旋轉(zhuǎn),位移拉伸等效果。
Widget build(BuildContext context) { return new Container( color: Colors.white, child: new Align( alignment: Alignment.center, child: new Transform( transform: new Matrix4.rotationZ(PI / 2), child: new Container( color: Colors.black, child: new Text("垂直文字", style: const TextStyle(color: Colors.red),) ) ), ), ); }
得注意一下,Transform控件中的transformHitTests屬性,如果我們沿著X軸位移一個(gè)按鈕,一般來(lái)說(shuō),我們照樣可以直接點(diǎn)擊位移之后的按鈕,因?yàn)閠ransformHitTests為true的時(shí)候,在hitTest會(huì)判斷點(diǎn)擊落點(diǎn)是否在transfrom所做的操作(旋轉(zhuǎn),拉伸或者位移等)后的區(qū)域里面,但是如果為false,此時(shí)點(diǎn)擊按鈕原來(lái)的區(qū)域仍然會(huì)觸發(fā)點(diǎn)擊事件,但是直接點(diǎn)擊就不行了。
FractionalTranslation可以提供位移,但是并沒(méi)有Tranform控件提供那么多變換,僅僅是上下左右的位移,而且位移的基準(zhǔn)是以child的大小進(jìn)行的。
Widget build(BuildContext context) { return new Container( color: Colors.white, alignment: Alignment.center, child: new FractionalTranslation( translation: const Offset(1.0, 0.0), child: new Container( width: 100.0, height: 100.0, color: Colors.red, ), ) ); }
效果:
紅色方塊往右邊移動(dòng)了一個(gè)身位,就跟CSS中transfrom: translate(100%, 0)效果一樣的。
RotatedBox旋轉(zhuǎn)盒子,可以使用quarterTurns屬性控制旋轉(zhuǎn),每次旋轉(zhuǎn)quarterTurns * 90度。
Widget build(BuildContext context) { return new Container( color: Colors.white, alignment: Alignment.center, child: new RotatedBox( quarterTurns: -1, child: new Container( width: 100.0, height: 100.0, color: Colors.red, child: new Text("我倒轉(zhuǎn)了"), ), ) ); }Padding
在前端每個(gè)元素都基本會(huì)有border, margin, padding,但是在Flutter里面可能不得不吐槽連padding都要用個(gè)控件,未免太過(guò)于麻煩。對(duì)于此框架的開(kāi)發(fā)者們也有自己一套看法,在Flutter里面組合簡(jiǎn)單的控件去實(shí)現(xiàn)復(fù)雜的控件,而不是通過(guò)繼承去實(shí)現(xiàn)可以說(shuō)是Flutter的主要設(shè)計(jì)思想,所以你會(huì)發(fā)現(xiàn)盡管Container控件提供了padding的參數(shù),但其實(shí)它也背后也是通過(guò)創(chuàng)建Padding控件來(lái)實(shí)現(xiàn)效果的。
FittedBox在CSS中有background-position和background-size兩個(gè)屬性控制背景圖如何平鋪,例如:如果背景圖比元素尺寸大或者小的時(shí)候,是否要進(jìn)行拉伸,如果要拉伸,是拉伸圖片寬度還是拉伸圖片高度來(lái)適應(yīng)等等。
而FittedBox所做的事情也是差不多,它有兩個(gè)很重要的參數(shù):aligment 和 fit。
fit可取值:
BoxFit.fill
BoxFit.contain
BoxFit.cover
BoxFit.fitWidth
BoxFit.fitHeight
基本這個(gè)跟CSS的background-size取值都一樣的。
而aligment則是控制,當(dāng)子元素大小沒(méi)有完全占滿父元素的時(shí)候,如何定位,是居中還是靠左靠右。
雖然拿background-size來(lái)做對(duì)比,但是background-size只是控制背景圖片,而FittedBox幾乎可以對(duì)任何元素起作用,因?yàn)樗峭ㄟ^(guò)Transform放大縮小子元素來(lái)達(dá)到剛才所說(shuō)的效果。
Widget build(BuildContext context) { return new Container( color: Colors.white, alignment: Alignment.center, child: new Container( width: 200.0, height: 100.0, color: Colors.black child: new FittedBox( fit: BoxFit.fitHeight, alignment: Alignment.bottomRight, child: new Container( color: Colors.red, width: 300.0, height: 240.0, alignment: Alignment.center, child: new Text("AAA"), ), ) ) ); }
效果:
這里紅盒子大小是比黑盒子大的,但是fit為BoxFit.fitHeight就會(huì)通過(guò)拉伸高度來(lái)適應(yīng)黑盒子,如果把fit屬性改成BoxFit.fitWidth,效果就是這樣的:
可以看到字體是被直接縮小了。
SizedBox & ConstrainedBox為什么把兩個(gè)控件一起講呢?因?yàn)樗鼈兌家蕾嚵讼嗤腞enderObject:RenderConstrainedBox,而RenderConstrainedBox只有一個(gè)參數(shù):additionalConstraints。
而這個(gè)參數(shù)在performLayout中:
void performLayout() { if (child != null) { child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true); size = child.size; } else { size = _additionalConstraints.enforce(constraints).constrain(Size.zero); } }
而B(niǎo)oxConstraints.enforce方法:
BoxConstraints enforce(BoxConstraints constraints) { return new BoxConstraints( minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth), maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth), minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight), maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight) ); }
可見(jiàn)additionalConstraints是在原基礎(chǔ)constraints增加了自己的約束,但是并不會(huì)打破原來(lái)的約束條件。
FractionallySizedBox主要有三個(gè)參數(shù):aligment, widthFactor 和 heightFactor。
aligment參數(shù)控制child的定位;widthFactor 和 heightFactor 控制child的約束,如果widthFactor或者h(yuǎn)eightFactor不為null,會(huì)產(chǎn)生一個(gè)新的BoxConstraints:它的minWidth 和 maxWidth為原BoxConstraint.maxWidth widthFactor;minHeight 和 maxHeight為原BoxConstraint.maxHeight heightFactor。
代碼:
Widget build(BuildContext context) { return new Align( alignment: Alignment.center, child: new Container( color: Colors.green, width: 300.0, height: 300.0, child: new FractionallySizedBox( heightFactor: .5, widthFactor: .5, alignment: Alignment.topLeft, child: new Container( color: Colors.red, ) ) ) ); }
效果:
可以看到當(dāng)widthFactor和heigthFractor時(shí),紅色盒子寬高都為綠色的一半。
LimitedBox看名稱也知道跟控制尺寸有關(guān)了,這個(gè)控件主要有兩個(gè)參數(shù):maxWidth和maxHeight,當(dāng)constraints是unbounded的時(shí)候,也就是maxWidth和maxHeight都是infinite的時(shí)候,會(huì)用maxWidth和maxHeight替換原來(lái)的maxWidth和maxHeight,所以如果contraints是bounded的時(shí)候并不會(huì)起作用。
關(guān)鍵代碼:
BoxConstraints _limitConstraints(BoxConstraints constraints) { return new BoxConstraints( minWidth: constraints.minWidth, maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth), minHeight: constraints.minHeight, maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight) ); } @override void performLayout() { if (child != null) { child.layout(_limitConstraints(constraints), parentUsesSize: true); size = constraints.constrain(child.size); } else { size = _limitConstraints(constraints).constrain(Size.zero); } }
對(duì)比ConstrainedBox,明顯使用范圍就沒(méi)有那么廣了。
OverflowBox從前面的幾個(gè)控件:SizedBox,ConstrainedBox和LimitedBox分析知道,我們似乎沒(méi)有辦法打破由parent傳遞下來(lái)的約束條件,但是我們總會(huì)有一些情況是子組件的尺寸大于父組件的情況,那么怎么解決的尼?來(lái),就看這里的OverflowBox控件,這個(gè)控件提供了幾個(gè)參數(shù):minWidth,minHeight,maxWidth,maxHeight 和 aligment;先看代碼:
BoxConstraints _getInnerConstraints(BoxConstraints constraints) { return new BoxConstraints( minWidth: _minWidth ?? constraints.minWidth, maxWidth: _maxWidth ?? constraints.maxWidth, minHeight: _minHeight ?? constraints.minHeight, maxHeight: _maxHeight ?? constraints.maxHeight ); } void performLayout() { if (child != null) { child.layout(_getInnerConstraints(constraints), parentUsesSize: true); alignChild(); } }
這里可以看到直接使用我們傳入的參數(shù)替換了原本的minxWidth,maxWidth等,所以底下的組件可以根據(jù)新的約束條件來(lái)布局。
做一下demo:
Widget build(BuildContext context) { return new Align( alignment: Alignment.center, child: new Container( color: Colors.green, alignment: Alignment.center, width: 300.0, height: 300.0, child: new OverflowBox( maxWidth: double.INFINITY, maxHeight: double.INFINITY, child: new Container( height: 600.0, width: 200.0, color: Colors.red, ), ) ) ); }
效果:
如果沒(méi)有OverflowBox控件,紅色的盒子是不可能超過(guò)綠色盒子的;而aligment可以控制紅色盒子在綠色盒子里面的定位,現(xiàn)在是居中顯示的。
SizedOverflowBox剛才OverflowBox是因?yàn)槲覀冃薷牧思s束條件所以child布局大小確實(shí)被改變了,所以會(huì)發(fā)生溢出,而SizedOverflowBox這個(gè)控件并不會(huì)改變約束條件,但是它還是可能會(huì)發(fā)生溢出,為什么尼?因?yàn)镾izedOverflowBox可以讓控件看上去“變小一點(diǎn)”,這怎樣做到的尼?這個(gè)控件有一個(gè)參數(shù):size,這個(gè)參數(shù)就是讓我們決定這個(gè)控件看上去應(yīng)該多大。
關(guān)鍵代碼在RenderSizedOverflowBox類(lèi)中:
@override double computeMinIntrinsicWidth(double height) { return _requestedSize.width; } @override double computeMaxIntrinsicWidth(double height) { return _requestedSize.width; } @override double computeMinIntrinsicHeight(double width) { return _requestedSize.height; } @override double computeMaxIntrinsicHeight(double width) { return _requestedSize.height; } void performLayout() { size = constraints.constrain(_requestedSize); if (child != null) { child.layout(constraints); alignChild(); } }
示例代碼:
Widget build(BuildContext context) { return new Align( alignment: Alignment.center, child: new Container( color: Colors.green, alignment: Alignment.center, width: 300.0, height: 300.0, child: new SizedOverflowBox( size: new Size(200.0, 300.0), child: new Container( color: Colors.red ) ) ) ); }
截圖:
在CSS有一個(gè)屬性visibility,當(dāng)設(shè)置為hidden時(shí),元素是存在但是不會(huì)繪制出來(lái);在Flutter中Offstage也可以做到這種效果。
在RenderOffstage類(lèi)中:
class RenderOffstage extends RenderProxyBox { ... @override void performLayout() { if (offstage) { child?.layout(constraints); } else { super.performLayout(); } } @override bool hitTest(HitTestResult result, { Offset position }) { return !offstage && super.hitTest(result, position: position); } @override void paint(PaintingContext context, Offset offset) { if (offstage) return; super.paint(context, offset); } ... }
可見(jiàn)當(dāng)offstage為true時(shí),布局還是會(huì)繼續(xù)進(jìn)行的,但是paint方法里面會(huì)直接返回,hitTest方法也會(huì)直接跳過(guò),也就是不能響應(yīng)任何手勢(shì)。
AspectRatio這個(gè)控件可以用來(lái)讓子控件大小維持在一個(gè)固定寬高比,例如:16:9。
直接看布局算法:
Size _applyAspectRatio(BoxConstraints constraints) { if (constraints.isTight) return constraints.smallest; double width = constraints.maxWidth; double height; // We default to picking the height based on the width, but if the width // would be infinite, that"s not sensible so we try to infer the height // from the width. if (width.isFinite) { height = width / _aspectRatio; } else { height = constraints.maxHeight; width = height * _aspectRatio; } // Similar to RenderImage, we iteratively attempt to fit within the given // constraints while maintaining the given aspect ratio. The order of // applying the constraints is also biased towards inferring the height // from the width. if (width > constraints.maxWidth) { width = constraints.maxWidth; height = width / _aspectRatio; } if (height > constraints.maxHeight) { height = constraints.maxHeight; width = height * _aspectRatio; } if (width < constraints.minWidth) { width = constraints.minWidth; height = width / _aspectRatio; } if (height < constraints.minHeight) { height = constraints.minHeight; width = height * _aspectRatio; } return constraints.constrain(new Size(width, height)); }
簡(jiǎn)單分析一下:
如果constraints是tight,那么這個(gè)控件并不會(huì)起啥作用,所以這個(gè)控件一般需要Align控件包裹一下。
如果寬度不是Inifinte,它首先會(huì)選擇最大寬度,否則根據(jù)maxHeight來(lái)反推寬度。
萬(wàn)一高度超出約束條件,它就會(huì)反過(guò)來(lái),選擇最大的高度反推出寬度,那么萬(wàn)一寬度小于最小寬度,它又會(huì)根據(jù)最小寬度計(jì)算高度等等。
當(dāng)然最后還是會(huì)根據(jù)約束條件來(lái)規(guī)范最終的Size,所以可能出來(lái)效果是跟我們預(yù)設(shè)的寬高比不一致,但是這種情況應(yīng)該很少。
示例代碼:
Widget build(BuildContext context) { return new Align( alignment: Alignment.center, child: new Container( color: Colors.green, alignment: Alignment.center, width: 300.0, height: 300.0, child: new AspectRatio( aspectRatio: 2.0, child: new Container( color: Colors.red, ), ) ) ); }
截圖:
Sizes its child"s width to the child"s maximum intrinsic width.
說(shuō)實(shí)在這個(gè)控件看了半天沒(méi)想出用于哪些場(chǎng)景,搜了一下代碼,基本都用在一些浮窗上。布局過(guò)程是調(diào)用getMaxIntrinsicWidth方法遞歸詢問(wèn)子控件最大的intrinsicWidth,因?yàn)檫@個(gè)方法需要遞歸下去,如果每個(gè)控件都調(diào)用比較耗性能,當(dāng)獲取到intrinsicWidth,就會(huì)使用這個(gè)值作為約束條件(當(dāng)然也受到原始的約束條件約束),然后傳遞給child,所以正如上面的話所說(shuō),但是還是想不到哪些場(chǎng)景會(huì)需要。
Baseline正如圖上,基線可以影響著文字水平排布;如果兩段文字的基線不一樣,兩段文字的可能會(huì)出現(xiàn)一上一下,并不是在同一水平線上排布,就像這樣:
這是兩個(gè)Text控件,文字大小分別是12dp和32dp,所以他們的基線位置是不一樣的,所以這樣的排布并不是我們想要的,所以我們可以使用Baseline控件讓他們都在一樣的基線上,修改后:
這才是我們常見(jiàn)的,代碼如下:
Widget build(BuildContext context) { return new Wrap( children:[ new Baseline( baseline: 30.0, baselineType: TextBaseline.alphabetic, child: new Text( "AAAAA", style: new TextStyle( fontSize: 12.0, textBaseline: TextBaseline.alphabetic, ), ) ), new Baseline( baseline: 30.0, baselineType: TextBaseline.alphabetic, child: new Text( "BBB", style: new TextStyle( fontSize: 32.0, textBaseline: TextBaseline.alphabetic, ), ), ) ], );
把基線的位置都定義為30,兩段文字都會(huì)在來(lái)30的水平線上排布,就可以看到現(xiàn)在整齊的效果。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/89699.html
摘要:開(kāi)始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺(jué)比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深的體會(huì)。關(guān)于屬性,指前一個(gè)組件的布局區(qū)域和繪制區(qū)域重疊了。 開(kāi)始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺(jué)比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深...
摘要:開(kāi)始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺(jué)比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深的體會(huì)。關(guān)于屬性,指前一個(gè)組件的布局區(qū)域和繪制區(qū)域重疊了。 開(kāi)始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺(jué)比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深...
摘要:開(kāi)始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺(jué)比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深的體會(huì)。關(guān)于屬性,指前一個(gè)組件的布局區(qū)域和繪制區(qū)域重疊了。 開(kāi)始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺(jué)比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深...
摘要:但是好像反其道而行之,樣式糅合在結(jié)構(gòu)里面,這樣究竟有啥意思尼首先應(yīng)該是一個(gè)性能的考慮,瀏覽器解析其實(shí)也是一個(gè)性能消耗點(diǎn),沒(méi)有解析自然也可以加快頁(yè)面的顯示。 開(kāi)始 搞前端的同學(xué)可能都習(xí)慣了CSS局部的思維,過(guò)去也出現(xiàn)過(guò)一些跟布局或者樣式相關(guān)的標(biāo)簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經(jīng)不推薦使用。但是在Flutter里...
閱讀 6932·2021-09-22 15:08
閱讀 1933·2021-08-24 10:03
閱讀 2446·2021-08-20 09:36
閱讀 1323·2020-12-03 17:22
閱讀 2481·2019-08-30 15:55
閱讀 913·2019-08-29 16:13
閱讀 3061·2019-08-29 12:41
閱讀 3257·2019-08-26 12:12