Flutter佈局——Flex、FittedBox、Stack、Container佈局教程【超詳細】

佈局

Flex佈局

Flutter中的Flex佈局和Web的CSS中的Flex佈局相似。html

在Flutter 中用於控制Flex佈局的有Row,Column,Expanded,Flexible,Spacer,Flex這些控件。web

row水平佈局

屬性名 類型 默認值 說明
mainAxisAlignment MainAxisAlignment MainAxisAlignment.start 主軸的排列方式
mainAxisSize MainAxisSize MainAxisSize.max 主軸佔空間的大小
crossAxisAlignment CrossAxisAlignment CrossAxisAlignment.center 次軸的排列方式
textDirection TextDirection null 肯定children在水平方向的擺放順序
verticalDirection VerticalDirection VerticalDirection.down 肯定children在垂直方向的擺放順序
textBaseline TextBaseline null 文字基準線對齊

咱們首先建立三個大小不一的Containerapi

class LyoutRowDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("水平佈局"),
        ),
        body: Container(
          child: Row(
            children: <Widget>[
              Container(
                height: 100,
                width: 50,
                color: Colors.redAccent,
              ),
              Container(
                height: 50,
                width: 50,
                color: Colors.blueAccent,
              ),
              Container(
                color: Colors.black,
                height: 75,
                width: 75,
              )
            ],
          ),
        ));
  }
}
複製代碼

主軸排列方式MainAxisAlignment

子元素children的排列方式由這兩個屬性決定textDirection和verticalDirection。textDirection決定水平方向的排列方式TextDirection.ltr從左往右排列(把左看成起始位置),TextDirection.rtl從右往左排列(把右看成起始位置)。verticalDirection時水平方向的排列方式。當值爲down(向下排列)時起始位置在頂部。當值爲up(向上排列)是起始位置在底部。app

  • start (默認值) 根據textDirection屬性排列方向。less

    將children放置在主軸的起點ide

    textDirection屬性值爲rtl時右圖函數

  • end源碼分析

    根據textDirection屬性排列方向。佈局

    將children放置在主軸的末尾測試

    textDirection屬性值爲rtl時右圖

  • center

    根據textDirection屬性排列方向。

    將children放置在主軸的中心

    textDirection屬性值爲rtl時右圖

  • spaceBetween

    根據textDirection屬性排列方向。

    將主軸方向上的空白區域均分,使得children之間的空白區域相等,首尾child都靠近首尾,沒有間隙

    textDirection屬性值爲rtl時右圖

  • spaceAround

    根據textDirection屬性排列方向。

    將主軸方向上的空白區域均分,使得children之間的空白區域相等,可是首尾空白區域爲一半

    textDirection屬性值爲rtl時右圖

  • spaceEvenly

    根據textDirection屬性排列方向

    將主軸方向上的空白區域均分,使得children之間的空白區域相等,包括首尾空白區域

    textDirection屬性值爲rtl時右圖

交叉軸的排列方式crossAxisAlignment

都以 mainAxisAlignment: MainAxisAlignment.start爲例

  • start

    將子元素在交叉軸上起點對齊。設置verticalDirection爲VerticalDirection.up右圖。

  • end

    將子元素在交叉軸上末尾對齊。設置verticalDirection爲VerticalDirection.up右圖。

  • center

    將子元素在交叉軸上居中對齊。設置verticalDirection無改變。

  • strentch

    將子元素在交叉軸上拉伸

  • baseline

    基準線對其適用於文字,咱們首先建立文字的start。使用baseline。必須設置textBaseline屬性。不一樣文字的文字基準線。主要是字母文字(如:英語)和表意文字(如:漢語)的基準線是不一樣的。必須告訴Flutter你使用的文字。

    Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      textBaseline: TextBaseline.alphabetic,
      children: [
        Text(
          'Flutter',
          style: TextStyle(
            color: Colors.yellow,
            fontSize: 30.0
          ),
        ),
        Text(
          'Flutter',
          style: TextStyle(
              color: Colors.blue,
              fontSize: 20.0
          ),
        ),
      ],
    );
    複製代碼

設置對齊方式爲基準線

主軸佔用的空間mainAxisSize

mainAxisSize只有兩個值一個是min一個是max。默認是max。

當值爲min時候。主軸縮緊,變爲最小。

如圖:即便這時mainAxisAlignment:爲MainAxisAlignment.spaceAround。主軸的長度仍爲最小狀態

Column垂直佈局

垂直佈局與水平佈局的屬性和方法一致,惟一須要注意的是textDirection(水平排列方式)和verticalDirection(垂直排列方式)

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: "垂直佈局",
        home: Scaffold(
            appBar: AppBar(title: Text("垂直佈局")),
            body: Center(
              child: Column(
			   mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: <Widget>[
                  Text("1",style: TextStyle(fontSize: 100),),
                  Expanded(
                    child: Text("2"),
                  ),
                  Text("33333"),
                  Text("4")
                ],
              ),
            )));
  }
}

複製代碼

爲了便於觀看咱們將1擴大

Flex佈局

咱們查看如下源碼便於理解

能夠看到RowColumn都繼承與Flex

class Row extends Flex class Column extends Flex 複製代碼

Row的構造函數可選命名參數(即{}包裹的參數)有8個。

傳入父級的super構造函數卻有9個,多出了direction: Axis.horizontal

Row({
    Key key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    TextDirection textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline textBaseline,
    List<Widget> children = const <Widget>[],
  }) : super(
    children: children,
    key: key,
    direction: Axis.horizontal,
    mainAxisAlignment: mainAxisAlignment,
    mainAxisSize: mainAxisSize,
    crossAxisAlignment: crossAxisAlignment,
    textDirection: textDirection,
    verticalDirection: verticalDirection,
    textBaseline: textBaseline,
  );
}
複製代碼

再Flex中屬性direction實際上時主軸的方向。且被@required標註。它是必選的。

Flex({
    Key key,
    @required this.direction,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.mainAxisSize = MainAxisSize.max,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    this.textBaseline,
    List<Widget> children = const <Widget>[],
  }) : assert(direction != null),
       assert(mainAxisAlignment != null),
       assert(mainAxisSize != null),
       assert(crossAxisAlignment != null),
       assert(verticalDirection != null),
       assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
       super(key: key, children: children);
複製代碼

Expanded

上面的例子咱們遇到從未見過的一個widget。很容易就能夠看出。Expanded會忽略子元素的大小並強制自動擴展使主軸填充父級可用的空白區域。Expanded必須是Row、Column、Flex的children。

下面兩個例子便於更好理解:

//當子元素只有一個Expanded時
class LyoutExpanded extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Expanded"),
      ),
      body: Center(
        child: Container(
          child: Column(
            children: <Widget>[
              Expanded(
                child: Container(
                  color: Colors.blue,
                  width: 100,//這個被忽略掉了
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

複製代碼

class LyoutExpanded extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Expanded"),
      ),
      body: Center(
        child: Container(
          height: 50,//填充使主軸擴展至父級高度
          child: Column(
            children: <Widget>[
           Expanded(
                child: Container(
                  color: Colors.blue,
                  height: 100,//
                  width: 100,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
複製代碼

Expanden還有一個屬性爲flex默認值爲1。表明權重。當子元素有多個Expanden。按照權重值佔據父級的高度(row時爲水平寬度)

<Widget>[
    Expanded(
        flex: 2,
        child: Container(
            color: Colors.blue,
            width: 100,
        ),
    ),
    Expanded(
        child: Container(
            color: Colors.red,
            width: 100,
        ),
    ),
    Expanded(
        child: Container(
            color: Colors.yellow,
            width: 100,
        ),
    ),
],
複製代碼

咱們能夠看到藍色的塊時紅和黃的二倍。

Flexible

Flexible組件可使Row、Column、Flex等子組件在主軸方向有填充可用空間的能力(例如,Row在水平方向,Column在垂直方向),可是它與Expanded組件不一樣,它不強制子組件填充可用空間。一樣Flexible組件必須是Row、Column、Flex等組件的children。

Flexiible 有屬性fit當屬性值FlexFit.tight時。Flexible和Expanded沒有區別。

從源碼能夠看出Expanded繼承與Flexible

且引用 上級構造函數傳入fit的值爲FlexFit.tight。

class Expanded extends Flexible //省區其餘源碼 const Expanded({
    Key key,
    int flex = 1,
    @required Widget child,
  }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
    
複製代碼

在Column的children中傳入。屬性fit:爲FlexFit.tight,按照權值填充空白區域。

<Widget>[
    Flexible(
        fit: FlexFit.tight,
        child: Container(
            color: Colors.blue,
            height: 100,
            width: 100,
        ),
    ),
    Flexible(
        fit: FlexFit.tight,
        flex: 2,
        child: Container(
            color: Colors.yellow,
            height: 100,
            width: 100,
        ),
    ),
],
複製代碼

一旦fit的值爲FlexFit.loose(默認值)先按flex的值分主軸肯定佔主軸的大小,再按child調整元素的高度。不強制拉伸

例如第一個爲tight強制擴展。第二個爲loose不強制擴展。

<Widget>[
    Flexible(
        fit: FlexFit.tight,
        child: Container(
            color: Colors.blue,
            height: 100,
            width: 100,
        ),
    ),
    Flexible(
        fit: FlexFit.loose,
        flex: 2,
        child: Container(
            color: Colors.yellow,
            height: 100,
            width: 100,
        ),
    ),
],
複製代碼

由於第二個爲loose因此黃顏色的Container高度爲100。而藍色Container的 fit值爲 tight,他的大小和上一個例子一致。

Spacer

Spacer建立一個能夠調整的空白區域,可用於調整Flex容器(Row或者Colum)中widget之間的間距。

一旦children裏面包含了Spacer。mainAxisAlignment的屬性值將起不到做用。Spacer已經佔據了全部額外的空間,所以沒有剩餘的空間能夠從新分配。

Row(
    mainAxisAlignment: MainAxisAlignment.end,//起不到做用
    children: <Widget>[
        Container(
            height: 100,
            width: 50,
            color: Colors.redAccent,
        ),
        Spacer(),
        Container(
            height: 50,
            width: 50,
            color: Colors.blueAccent,
        ),
        Container(
            color: Colors.black,
            height: 75,
            width: 75,
        )
    ],
),
複製代碼

縮放佈局

絕大多數flutter的widget是盒子。能夠將他們疊放,堆疊,嵌套。

咱們能夠層級嵌套盒子,可是若是一個盒子不適合(大小不適合)另外一個盒子。該如何解決?

爲了解決這個問題Flutter提供了FittedBox,這個和移動端的ImageView相似。

它實現的功能是使子元素縮放(fit)或者調整位置(alignment)

  • BoxFit.contain(默認值)等比例擴大或縮小,但內容不會超過容器範圍

  • BoxFit.cover按照比例逐步擴大至充滿容器,內容有能會超過容器範圍。當child比例與容器不一樣時,要麼高度溢出容器,要麼寬度溢出容器。

  • BoxFit.fill不保留比例強制拉伸(縮小)填充容器。

  • BoxFit.fitHeight保持比例確保高度在容器中顯示完整。

  • BoxFit.fitWidth保持比例確保寬度在容器中顯示完整。

  • BoxFit.none將child對齊在目標框內(默認劇中),並丟棄位於框外的部分,源文件不放大也不縮小。

  • BoxFit.scaleDown將child對齊在目標框內(默認劇中),當child大於容器,則與contain一致。若是child小於容器,則與none一致

爲了便於理解咱們能夠找一個圖片進行測試

class Lyoutfitdemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("data")),
      body: Container(
        height: 250,
        width: 250,
        color: Colors.indigo[200],
        child: FittedBox(
          child: Image.asset('images/fittedbox.png')),
      ),
    );
  }
}
複製代碼
  1. BoxFit.contain

  1. BoxFit.cover

  1. BoxFit.fill

  1. BoxFit.fitHeight

5.BoxFit.fitWidth

  1. BoxFit.none

  1. BoxFit.scaleDown

備註:不用建立FittedBox,Image含有屬性fit。值效果和FittedBox一致。

堆疊佈局

Stack與web絕對定位佈局模型相似。Stack不是按行或者列來佈局的,而是按照特定順序和位置堆疊。可使用 PositionedAlign做爲Stack的的定位。

示例:使用層疊佈局實現一個 圖片漸變效果

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "漸變狀態欄",
      home: Scaffold(
        body: Container(
          height: 400,
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              Image.asset(
                'images/Stack.png',
                fit: BoxFit.cover,
              ),
          
              const DecoratedBox(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    begin: Alignment(0.0, -1.0),
                    end: Alignment(0.0, -0.4),
                    colors: <Color>[Color(0x90000000), Color(0x00000000)],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
複製代碼

圖片漸變效果

這是兩個控件堆疊在一塊兒的例子。固然子部件能夠出如今父級內的任何位置。經過兩種widget實現對位置的控制。

Align佈局

Align爲對齊部件,設置child的在父級的(如container、stack等)對齊方式,並根據child的尺寸調整自身的尺寸。

本文的父級容器選用stack

Alignment

其中有這幾種屬性

topLeft(左上),topCenter(頂部中央),topRight(右上),centerLeft,center,centerRight,bottomLeft,bottomCenter,bottomRight

class LayoutAlignDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("align部件"),
        ),
        body: Stack(
          children: <Widget>[
            Align(
              alignment: Alignment.topLeft,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
          ],
        ));
  }
}
複製代碼

Alignment.lerp(Alignment a, Alignment b, double t)

lerp方法有三個參數,前兩個參數爲Alignment類型,第三個參數爲小數。

當t爲0時,這個方法返回的值爲a。

當t爲1時,返回b的值。

當t爲0.5時,這個widget位置就位於a和b指定的位置中間。

因此這個t爲一個偏移量。指定爲a到b之間的偏移。

//在Stack children中添加一個align
Align(
  alignment: Alignment.lerp(Alignment.bottomCenter, Alignment.center,0.5),//位於Alignment.bottomCenter和Alignment.center的中央
  child: Container(
  width: 100,
  height: 100,
  color: Colors.yellow,
   ),
),
複製代碼

偏移量對齊

上面介紹了相對於對其方式a和b的偏移對其。

FractionalOffset 是另一個偏移方法,它是相對於父部件左上角的偏移。

建立偏移量FractionalOffset (dx,dy)。dx和dy的取值都是0~1。左上的位置爲dx和dy都爲0。

Align(
  alignment: FractionalOffset(0, 0.5),
  child: Container(
    width: 100,
    height: 100,
    color: Colors.red,
  ),
),
複製代碼

另外在源碼中:

class FractionalOffset extends Alignment 複製代碼

FractionalOffset繼承於Alignment因此Alignment的屬性均可以使用,這樣咱們要使得widget位於左上也能夠用使用FractionalOffset.topLeft

class LayoutAlignDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("align部件"),
        ),
        body: Stack(
          children: <Widget>[
            Align(
              alignment: FractionalOffset(0, 0),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
            Align(
              alignment: FractionalOffset(0, 0.5),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
            Align(
              alignment: FractionalOffset(0, 1),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
            Align(
              alignment: Alignment.lerp(Alignment.bottomCenter, Alignment.center,0.5),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.yellow,
              ),
            ),
            Align(
              alignment: FractionalOffset.topRight,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.yellow,
              ),
            ),
          ],
        ));
  }
}

複製代碼

與之相相似的還有Alignment(dx, dy),以父級容器的中心爲座標系原點。dx的取值範圍爲-1~1,dy的取值範圍爲-1~1。設置子元素的位置。

Positioned

Positioned 部件能夠控制Stack中子元素的位置。Positioned 與Align不一樣的是Position必須是Stack的Children。

Position有topbottomleftrightheightwidth

topbottomleftright這些量都是子元素邊界與父級某一邊界的距離。

也就是說當一個高度和長度固定容器,一旦咱們肯定left或right的一個量和bottom或top的一個量,其位置就能夠肯定。

部分代碼

Stack(
    children: <Widget>[
        Positioned(
            top: 100,
            right: 100,
            width: 100,
            height: 100,
            child: Container(
                color: Colors.red,
            ),
        ),
    ],
)
複製代碼

源碼分析

const Positioned({
    Key key,
    this.left,
    this.top,
    this.right,
    this.bottom,
    this.width,
    this.height,
    @required Widget child,
  }) : assert(left == null || right == null || width == null),
       assert(top == null || bottom == null || height == null),
       super(key: key, child: child);
複製代碼

在水平方向上若是assert(false)會拋出錯誤

而括號裏的值爲left == null || right == null || width == null

left ,right ,width 至少有一個值爲null這個程序纔不會報錯。

也就是說當width未指定(爲空)使left和right是能夠同時存在的。這時的容器的寬度會以據邊界的距離(left和right)自動調整。

Stack(
    children: <Widget>[
        Positioned(
            top: 100,
            right: 100,
            left: 100,
            // width: 100,
            height: 100,
            child: Container(
                color: Colors.red,
            ),
        ),
    ],
)
複製代碼

IndexedStack

IndexedStack繼承於Stack

class IndexedStack extends Stack 複製代碼

他和Stack不一樣的是有一個index的屬性,表示只顯示第幾個元素。

IndexedStack(
    index: 1,//只顯示第二個
    children: <Widget>[
        Positioned(
            top: 100,
            right: 100,
            left: 100,
            height: 100,
            child: Container(
                color: Colors.red,
            ),
        ),  
        Positioned(
            top: 500,
            right: 100,
            left: 100,
            height: 100,
            child: Container(
                color: Colors.red,
            ),
        ),
    ],
)
複製代碼

容器佈局

Container能夠建立一個矩形元素。能夠用BoxDecoration進行裝飾。背景,邊框,陰影。

class LyoutContainerdemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Container容器"),
      ),
      body: Container(
        height: 200,
        width: 200,
        child: Text("這是一個Container容器"),
        decoration: BoxDecoration(
          color: Colors.red[200],
          // shape: BoxShape.circle, //形狀
          border: Border.all(width: 10.0),
          boxShadow: [
            BoxShadow(
              offset: Offset(0.0, 100.0), //模糊偏移量
              color: Color.fromRGBO(16, 20, 188, 1.0), //顏色
              blurRadius: 10, //模糊
              spreadRadius: -9.0, //在應用模糊以前陰影的膨脹量
            ),
          ],
        ),
      ),
    );
  }
}

複製代碼

爲了演示功能,這個圖片效果作的比較誇張。

附上一個好看的效果

類似於Css的盒子佈局,咱們也能夠經過Margin給盒子設置外邊距。

Container(
    margin: EdgeInsets.all(50.0),//外邊距50.0
    height: 200,
    width: 200,
    decoration: BoxDecoration(
        color: Colors.red[600],
        border: Border.all(width: 2.0),
        boxShadow: [
            BoxShadow(
                offset: Offset(2.0, 9.0), //偏移量
                color: Colors.red[200], //顏色
                blurRadius: 10, //模糊
                spreadRadius: -1.0, //在應用模糊以前陰影的膨脹量
            ),
        ],
    ),
),
複製代碼

Padding佈局

用於處理容器與子元素之間的距離。與Padding對應的是margin屬性。margin用於處理與其餘組件之間的距離。Padding部件和容器內的pading屬性的效果其實是一致的,當同時出現,真實的padding將是二者相加。

class LayoutPaddingDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Padding 佈局"),
      ),
      body: Container(
        padding: EdgeInsets.all(20.0),//#1這兩處的屬性效果是一致的
        decoration: BoxDecoration(
            color: Colors.yellow,
            border: Border.all(color: Colors.white, width: 8.0)),//白色邊框
        child: Padding(
          child: Container(
            decoration: BoxDecoration(
            color: Colors.red,
            border: Border.all(color: Colors.white, width: 8.0)),//白色邊框
          ),
          padding: EdgeInsets.all(10.0),//#1這兩處的屬性效果是一致的
        ),
      ),
    );
  }
}

複製代碼

寫在後面

Flutter中涉及到佈局的Widget有30多種,同樣的效果的ui,實現的途徑有不少中。本篇就重點涉及幾個經常使用的部件。

參考連接

youtu.be/T4Uehk3_wlY

medium.com/jlouage/flu…

api.flutter.dev/index.html

flutter.dev/docs

相關文章
相關標籤/搜索