Flutter之布局

Widget 分類(lèi)

widget 其實(shí)是Element 的配置文件,而Element是右RenderObject 渲染的壤靶!如果想知道的更詳細(xì)請(qǐng)看我的這篇《Flutter之HolleWold》

根據(jù)Widget是否需要包含子節(jié)點(diǎn)將Widget分為了三類(lèi),分別對(duì)應(yīng)三種Element缸濒,如下:

【注】:Flutter中的很多Widget是直接繼承自StatelessWidget或StatefulWidget族沃,然后在build()方法中構(gòu)建真正的RenderObjectWidget茉兰,如Text,它其實(shí)是繼承自StatelessWidget歌逢,然后在build()方法中通過(guò)RichText來(lái)構(gòu)建其子樹(shù)巾钉,而RichText才是繼承自LeafRenderObjectWidget。所以為了方便敘述秘案,我們也可以直接說(shuō)Text屬于LeafRenderObjectWidget(其它widget也可以這么描述)砰苍,這才是本質(zhì)。讀到這里我們也會(huì)發(fā)現(xiàn)阱高,其實(shí)StatelessWidget和StatefulWidget就是兩個(gè)用于組合Widget的基類(lèi)赚导,它們本身并不關(guān)聯(lián)最終的渲染對(duì)象(RenderObjectWidget)。

布局類(lèi)組件就是指直接或間接繼承(包含)MultiChildRenderObjectWidget的Widget赤惊,它們一般都會(huì)有一個(gè)children屬性用于接收子Widget吼旧。我們看一下繼承關(guān)系 Widget > RenderObjectWidget > (Leaf/SingleChild/MultiChild)RenderObjectWidget 。

整個(gè)Widget 樹(shù)的關(guān)系圖如下:

關(guān)系樹(shù)

Flutter 布局分類(lèi)

線性布局:Row(X 軸 橫向)未舟,Column(Y軸 縱向)
彈性布局:Flex(Row 和 Column 的基類(lèi))圈暗,Expanded(靈活布局),Spacer(特殊的Expanded)
流式布局:Wrap(Row和Column + 折行功能)裕膀,F(xiàn)low(類(lèi)似于UICollectionview)
層疊布局:Stack(Z 軸)员串,Positioned(類(lèi)似于Masonry,相對(duì)布局)
相對(duì)布局:Align

Row可以在水平方向排列其子widget昼扛。定義如下:

Row({
  ...  
  TextDirection textDirection,    
  MainAxisSize mainAxisSize = MainAxisSize.max,    
  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
  VerticalDirection verticalDirection = VerticalDirection.down,  
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
  List<Widget> children = const <Widget>[],
})
  1. textDirection:表示水平方向子widget的布局順序(是從左往右還是從右往左)寸齐,默認(rèn)為系統(tǒng)當(dāng)前Locale環(huán)境的文本方向(如中文、英語(yǔ)都是從左往右,而阿拉伯語(yǔ)是從右往左)渺鹦。
  2. mainAxisSize:表示Row在主軸(水平)方向占用的空間扰法,默認(rèn)是MainAxisSize.max,表示盡可能多的占用水平方向的空間海铆,此時(shí)無(wú)論子widgets實(shí)際占用多少水平空間迹恐,Row的寬度始終等于水平方向的最大寬度;而MainAxisSize.min表示盡可能少的占用水平空間卧斟,當(dāng)子widgets沒(méi)有占滿水平剩余空間,則Row的實(shí)際寬度等于所有子widgets占用的的水平空間憎茂。
  3. mainAxisAlignment:表示子Widgets在Row所占用的水平空間內(nèi)對(duì)齊方式珍语,如果mainAxisSize值為MainAxisSize.min,則此屬性無(wú)意義竖幔,因?yàn)樽觲idgets的寬度等于Row的寬度板乙。只有當(dāng)mainAxisSize的值為MainAxisSize.max時(shí),此屬性才有意義拳氢,MainAxisAlignment.start表示沿textDirection的初始方向?qū)R募逞,如textDirection取值為T(mén)extDirection.ltr時(shí),則MainAxisAlignment.start表示左對(duì)齊馋评,textDirection取值為T(mén)extDirection.rtl時(shí)表示從右對(duì)齊放接。而MainAxisAlignment.end和MainAxisAlignment.start正好相反。
  4. MainAxisAlignment.center表示居中對(duì)齊留特。讀者可以這么理解:textDirection是mainAxisAlignment的參考系纠脾。
  5. verticalDirection:表示Row縱軸(垂直)的對(duì)齊方向,默認(rèn)是VerticalDirection.down蜕青,表示從上到下苟蹈。
  6. crossAxisAlignment:表示子Widgets在縱軸方向的對(duì)齊方式,Row的高度等于子Widgets中最高的子元素高度右核,它的取值和MainAxisAlignment一樣(包含start慧脱、end、 center三個(gè)值)贺喝,不同的是crossAxisAlignment的參考系是verticalDirection菱鸥,即verticalDirection值為VerticalDirection.down時(shí)crossAxisAlignment.start指頂部對(duì)齊,verticalDirection值為VerticalDirection.up時(shí)搜变,crossAxisAlignment.start指底部對(duì)齊采缚;而crossAxisAlignment.end和crossAxisAlignment.start正好相反。
  7. children :子Widgets數(shù)組挠他。

Column 和 Row 的屬性一樣扳抽,對(duì)應(yīng)使用就可以,這里就不贅述了。

Flex 和 Expanded

Flex({
  @required this.direction, //彈性布局的方向, Row默認(rèn)為水平方向贸呢,Column默認(rèn)為垂直方向
  List<Widget> children = const <Widget>[],
})

由源碼可以看出 Flex 是 Row 和 Column 的基類(lèi)(用法也相同镰烧,這里不詳細(xì)介紹)

Expanded

const Expanded({
//flex參數(shù)為彈性系數(shù),如果為0或null楞陷,則child是沒(méi)有彈性的怔鳖,即不會(huì)被擴(kuò)伸占用的空間。
//如果大于0固蛾,所有的Expanded按照其flex的比例來(lái)分割主軸的全部空閑空間结执。
  int flex = 1, 
  @required Widget child,
})

Flex + Expanded 使用簡(jiǎn)單例子

 Widget build(BuildContext context) {
    return Flex(
      direction: Axis.horizontal,
      children: <Widget>[
        Text('aaaa'),
        //屏幕寬度減去Text的寬度,剩下的紅色和藍(lán)色寬度按1:2比例分配
        Expanded(
            flex: 1,
            child: Container(
              color: Colors.red,
            )),
        Expanded(
            flex: 2,
            child: Container(
              color: Colors.blue,
            ))
      ],
    );
  }

【注】:

  1. Expanded 是一種彈性的填充方式艾凯,默認(rèn)會(huì)把剩下的位置全部填滿(除了Text('aaaa')的位置剩下的全部)
  2. Expanded 無(wú)法直接單獨(dú)使用献幔,必須包含在Flex 里邊(包含Row 和 Column)
  3. Spacer 是一個(gè)特殊的Expanded,只有一個(gè) flex 屬性趾诗,純粹占位子使用

Wrap

流式布局(折行布局)蜡感,部件主軸方滿了自動(dòng)折行

Wrap({
  ...
  this.direction = Axis.horizontal,
  this.alignment = WrapAlignment.start,
  this.spacing = 0.0,//主軸方向子widget的間距
  this.runAlignment = WrapAlignment.start,//縱軸方向的對(duì)齊方式
  this.runSpacing = 0.0,//縱軸方向的間距
  this.crossAxisAlignment = WrapCrossAlignment.start,
  this.textDirection,
  this.verticalDirection = VerticalDirection.down,
  List<Widget> children = const <Widget>[],
})

使用方法和上邊差不多,不贅述了恃泪。

Flow :

我們一般很少會(huì)使用Flow郑兴,因?yàn)槠溥^(guò)于復(fù)雜,需要自己實(shí)現(xiàn)子widget的位置轉(zhuǎn)換贝乎,在很多場(chǎng)景下首先要考慮的是Wrap是否滿足需求情连。Flow主要用于一些需要自定義布局策略或性能要求較高(如動(dòng)畫(huà)中)的場(chǎng)景。

優(yōu)點(diǎn):

  1. 性能好糕非;Flow是一個(gè)對(duì)子組件尺寸以及位置調(diào)整非常高效的控件蒙具,F(xiàn)low用轉(zhuǎn)換矩陣在對(duì)子組件進(jìn)行位置調(diào)整的時(shí)候進(jìn)行了優(yōu)化:在Flow定位過(guò)后,如果子組件的尺寸或者位置發(fā)生了變化朽肥,在FlowDelegate中的paintChildren()方法中調(diào)用context.paintChild 進(jìn)行重繪禁筏,而context.paintChild在重繪時(shí)使用了轉(zhuǎn)換矩陣,并沒(méi)有實(shí)際調(diào)整組件位置衡招。
  2. 靈活篱昔;由于我們需要自己實(shí)現(xiàn)FlowDelegate的paintChildren()方法,所以我們需要自己計(jì)算每一個(gè)組件的位置始腾,因此州刽,可以自定義布局策略。

缺點(diǎn):

  1. 使用復(fù)雜浪箭。
  2. 不能自適應(yīng)子組件大小穗椅,必須通過(guò)指定父容器大小或?qū)崿F(xiàn)TestFlowDelegate的getSize返回固定大小。

示例:

Flow(
  delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
  children: <Widget>[
    new Container(width: 80.0, height:80.0, color: Colors.red,),
    new Container(width: 80.0, height:80.0, color: Colors.green,),
    new Container(width: 80.0, height:80.0, color: Colors.blue,),
    new Container(width: 80.0, height:80.0,  color: Colors.yellow,),
    new Container(width: 80.0, height:80.0, color: Colors.brown,),
    new Container(width: 80.0, height:80.0,  color: Colors.purple,),
  ],
)

//實(shí)現(xiàn)TestFlowDelegate:
class TestFlowDelegate extends FlowDelegate {
  EdgeInsets margin = EdgeInsets.zero;
  TestFlowDelegate({this.margin});
  @override
  void paintChildren(FlowPaintingContext context) {
    var x = margin.left;
    var y = margin.top;
    //計(jì)算每一個(gè)子widget的位置  
    for (int i = 0; i < context.childCount; i++) {
      var w = context.getChildSize(i).width + x + margin.right;
      if (w < context.size.width) {
        context.paintChild(i,
            transform: new Matrix4.translationValues(
                x, y, 0.0));
        x = w + margin.left;
      } else {
        x = margin.left;
        y += context.getChildSize(i).height + margin.top + margin.bottom;
        //繪制子widget(有優(yōu)化)  
        context.paintChild(i,
            transform: new Matrix4.translationValues(
                x, y, 0.0));
         x += context.getChildSize(i).width + margin.left + margin.right;
      }
    }
  }

  @override
  getSize(BoxConstraints constraints){
    //指定Flow的大小  
    return Size(double.infinity,200.0);
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return oldDelegate != this;
  }
}

Stack

Stack({
  this.alignment = AlignmentDirectional.topStart,
  this.textDirection,
  this.fit = StackFit.loose,
  this.overflow = Overflow.clip,
  List<Widget> children = const <Widget>[],
})
  1. alignment:此參數(shù)決定如何去對(duì)齊沒(méi)有定位(沒(méi)有使用Positioned)或部分定位的子組件奶栖。所謂部分定位匹表,在這里特指沒(méi)有在某一個(gè)軸上定位:left门坷、right為橫軸,top袍镀、bottom為縱軸默蚌,只要包含某個(gè)軸上的一個(gè)定位屬性就算在該軸上有定位。
  2. textDirection:和Row苇羡、Wrap的textDirection功能一樣绸吸,都用于確定alignment對(duì)齊的參考系,即:textDirection的值為T(mén)extDirection.ltr设江,則alignment的start代表左锦茁,end代表右,即從左往右的順序叉存;textDirection的值為T(mén)extDirection.rtl蜻势,則alignment的start代表右,end代表左鹉胖,即從右往左的順序。
  3. fit:此參數(shù)用于確定沒(méi)有定位的子組件如何去適應(yīng)Stack的大小够傍。StackFit.loose表示使用子組件的大小甫菠,StackFit.expand表示擴(kuò)伸到Stack的大小。
  4. overflow:此屬性決定如何顯示超出Stack顯示空間的子組件冕屯;值為Overflow.clip時(shí)寂诱,超出部分會(huì)被剪裁(隱藏),值為Overflow.visible 時(shí)則不會(huì)安聘。

Positioned

const Positioned({
  Key key,
  this.left, 
  this.top,
  this.right,
  this.bottom,
  this.width,
  this.height,
  @required Widget child,
})

例子:

 Stack(
    alignment:Alignment.center , //指定未定位或部分定位widget的對(duì)齊方式
    children: <Widget>[
      Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
        color: Colors.red,
      ),
      Positioned(
        left: 18.0,
        child: Text("I am Jack"),
      ),
      Positioned(
        top: 18.0,
        child: Text("Your friend"),
      )        
    ],
  ),

Align

Align({
  Key key,
  this.alignment = Alignment.center, //它有兩個(gè)常用的子類(lèi):Alignment和 FractionalOffset
  this.widthFactor,//縮放因子
  this.heightFactor,
  Widget child,
})

例子:

Align(
  widthFactor: 2,
  heightFactor: 2,
  alignment: Alignment.topRight,
  child: FlutterLogo(//圖片寬高為120*120 ,因?yàn)樵O(shè)置了縮放因子,所以用60
    size: 60,
  ),
),

Alignment:

// 以屏幕中心為中心點(diǎn)(0.0,0.0)镊掖,x诵竭,y的范圍都是(-1~1)
// 上邊的例子Alignment.topRight = Alignment(1.0, -1.0);
//Container 有alignment 屬性,可以直接使用這個(gè)來(lái)實(shí)現(xiàn)的布局
Alignment(this.x, this.y)  

FractionalOffset:

//和Alignment 一樣念颈,只是原點(diǎn)為屏幕的坐上角(這個(gè)和蘋(píng)果一樣)
// x和y的范圍是(0~1.0)
FractionalOffset(this.x, this.y),
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泉粉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子榴芳,更是在濱河造成了極大的恐慌嗡靡,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窟感,死亡現(xiàn)場(chǎng)離奇詭異讨彼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)柿祈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)哈误,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)哩至,“玉大人,你說(shuō)我怎么就攤上這事黑滴『┠迹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵袁辈,是天一觀的道長(zhǎng)菜谣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)晚缩,這世上最難降的妖魔是什么尾膊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮荞彼,結(jié)果婚禮上冈敛,老公的妹妹穿的比我還像新娘。我一直安慰自己鸣皂,他們只是感情好抓谴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著寞缝,像睡著了一般癌压。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上荆陆,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天滩届,我揣著相機(jī)與錄音,去河邊找鬼被啼。 笑死帜消,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浓体。 我是一名探鬼主播泡挺,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼汹碱!你這毒婦竟也來(lái)了粘衬?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咳促,失蹤者是張志新(化名)和其女友劉穎稚新,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體跪腹,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褂删,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冲茸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屯阀。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缅帘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出难衰,到底是詐尸還是另有隱情钦无,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布盖袭,位于F島的核電站失暂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鳄虱。R本人自食惡果不足惜弟塞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拙已。 院中可真熱鬧决记,春花似錦、人聲如沸倍踪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)建车。三九已至笙瑟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間癞志,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工框产, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凄杯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓秉宿,卻偏偏與公主長(zhǎng)得像戒突,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子描睦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容