摘要:但是好像反其道而行之,樣式糅合在結構里面,這樣究竟有啥意思尼首先應該是一個性能的考慮,瀏覽器解析其實也是一個性能消耗點,沒有解析自然也可以加快頁面的顯示。
開始
搞前端的同學可能都習慣了CSS局部的思維,過去也出現過一些跟布局或者樣式相關的標簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經不推薦使用。但是在Flutter里面,是沒有CSS這樣一個概念的,布局和樣式都可能會是一個組件或者是組件里面的屬性所定義和實現的,對于習慣寫樣式的前端同學可能需要適應一下。
個人思考現在可能要想一下,Flutter為啥沒有像瀏覽器一樣抽離出CSS?
我們知道在瀏覽器里面JS,CSS,HTML各司其職:行為,表現和結構,已經深入人心,也被很多人所推崇。但是Flutter好像反其道而行之,樣式糅合在結構里面,這樣究竟有啥意思尼?
首先應該是一個性能的考慮,瀏覽器解析CSS其實也是一個性能消耗點,沒有CSS解析自然也可以加快頁面的顯示。
其次再討論一下CSS,CSS確實非常適合描述樣式和布局,但是也有很明顯的缺點:作用域全局性,代碼冗余,代碼難以重用,難以模塊化等;我們修修補補,又創造了less,sass等工具幫助我們去解決問題,但是自身的缺陷依然會存在,甚至有點鉆牛角尖,因為存在了CSS,所以只能改進CSS。
而在Flutter,沒有了CSS,以上的問題自然蕩然無存,那么描述樣式會不會變得很麻煩?大道行之,我們的前輩們早就在代碼上總結出很多設計模式或者技術去解決代碼重用,代碼冗余,模塊化的問題,為什么我們不去用已經存在很久而且行之有效的技術去解決問題尼。自然把樣式糅合進結構會增加信息量,對我們閱讀代碼可能會是一個小小的挑戰,但是應該也會很快適應下來的,我相信。
我們很多時候都在創造新的工具的解決問題,其實也有可能創造出新的問題,有時候回歸根本,不一定是一件壞事。
主要控制文字方向
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
跟文本相關的還有一個DefaultTextStyle控件,提供了更多的控制選項:textAlign,softWrap,style和maxLines等,都是控制整體:換行,文字居中和多行省略等,相對style提供都是文字自身樣式相關:字重,字體大小等
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("我是一段超長的文本啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦" "啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦" "啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦" "啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦") ) ) ); }
其實Text控件就已經帶上這些屬性:
const Text(this.data, { Key key, this.style, this.textAlign, this.textDirection, this.softWrap, this.overflow, this.textScaleFactor, this.maxLines, })
為什么又要獨立出這些控件專門管理呢,我們知道CSS屬性里面有些屬性時繼承父元素的,例如:字體大小,顏色等;這樣的話,我們很容易統一一個模塊里面的樣式,并不需要每個元素都要去設置一遍,這里的這些控件也是起到這樣的功能,其實除了些字體樣式還有很多地方會有這種繼承關系,例如:主題顏色,語言文字等等。所以后面Text控件很容易從控件樹上找到這些父控件,獲取它們設置的屬性,就這樣就可以把父控件的樣式繼承下來。
怎么做到的呢,無論Directionality還是DefaultTextStyle都是InheritedWidget的子類,InheritedWidget實現了一個發布/訂閱的模式,當子控件調用inheritFromWidgetOfExactType方法獲取父控件時,同時也把自己加入到InheritedWidget的訂閱者列表里面,所以當InheritedWidget屬性改變的時候,就會調起子組件didChangeDependencies方法去通知子組件。
這個控件感覺必須得介紹一下,因為在前端我們有一個canvas元素,可以提供給我們直接去繪制元素,給了我們很大的靈活性,那么Flutter中對應的應該就是這個控件了。
如何使用:
先繼承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; } }
這里我畫了一個綠色的圓,然后把這個CustomPainterSample傳到CustomPaint控件。
Widget build(BuildContext context) { return new Container( color: Colors.white, child: new CustomPaint( painter: new CustomPainterSample(progress: this.progress), ) ); }
當然了,既然可以隨便畫點東西,做點動畫也是妥妥的,好再加個放大的動畫,完整代碼:
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,里面有一個createTicker方法,主要是監聽每一幀生成然后回調,主要是由SchedulerBinding.instance.scheduleFrameCallback方法所驅動的。
ClipRRect剪切元素的邊界,這里類似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值調到90,變成了圓形:
類似的可以剪切元素的還有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, ), ), ), ); }
可以看到紅色方塊底下有一個陰影,讓紅色方塊有一種懸浮的感覺,有material design的風格。
Transform類似于CSS的transform屬性,可以提供沿著X,Y或者Z軸旋轉,位移拉伸等效果。
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軸位移一個按鈕,一般來說,我們照樣可以直接點擊位移之后的按鈕,因為transformHitTests為true的時候,在hitTest會判斷點擊落點是否在transfrom所做的操作(旋轉,拉伸或者位移等)后的區域里面,但是如果為false,此時點擊按鈕原來的區域仍然會觸發點擊事件,但是直接點擊就不行了。
FractionalTranslation可以提供位移,但是并沒有Tranform控件提供那么多變換,僅僅是上下左右的位移,而且位移的基準是以child的大小進行的。
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, ), ) ); }
效果:
紅色方塊往右邊移動了一個身位,就跟CSS中transfrom: translate(100%, 0)效果一樣的。
RotatedBox旋轉盒子,可以使用quarterTurns屬性控制旋轉,每次旋轉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("我倒轉了"), ), ) ); }Padding
在前端每個元素都基本會有border, margin, padding,但是在Flutter里面可能不得不吐槽連padding都要用個控件,未免太過于麻煩。對于此框架的開發者們也有自己一套看法,在Flutter里面組合簡單的控件去實現復雜的控件,而不是通過繼承去實現可以說是Flutter的主要設計思想,所以你會發現盡管Container控件提供了padding的參數,但其實它也背后也是通過創建Padding控件來實現效果的。
FittedBox在CSS中有background-position和background-size兩個屬性控制背景圖如何平鋪,例如:如果背景圖比元素尺寸大或者小的時候,是否要進行拉伸,如果要拉伸,是拉伸圖片寬度還是拉伸圖片高度來適應等等。
而FittedBox所做的事情也是差不多,它有兩個很重要的參數:aligment 和 fit。
fit可取值:
BoxFit.fill
BoxFit.contain
BoxFit.cover
BoxFit.fitWidth
BoxFit.fitHeight
基本這個跟CSS的background-size取值都一樣的。
而aligment則是控制,當子元素大小沒有完全占滿父元素的時候,如何定位,是居中還是靠左靠右。
雖然拿background-size來做對比,但是background-size只是控制背景圖片,而FittedBox幾乎可以對任何元素起作用,因為它是通過Transform放大縮小子元素來達到剛才所說的效果。
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就會通過拉伸高度來適應黑盒子,如果把fit屬性改成BoxFit.fitWidth,效果就是這樣的:
可以看到字體是被直接縮小了。
SizedBox & ConstrainedBox為什么把兩個控件一起講呢?因為它們都依賴了相同的RenderObject:RenderConstrainedBox,而RenderConstrainedBox只有一個參數:additionalConstraints。
而這個參數在performLayout中:
void performLayout() { if (child != null) { child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true); size = child.size; } else { size = _additionalConstraints.enforce(constraints).constrain(Size.zero); } }
而BoxConstraints.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) ); }
可見additionalConstraints是在原基礎constraints增加了自己的約束,但是并不會打破原來的約束條件。
FractionallySizedBox主要有三個參數:aligment, widthFactor 和 heightFactor。
aligment參數控制child的定位;widthFactor 和 heightFactor 控制child的約束,如果widthFactor或者heightFactor不為null,會產生一個新的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, ) ) ) ); }
效果:
可以看到當widthFactor和heigthFractor時,紅色盒子寬高都為綠色的一半。
LimitedBox看名稱也知道跟控制尺寸有關了,這個控件主要有兩個參數:maxWidth和maxHeight,當constraints是unbounded的時候,也就是maxWidth和maxHeight都是infinite的時候,會用maxWidth和maxHeight替換原來的maxWidth和maxHeight,所以如果contraints是bounded的時候并不會起作用。
關鍵代碼:
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); } }
對比ConstrainedBox,明顯使用范圍就沒有那么廣了。
OverflowBox從前面的幾個控件:SizedBox,ConstrainedBox和LimitedBox分析知道,我們似乎沒有辦法打破由parent傳遞下來的約束條件,但是我們總會有一些情況是子組件的尺寸大于父組件的情況,那么怎么解決的尼?來,就看這里的OverflowBox控件,這個控件提供了幾個參數: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(); } }
這里可以看到直接使用我們傳入的參數替換了原本的minxWidth,maxWidth等,所以底下的組件可以根據新的約束條件來布局。
做一下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, ), ) ) ); }
效果:
如果沒有OverflowBox控件,紅色的盒子是不可能超過綠色盒子的;而aligment可以控制紅色盒子在綠色盒子里面的定位,現在是居中顯示的。
SizedOverflowBox剛才OverflowBox是因為我們修改了約束條件所以child布局大小確實被改變了,所以會發生溢出,而SizedOverflowBox這個控件并不會改變約束條件,但是它還是可能會發生溢出,為什么尼?因為SizedOverflowBox可以讓控件看上去“變小一點”,這怎樣做到的尼?這個控件有一個參數:size,這個參數就是讓我們決定這個控件看上去應該多大。
關鍵代碼在RenderSizedOverflowBox類中:
@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有一個屬性visibility,當設置為hidden時,元素是存在但是不會繪制出來;在Flutter中Offstage也可以做到這種效果。
在RenderOffstage類中:
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); } ... }
可見當offstage為true時,布局還是會繼續進行的,但是paint方法里面會直接返回,hitTest方法也會直接跳過,也就是不能響應任何手勢。
AspectRatio這個控件可以用來讓子控件大小維持在一個固定寬高比,例如: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)); }
簡單分析一下:
如果constraints是tight,那么這個控件并不會起啥作用,所以這個控件一般需要Align控件包裹一下。
如果寬度不是Inifinte,它首先會選擇最大寬度,否則根據maxHeight來反推寬度。
萬一高度超出約束條件,它就會反過來,選擇最大的高度反推出寬度,那么萬一寬度小于最小寬度,它又會根據最小寬度計算高度等等。
當然最后還是會根據約束條件來規范最終的Size,所以可能出來效果是跟我們預設的寬高比不一致,但是這種情況應該很少。
示例代碼:
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.
說實在這個控件看了半天沒想出用于哪些場景,搜了一下代碼,基本都用在一些浮窗上。布局過程是調用getMaxIntrinsicWidth方法遞歸詢問子控件最大的intrinsicWidth,因為這個方法需要遞歸下去,如果每個控件都調用比較耗性能,當獲取到intrinsicWidth,就會使用這個值作為約束條件(當然也受到原始的約束條件約束),然后傳遞給child,所以正如上面的話所說,但是還是想不到哪些場景會需要。
Baseline正如圖上,基線可以影響著文字水平排布;如果兩段文字的基線不一樣,兩段文字的可能會出現一上一下,并不是在同一水平線上排布,就像這樣:
這是兩個Text控件,文字大小分別是12dp和32dp,所以他們的基線位置是不一樣的,所以這樣的排布并不是我們想要的,所以我們可以使用Baseline控件讓他們都在一樣的基線上,修改后:
這才是我們常見的,代碼如下:
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,兩段文字都會在來30的水平線上排布,就可以看到現在整齊的效果。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/51511.html
摘要:開始繼續接著分析相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深的體會。關于屬性,指前一個組件的布局區域和繪制區域重疊了。 開始 繼續接著分析Flutter相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深...
摘要:開始繼續接著分析相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深的體會。關于屬性,指前一個組件的布局區域和繪制區域重疊了。 開始 繼續接著分析Flutter相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深...
摘要:開始繼續接著分析相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深的體會。關于屬性,指前一個組件的布局區域和繪制區域重疊了。 開始 繼續接著分析Flutter相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深...
摘要:但是好像反其道而行之,樣式糅合在結構里面,這樣究竟有啥意思尼首先應該是一個性能的考慮,瀏覽器解析其實也是一個性能消耗點,沒有解析自然也可以加快頁面的顯示。 開始 搞前端的同學可能都習慣了CSS局部的思維,過去也出現過一些跟布局或者樣式相關的標簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經不推薦使用。但是在Flutter里...
閱讀 1405·2023-04-26 03:04
閱讀 2349·2019-08-30 15:44
閱讀 3731·2019-08-30 14:15
閱讀 3528·2019-08-27 10:56
閱讀 2737·2019-08-26 13:53
閱讀 2619·2019-08-26 13:26
閱讀 3081·2019-08-26 12:11
閱讀 3613·2019-08-23 18:21