3.3、Flutter:組件Widget

一、概述

聲明式UI 和 響應(yīng)式UI

  • Flutter的頁面編寫風(fēng)格,屬于聲明式UI風(fēng)格
    這與iOS的 UIKit響應(yīng)式 差距很大,不過后面的 SwiftUI 蘋果擁抱 聲明式UI 的努力。

  • Flutter的UI框架吸取了React的理念,即 UI 是關(guān)于狀態(tài)的函數(shù)。
    所謂的狀態(tài),可以理解為頁面所需要的數(shù)據(jù)。可以分為短時狀態(tài),如StatefullWidget里面的state,其不能直接被其他widget獲取。應(yīng)用級別狀態(tài),如多個頁面共享的state。

image.png

Flutter組件的分類

1、基礎(chǔ)組件

  • 文本,Text
  • 按鈕,TextButton
  • 圖片圖標(biāo),ImageIcon
  • 輸入框 和 表單,TextField、Form
  • 進度指示器,LinearProgressIndicator、CircularProgressIndicator
  • 單選框和復(fù)選框,SwitchCheckbox

2、布局之容器組件

  • 裝飾,DecoratedBox、BoxDecoration等,如顏色、背景、邊框、漸變、陰影等
  • 復(fù)合容器,Container,它是結(jié)合align、padding、margin、box、變換等多種功能。
  • 尺寸,主要是設(shè)置頁面的寬高大小 或 寬高比例。
    寬高SizedBox、AspectRatio、LimitedBox、FractionallySizedBox等;
    限制類ConstrainedBox、BoxConstraints、UnconstrainedBox
  • 對齊,Align
  • 居中,Center
  • 填充,Padding
  • 剪裁,ClipOval、ClipPathClipRect、ClipRRect
  • 變換(如旋轉(zhuǎn)等),Transform
  • 適配(如換行,即超出父組件邊界約束),FittedBox

3、布局組件

  • 線性布局,Row、Column
  • 彈性布局,Flex、Expanded
  • 流式布局(子元素自動換行),Wrap、Flow
  • 層疊布局,StackPositioned

4、復(fù)合組件(滾動、列表等)

  • 簡單滾動列表(類似iOS的ScrollView),SingleChildScrollView
  • 列表,ListView、AnimatedList
  • 二維列表(類似iOS的CollectionView),GridView
  • 自定義滾動列表,CustomScrollView、NestedScrollView
  • TabBar,TabBarView

5、交互組件

  • 手勢,GestureDetector、GestureRecognizer等相關(guān)類
  • 動畫,Animation、Curve、AnimationController、Tween、Ticker等相關(guān)類

二、組件相關(guān)類

主要匯總組件對應(yīng)的類以及繼承關(guān)系,對于組件學(xué)習(xí)和內(nèi)部原理有一定的幫助。

一、組件基類
abstract class DiagnosticableTree with Diagnosticable
abstract class Widget extends DiagnosticableTree

二、普通組件
abstract class StatelessWidget extends Widget
abstract class StatefulWidget extends Widget

// 狀態(tài)
abstract class State<T extends StatefulWidget> with Diagnosticable


三、布局組件
// 布局類基類
abstract class RenderObjectWidget extends Widget 

// 包含一個子Widget,如:ConstrainedBox、DecoratedBox等
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget 
  通過屬性child關(guān)聯(lián)單個widget

// 包含多個子Widget,一般都有一個children參數(shù),接受一個Widget數(shù)組。如Row、Column、Stack等
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget 
  通過屬性children關(guān)聯(lián)多個widget。

// Widget樹的葉子節(jié)點,用于沒有子節(jié)點的widget,通?;A(chǔ)組件都屬于這一類,如Image。
abstract class LeafRenderObjectWidget extends RenderObjectWidget 

// Widget代理
abstract class ProxyWidget extends Widget
abstract class ParentDataWidget<T extends ParentData> extends ProxyWidget

// 彈性布局,flex
class Flex extends MultiChildRenderObjectWidget

class Flexible extends ParentDataWidget<FlexParentData>
class Expanded extends Flexible

// 線性布局
class Column extends Flex
class Row extends Flex

// 設(shè)置padding
class Padding extends SingleChildRenderObjectWidget
// SizedBox 設(shè)置寬高
class SizedBox extends SingleChildRenderObjectWidget
// Spacer 基于flex的空白,內(nèi)部基于Expanded實現(xiàn)的,它只是Expanded的一個包裝類
class Spacer extends StatelessWidget

// 流式布局
class Wrap extends MultiChildRenderObjectWidget
class Flow extends MultiChildRenderObjectWidget

// 層疊布局
class Stack extends MultiChildRenderObjectWidget
class Positioned extends ParentDataWidget<StackParentData>

// 對齊與相對定位
class Align extends SingleChildRenderObjectWidget
class Center extends Align

/* 容器類組件*/
// 填充
class Padding extends SingleChildRenderObjectWidget

// 尺寸大小限制
class ConstrainedBox extends SingleChildRenderObjectWidget
class SizedBox extends SingleChildRenderObjectWidget
class AspectRatio extends SingleChildRenderObjectWidget
class FractionallySizedBox extends SingleChildRenderObjectWidget
class LimitedBox extends SingleChildRenderObjectWidget

class UnconstrainedBox extends StatelessWidget

// 裝飾類組件
class DecoratedBox extends SingleChildRenderObjectWidget
class BoxDecoration extends Decoration

// 組合類容器
class Container extends StatelessWidget


/* Material常用組件類 */
class Scaffold extends StatefulWidget

abstract class PreferredSizeWidget implements Widget
class AppBar extends StatefulWidget implements PreferredSizeWidget

class Drawer extends StatelessWidget
class FloatingActionButton extends StatelessWidget

class BottomNavigationBar extends StatefulWidget 
class BottomNavigationBarItem

三、基礎(chǔ)組件

1.1、Widget 和 Element

Widget只是UI元素的一個配置數(shù)據(jù),并且一個Widget可以對應(yīng)多個Element。
注意1:Widget實際上是Element的配置數(shù)據(jù)樹,而真正的UI渲染樹是由Element構(gòu)成。
注意2:一個Widget對象可以對應(yīng)多個Element對象。

1.2、Widget主要接口

// Widget 是一個抽象類。在Flutter開發(fā)中,我們一般都不用直接繼承Widget類來實現(xiàn)一個新組件。
@immutable
abstract class Widget extends DiagnosticableTree { // 繼承與DiagnosticableTree“診斷樹”,主要作用是提供調(diào)試信息。
  // 類似于React/Vue中的key,主要的作用是決定是否在下一次build時復(fù)用舊的widget
  const Widget({ this.key });
  final Key key;
    
  // Flutter Framework隱式調(diào)用的,在我們開發(fā)過程中基本不會調(diào)用到。
  @protected
  Element createElement();

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

  // 復(fù)寫父類的方法,主要是設(shè)置診斷樹的一些特性。
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }
  
  // 靜態(tài)方法,它主要用于在Widget樹重新build時復(fù)用舊的widget
  // 源碼分析可知:只要newWidget與oldWidget的runtimeType和key同時相等時就會用newWidget去更新Element對象的配置,否則就會創(chuàng)建新的Element。
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

1.3、核心Widget子類:StatelessWidget 和 StatefulWidget

StatelessWidget

  • 它繼承自Widget類,重寫了createElement()方法:
// StatelessElement 間接繼承自Element類,與StatelessWidget相對應(yīng)(作為其配置數(shù)據(jù))。
@override
StatelessElement createElement() => new StatelessElement(this);
  • 用于不需要維護狀態(tài)的場景,它通常在build方法中通過嵌套其它Widget來構(gòu)建UI,在構(gòu)建過程中會遞歸的構(gòu)建其嵌套的Widget。

  • 初始BuildContext
    表示當(dāng)前widget在widget樹中的上下文,每一個widget都會對應(yīng)一個context對象(因為每一個widget都是widget樹上的一個節(jié)點)。實際上,context是當(dāng)前widget在widget樹中位置中執(zhí)行”相關(guān)操作“的一個句柄(例如,它提供了從當(dāng)前widget開始向上遍歷widget樹以及按照widget類型查找父級widget的方法。)。

StatefulWidget

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);
    
  // StatefulElement 間接繼承自Element類,與StatefulWidget相對應(yīng)(作為其配置數(shù)據(jù))。
  // StatefulElement中可能會多次調(diào)用createState()來創(chuàng)建狀態(tài)(State)對象。
  @override
  StatefulElement createElement() => new StatefulElement(this);
    
  // 比StatelessWidget類多添加了一個新的接口createState()。
  // 用于創(chuàng)建和Stateful widget相關(guān)的狀態(tài),它在Stateful widget的生命周期中可能會被多次調(diào)用。
  @protected
  State createState();
}
  • 注意:StatefulWidget的組件一般會將 Widget build(BuildContext context) 放在State中實現(xiàn)。

理解State

  • 核心作用:
    構(gòu)建Widget的時候可以從State讀取信息,同時State變化的時候可以同步更新對應(yīng)Widget。

  • State中有兩個常用屬性:
    widget,它表示與該State實例關(guān)聯(lián)的widget實例;context,當(dāng)前widget對應(yīng)的BuildContext上下文。

  • 深刻理解「State生命周期」

class _CounterWidgetState extends State<CounterWidget> {  
  int _counter;

  // step 1、
  // 當(dāng)Widget第一次插入到Widget樹時會被調(diào)用,對于每一個State對象,F(xiàn)lutter framework只會調(diào)用一次該回調(diào),
  // 所以,通常在該回調(diào)中做一些一次性的操作,如狀態(tài)初始化、訂閱子樹的事件通知等。
  @override
  void initState() {
    super.initState();
    //初始化狀態(tài)  
    _counter=widget.initValue;
    print("initState");
  }

  // step 2、
  // 當(dāng)State對象的依賴發(fā)生變化時會被調(diào)用;
  // 典型的場景是當(dāng)系統(tǒng)語言Locale或應(yīng)用主題改變時,F(xiàn)lutter framework會通知widget調(diào)用此回調(diào)。
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }

  // step 3、
  // 主要是用于構(gòu)建Widget子樹的,會在如下場景被調(diào)用:
  // 
  // 1、在調(diào)用initState()之后。
  // 2、在調(diào)用didUpdateWidget()之后。
  // 3、在調(diào)用setState()之后。
  // 4、在調(diào)用didChangeDependencies()之后。
  // 5、在State對象從樹中一個位置移除后(會調(diào)用deactivate)又重新插入到樹的其它位置之后。
  @override
  Widget build(BuildContext context) {
    print("build");
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text('$_counter'),
          //點擊后計數(shù)器自增
          onPressed:()=>setState(()=> ++_counter,
          ),
        ),
      ),
    );
  }

  // step 4、
  // 當(dāng)State對象從樹中被移除時,會調(diào)用此回調(diào)。
  @override
  void deactivate() {
    super.deactivate();
    print("deactive");
  }

  // step 5、
  // 當(dāng)State對象從樹中被永久移除時調(diào)用;通常在此回調(diào)中釋放資源。
  @override
  void dispose() {
    super.dispose();
    print("dispose");
  }




  // step 3.1、
  // 是專門為了開發(fā)調(diào)試而提供的,在熱重載(hot reload)時會被調(diào)用,此回調(diào)在Release模式下永遠(yuǎn)不會被調(diào)用。
  @override
  void reassemble() {
    super.reassemble();
    print("reassemble");
  }
  // step 3.2、
  // 在widget重新構(gòu)建時,F(xiàn)lutter會調(diào)用Widget.canUpdate來檢測Widget樹中同一位置的新舊節(jié)點,然后決定是否需要更新,  
  // 如果Widget.canUpdate返回true則會調(diào)用此回調(diào)。
  @override
  void didUpdateWidget(CounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }
}

在Widget樹中獲取State對象

  • 通過Context獲取
    context對象有一個findAncestorStateOfType()方法,該方法可以從當(dāng)前節(jié)點沿著widget樹向上查找指定類型的StatefulWidget對應(yīng)的State對象。
// 查找父級最近的Scaffold對應(yīng)的ScaffoldState對象
ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>();

注意:在Flutter開發(fā)中便有了一個默認(rèn)的約定,如果StatefulWidget的狀態(tài)是希望暴露出的,應(yīng)當(dāng)在StatefulWidget中提供一個of靜態(tài)方法來獲取其State對象,開發(fā)者便可直接通過該方法來獲??;如果State不希望暴露,則不提供of方法。

  • 通過GlobalKey
    Flutter還有一種通用的獲取State對象的方法——通過GlobalKey來獲??!
// 1、定義一個globalKey, 由于GlobalKey要保持全局唯一性,我們使用靜態(tài)變量存儲
static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
...
Scaffold(
    key: _globalKey , //設(shè)置key
    ...  
)

// 2、通過GlobalKey來獲取State對象
_globalKey.currentState.openDrawer()

Flutter SDK內(nèi)置組件庫介紹

基礎(chǔ)組件庫

  • Text:帶格式的文本
  • Row/Column:彈性控件布局類,其設(shè)計是基于Web開發(fā)中的Flexbox布局模型。
  • Stack:線性布局,使用 Positioned來定位他們相對于Stack的上下左右四條邊的位置?;赪eb開發(fā)中的絕對定位(absolute positioning )布局模型設(shè)計的。
  • Container:矩形視覺元素,具有邊距(margins)、填充(padding)和應(yīng)用于其大小的約束(constraints)??梢允褂镁仃囋谌S空間中對其進行變換。

Material組件庫(Android風(fēng)格,遵循Material Design設(shè)計規(guī)范)

Material應(yīng)用程序以MaterialApp組件開始的。

Cupertino組件(iOS風(fēng)格)

豐富的Cupertino風(fēng)格的組件,盡管目前還沒有Material 組件那么豐富,但是它仍在不斷的完善中。

狀態(tài)管理

以下是管理狀態(tài)的最常見的方法:

  • Widget管理自己的狀態(tài)。
  • Widget管理子Widget狀態(tài)。
  • 混合管理(父Widget和子Widget都管理狀態(tài))。

如何決定使用哪種管理方法?下面是官方給出的一些原則可以幫助你做決定:

  • 如果狀態(tài)是用戶數(shù)據(jù),如復(fù)選框的選中狀態(tài)、滑塊的位置,則該狀態(tài)最好由父Widget管理。
  • 如果狀態(tài)是有關(guān)界面外觀效果的,例如顏色、動畫,那么狀態(tài)最好由Widget本身來管理。
  • 如果某一個狀態(tài)是不同Widget共享的則最好由它們共同的父Widget管理。

基礎(chǔ)組件庫

1、Text

  • 基本屬性
Text(
          'Welcome to first page!',
          textAlign: TextAlign.left, // 對齊方式
          maxLines: 1, // 最大行數(shù)
          overflow: TextOverflow.ellipsis, // 截斷方式
          textScaleFactor: 1.5, // 當(dāng)前字體大小的縮放因子
          style: TextStyle(
              //用于指定文本顯示的樣式如顏色、字體、粗細(xì)、背景等。
              color: Colors.blue,
              fontSize: 18.0,
              height: 1.2,
              fontFamily: "Courier",
              background: new Paint()..color = Colors.yellow,
              decoration: TextDecoration.underline,
              decorationStyle: TextDecorationStyle.dashed),
),
  • TextSpan
    如果我們需要對一個Text內(nèi)容的不同部分按照不同的樣式顯示,這時就可以使用TextSpan,它代表文本的一個“片段”。
Text.rich(TextSpan(
    children: [
     TextSpan(
       text: "Home: "
     ),
     TextSpan(
       text: "https://flutterchina.club",
       style: TextStyle(
         color: Colors.blue
       ),  
       recognizer: _tapRecognizer
     ),
    ]
))
  • DefaultTextStyle
    在Widget樹中,文本的樣式默認(rèn)是可以被繼承的(子類文本類組件未指定具體樣式時可以使用Widget樹中父級設(shè)置的默認(rèn)樣式),因此,如果在Widget樹的某一個節(jié)點處設(shè)置一個默認(rèn)的文本樣式,那么該節(jié)點的子樹中所有文本都會默認(rèn)使用這個樣式,而DefaultTextStyle正是用于設(shè)置默認(rèn)文本樣式的。

2、按鈕(Material 組件庫)

  • Material組件庫中的按鈕
    Material 組件庫中提供了多種按鈕組件如RaisedButton、FlatButton、OutlineButton等,它們都是直接或間接對RawMaterialButton組件的包裝定制,所以他們大多數(shù)屬性都和RawMaterialButton一樣。
    這些按鈕在按下時都會有“水波動畫”。并且有一個onPressed屬性來設(shè)置點擊回調(diào),當(dāng)按鈕按下時會執(zhí)行該回調(diào),如果不提供該回調(diào)則按鈕會處于禁用狀態(tài),禁用狀態(tài)不響應(yīng)用戶點擊。

3、圖片及ICON

  • ImageProvider
    其是一個抽象類,主要定義了圖片數(shù)據(jù)獲取的接口load(),從不同的數(shù)據(jù)源獲取圖片需要實現(xiàn)不同的ImageProvider ,如AssetImage是實現(xiàn)了從Asset中加載圖片的ImageProvider,而NetworkImage實現(xiàn)了從網(wǎng)絡(luò)加載圖片的ImageProvider。

  • Image組件

/* 本地加載 */ 
Image(
  image: AssetImage("images/avatar.png"),
  width: 100.0
);
// 快捷加載
Image.asset("images/avatar.png",
  width: 100.0,
)

/* 網(wǎng)絡(luò)加載 */ 
Image(
  image: NetworkImage(
      "https://avatars2.githubusercontent.com/u/20411648?s=460&v=4"),
  width: 100.0,
)
// 快捷加載
Image.network(
  "https://avatars2.githubusercontent.com/u/20411648?s=460&v=4",
  width: 100.0,
)
  • Image組件的屬性
const Image({
  ...
  this.width, //圖片的寬
  this.height, //圖片高度
  this.color, //圖片的混合色值
  this.colorBlendMode, //混合模式
  this.fit,//縮放模式
  this.alignment = Alignment.center, //對齊方式
  this.repeat = ImageRepeat.noRepeat, //重復(fù)方式
  ...
})
  • 關(guān)于Image緩存
    Flutter框架對加載過的圖片是有緩存的(內(nèi)存),默認(rèn)最大緩存數(shù)量是1000,最大緩存空間為100M。

ICON

Flutter中,可以像Web開發(fā)一樣使用iconfont,iconfont即“字體圖標(biāo)”,它是將圖標(biāo)做成字體文件,然后通過指定不同的字符而顯示不同的圖片。

在字體文件中,每一個字符都對應(yīng)一個位碼,而每一個位碼對應(yīng)一個顯示字形,不同的字體就是指字形不同,即字符對應(yīng)的字形是不同的。而在iconfont中,只是將位碼對應(yīng)的字形做成了圖標(biāo),所以不同的字符最終就會渲染成不同的圖標(biāo)。

  • 在Flutter開發(fā)中,iconfont和圖片相比有如下優(yōu)勢:
    1、體積?。嚎梢詼p小安裝包大小。
    2、矢量的:iconfont都是矢量圖標(biāo),放大不會影響其清晰度。
    3、可以應(yīng)用文本樣式:可以像文本一樣改變字體圖標(biāo)的顏色、大小對齊等。
    4、可以通過TextSpan和文本混用。

4、單選開關(guān)和復(fù)選框(Material 組件庫)

Material 組件庫中提供了Material風(fēng)格的單選開關(guān)Switch和復(fù)選框Checkbox,雖然它們都是繼承自StatefulWidget,但它們本身不會保存當(dāng)前選中狀態(tài),選中狀態(tài)都是由父組件來管理的。當(dāng)Switch或Checkbox被點擊時,會觸發(fā)它們的onChanged回調(diào),我們可以在此回調(diào)中處理選中狀態(tài)改變邏輯。
Checkbox的大小是固定的,無法自定義,而Switch只能定義寬度,高度也是固定的。

class SwitchAndCheckBoxTestRoute extends StatefulWidget {
  @override
  _SwitchAndCheckBoxTestRouteState createState() => new _SwitchAndCheckBoxTestRouteState();
}

class _SwitchAndCheckBoxTestRouteState extends State<SwitchAndCheckBoxTestRoute> {
  bool _switchSelected=true; //維護單選開關(guān)狀態(tài)
  bool _checkboxSelected=true;//維護復(fù)選框狀態(tài)
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Switch(
          value: _switchSelected,//當(dāng)前狀態(tài)
          onChanged:(value){
            //重新構(gòu)建頁面  
            setState(() {
              _switchSelected=value;
            });
          },
        ),
        Checkbox(
          value: _checkboxSelected,
          activeColor: Colors.red, //選中時的顏色
          onChanged:(value){
            setState(() {
              _checkboxSelected=value;
            });
          } ,
        )
      ],
    );
  }
}

5、輸入框及表單(Material 組件庫)

  • TextField

  • Form
    它可以對輸入框進行分組,然后進行一些統(tǒng)一操作,如輸入內(nèi)容校驗、輸入框重置以及輸入內(nèi)容保存。

6、進度指示器(Material 組件庫)

  • LinearProgressIndicator是一個線性、條狀的進度條。
LinearProgressIndicator({
  double value,
  Color backgroundColor,
  Animation<Color> valueColor,
  ...
})
  • CircularProgressIndicator是一個圓形進度條
CircularProgressIndicator({
  double value,
  Color backgroundColor,
  Animation<Color> valueColor,
  this.strokeWidth = 4.0,
  ...   
}) 

三、布局類組件

在Flutter中,根據(jù)Widget是否需要包含子節(jié)點將Widget分為了三類,分別對應(yīng)三種Element:

線性布局(Row和Column)

Row和Column都繼承自Flex,類似于Android中的LinearLayout控件。

Row({
  ...  
  TextDirection textDirection,    // 表示水平方向子組件的布局順序
  MainAxisSize mainAxisSize = MainAxisSize.max,    // 表示Row在主軸(水平)方向占用的空間
  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, // 表示子組件在Row所占用的水平空間內(nèi)對齊方式
  VerticalDirection verticalDirection = VerticalDirection.down,   // 表示Row縱軸(垂直)的對齊方向
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, // 表示子組件在縱軸方向的對齊方式
  List<Widget> children = const <Widget>[], // 子組件數(shù)組。
})

注意:如果Row里面嵌套Row,或者Column里面再嵌套Column,那么只有最外面的Row或Column會占用盡可能大的空間,里面Row或Column所占用的空間為實際大小。

彈性布局(Flex)

彈性布局允許子組件按照一定比例來分配父容器空間。Flutter中的彈性布局主要通過FlexExpanded來配合實現(xiàn)。
彈性布局的概念在其它UI系統(tǒng)中也都存在,如H5中的彈性盒子布局,Android中的FlexboxLayout等。

流式布局

注意:如果Row和Colum時,如果子widget超出屏幕范圍,則會報溢出錯誤。

Flutter中通過Wrap和Flow來支持流式布局。

Wrap({
  ...
  this.direction = Axis.horizontal,
  this.alignment = WrapAlignment.start,
  this.spacing = 0.0,
  this.runAlignment = WrapAlignment.start,
  this.runSpacing = 0.0,
  this.crossAxisAlignment = WrapCrossAlignment.start,
  this.textDirection,
  this.verticalDirection = VerticalDirection.down,
  List<Widget> children = const <Widget>[],
})

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,),
  ],
)

層疊布局 Stack、Positioned

層疊布局和Web中的絕對定位、Android中的Frame布局是相似的,子組件可以根據(jù)距父容器四個角的位置來確定自身的位置。絕對定位允許子組件堆疊起來(按照代碼中聲明的順序)。

Stack({
  this.alignment = AlignmentDirectional.topStart,
  this.textDirection,
  this.fit = StackFit.loose,
  this.overflow = Overflow.clip,
  List<Widget> children = const <Widget>[],
})

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

對齊與相對定位(Align 和 Center)

Align({
  Key key,
  this.alignment = Alignment.center,
  this.widthFactor,
  this.heightFactor,
  Widget child,
})

// Alignment繼承自AlignmentGeometry,表示矩形內(nèi)的一個點,他有兩個屬性x、y,分別表示在水平和垂直方向的偏移。
// FractionalOffset 繼承自 Alignment,它和 Alignment唯一的區(qū)別就是坐標(biāo)原點不同!FractionalOffset 的坐標(biāo)原點為矩形的左側(cè)頂點,這和布局系統(tǒng)的一致,所以理解起來會比較容易。

四、容器類Widget

填充(Padding)

Padding({
  EdgeInsetsGeometry padding,
  Widget child,
})

尺寸限制類容器

  • ConstrainedBox
    用于對子組件添加額外的約束。

  • SizedBox
    用于給子元素指定固定的寬高。

  • UnconstrainedBox
    不會對子組件產(chǎn)生任何限制,它允許其子組件按照其本身大小繪制。
    一般情況下,我們會很少直接使用此組件,但在"去除"多重限制的時候也許會有幫助。

  • AspectRatio,它可以指定子組件的長寬比

  • LimitedBox 用于指定最大寬高

  • FractionallySizedBox 可以根據(jù)父容器寬高的百分比來設(shè)置子組件寬高

裝飾容器DecoratedBox

可以在其子組件繪制前(或后)繪制一些裝飾(Decoration),如背景、邊框、漸變等。

  • BoxDecoration
    我們通常會直接使用BoxDecoration類,它是一個Decoration的子類,實現(xiàn)了常用的裝飾元素的繪制。

變換(Transform)

Transform可以在其子組件繪制時對其應(yīng)用一些矩陣變換來實現(xiàn)一些特效。
Matrix4是一個4D矩陣,通過它我們可以實現(xiàn)各種矩陣操作。

組合類容器(Container)

一個組合類容器,它本身不對應(yīng)具體的RenderObject,它是DecoratedBox、ConstrainedBox、Transform、Padding、Align等組件組合的一個多功能容器,所以我們只需通過一個Container組件可以實現(xiàn)同時需要裝飾、變換、限制的場景。

Container({
  this.alignment,
  this.padding, //容器內(nèi)補白,屬于decoration的裝飾范圍
  Color color, // 背景色
  Decoration decoration, // 背景裝飾
  Decoration foregroundDecoration, //前景裝飾
  double width,//容器的寬度
  double height, //容器的高度
  BoxConstraints constraints, //容器大小的限制條件
  this.margin,//容器外補白,不屬于decoration的裝飾范圍
  this.transform, //變換
  this.child,
})

五、Material組件庫常用

Scaffold

Scaffold是一個路由頁的骨架,包含導(dǎo)航欄、抽屜菜單(Drawer)以及底部Tab導(dǎo)航菜單等。

TabBar

AppBar是一個Material風(fēng)格的導(dǎo)航欄,通過它可以設(shè)置導(dǎo)航欄標(biāo)題、導(dǎo)航欄菜單、導(dǎo)航欄底部的Tab標(biāo)題等。

AppBar({
  Key key,
  this.leading, //導(dǎo)航欄最左側(cè)Widget,常見為抽屜菜單按鈕或返回按鈕。
  this.automaticallyImplyLeading = true, //如果leading為null,是否自動實現(xiàn)默認(rèn)的leading按鈕
  this.title,// 頁面標(biāo)題
  this.actions, // 導(dǎo)航欄右側(cè)菜單
  this.bottom, // 導(dǎo)航欄底部菜單,通常為Tab按鈕組
  this.elevation = 4.0, // 導(dǎo)航欄陰影
  this.centerTitle, //標(biāo)題是否居中 
  this.backgroundColor,
  ...   //其它屬性見源碼注釋
})

抽屜菜單Drawer

Scaffold的drawer和endDrawer屬性可以分別接受一個Widget來作為頁面的左、右抽屜菜單。

FloatingActionButton

是Material設(shè)計規(guī)范中的一種特殊Button,通常懸浮在頁面的某一個位置作為某種常用動作的快捷入口,如本節(jié)示例中頁面右下角的"?"號按鈕。

底部導(dǎo)航

通過Material組件庫提供的BottomNavigationBar和BottomNavigationBarItem兩種組件來實現(xiàn)Material風(fēng)格的底部導(dǎo)航欄。


六、可滾動組件和列表組件

abstract class ScrollView extends StatelessWidget
abstract class BoxScrollView extends ScrollView
class ListView extends BoxScrollView
class GridView extends BoxScrollView
class CustomScrollView extends ScrollView


class Scrollable extends StatefulWidget
class SingleChildScrollView extends StatelessWidget


abstract class Listenable
class ChangeNotifier implements Listenable
class ScrollController extends ChangeNotifier

可滾動組件簡介

當(dāng)組件內(nèi)容超過當(dāng)前顯示視口(ViewPort)時,如果沒有特殊處理,F(xiàn)lutter則會提示Overflow錯誤。

  • Scrollable組件
    可滾動組件都直接或間接包含一個Scrollable組件,因此它們包括一些共同的屬性
Scrollable({
  ...
  this.axisDirection = AxisDirection.down, // 滾動方向
  this.controller, 
  this.physics, 
  @required this.viewportBuilder, // 
})

// ScrollController的主要作用是控制滾動位置和監(jiān)聽滾動事件。
// 默認(rèn)情況下,Widget樹中會有一個默認(rèn)的PrimaryScrollController

// ScrollPhysics 決定可滾動組件如何響應(yīng)用戶操作。
// Flutter SDK中包含了兩個ScrollPhysics的子類,他們可以直接使用:ClampingScrollPhysics(Android下微光效果);BouncingScrollPhysics(iOS下彈性效果)。
  • Scrollbar 和 CupertinoScrollbar
    Scrollbar一個Material風(fēng)格的滾動指示器(滾動條),如果要給可滾動組件添加滾動條,只需將Scrollbar作為可滾動組件的任意一個父級組件即可。
    CupertinoScrollbar是iOS風(fēng)格的滾動條,如果你使用的是Scrollbar,那么在iOS平臺它會自動切換為CupertinoScrollbar。

  • ViewPort視口
    指一個Widget的實際顯示區(qū)域。
    在很多布局系統(tǒng)中都有ViewPort的概念,在Flutter中,術(shù)語ViewPort(視口),如無特別說明。

  • 基于Sliver的延遲構(gòu)建
    通??蓾L動組件的子組件可能會非常多、占用的總高度也會非常大;如果要一次性將子組件全部構(gòu)建出將會非常昂貴!

    為此,F(xiàn)lutter中提出一個Sliver(中文為“薄片”的意思)概念,如果一個可滾動組件支持Sliver模型,那么該滾動可以將子組件分成好多個“薄片”(Sliver),只有當(dāng)Sliver出現(xiàn)在視口中時才會去構(gòu)建它,這種模型也稱為“基于Sliver的延遲構(gòu)建模型”。

    可滾動組件中有很多都支持基于Sliver的延遲構(gòu)建模型,如ListView、GridView,但是也有不支持該模型的,如SingleChildScrollView。

SingleChildScrollView

類似于Android中的ScrollView,它只能接收一個子組件。

SingleChildScrollView({
  this.scrollDirection = Axis.vertical, //滾動方向,默認(rèn)是垂直方向
  this.reverse = false,  // 是否按照閱讀方向相反的方向滑動,此屬性本質(zhì)上是決定可滾動組件的初始滾動位置是在“頭”還是“尾”,取false時,初始滾動位置在“頭”,反之則在“尾”,讀者可以自己試驗。
  this.padding, 
  bool primary,  // 是否使用widget樹中默認(rèn)的PrimaryScrollController;
  this.physics, 
  this.controller,
  this.child,
})

ListView

最常用的可滾動組件之一,它可以沿一個方向線性排布所有子組件,并且它也支持基于Sliver的延遲構(gòu)建模型。

ListView({
  ...  
  //可滾動widget公共參數(shù)
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController controller,
  bool primary,
  ScrollPhysics physics,
  EdgeInsetsGeometry padding,
  
  //ListView各個構(gòu)造函數(shù)的共同參數(shù)  
  double itemExtent, 
  bool shrinkWrap = false,
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  double cacheExtent,
    
  //子widget列表
  List<Widget> children = const <Widget>[],
})

itemExtent:該參數(shù)如果不為null,則會強制children的“長度”為itemExtent的值;這里的“長度”是指滾動方向上子組件的長度,也就是說如果滾動方向是垂直方向,則itemExtent代表子組件的高度;如果滾動方向為水平方向,則itemExtent就代表子組件的寬度。在ListView中,指定itemExtent比讓子組件自己決定自身長度會更高效,這是因為指定itemExtent后,滾動系統(tǒng)可以提前知道列表的長度,而無需每次構(gòu)建子組件時都去再計算一下,尤其是在滾動位置頻繁變化時(滾動系統(tǒng)需要頻繁去計算列表高度)。

shrinkWrap:該屬性表示是否根據(jù)子組件的總長度來設(shè)置ListView的長度,默認(rèn)值為false 。默認(rèn)情況下,ListView的會在滾動方向盡可能多的占用空間。當(dāng)ListView在一個無邊界(滾動方向上)的容器中時,shrinkWrap必須為true。

addAutomaticKeepAlives:該屬性表示是否將列表項(子組件)包裹在AutomaticKeepAlive 組件中;典型地,在一個懶加載列表中,如果將列表項包裹在AutomaticKeepAlive中,在該列表項滑出視口時它也不會被GC(垃圾回收),它會使用KeepAliveNotification來保存其狀態(tài)。如果列表項自己維護其KeepAlive狀態(tài),那么此參數(shù)必須置為false。

addRepaintBoundaries:該屬性表示是否將列表項(子組件)包裹在RepaintBoundary組件中。當(dāng)可滾動組件滾動時,將列表項包裹在RepaintBoundary中可以避免列表項重繪,但是當(dāng)列表項重繪的開銷非常小(如一個顏色塊,或者一個較短的文本)時,不添加RepaintBoundary反而會更高效。和addAutomaticKeepAlive一樣,如果列表項自己維護其KeepAlive狀態(tài),那么此參數(shù)必須置為false。

  • 通過children添加cell(適用于少量數(shù)據(jù))

  • 通過ListView.builder添加cell(適合列表項比較多或者無限的情況,是支持基于Sliver的懶加載模型)

ListView.builder({
  // ListView公共參數(shù)已省略  
  ...
  @required IndexedWidgetBuilder itemBuilder, // 它是列表項的構(gòu)建器,返回值為一個widget。當(dāng)列表滾動到具體的index位置時,會調(diào)用該構(gòu)建器構(gòu)建列表項。
  int itemCount, // 列表項的數(shù)量,如果為null,則為無限列表。
  ...
})

// 例如
ListView.builder(
    itemCount: 100,
    itemExtent: 50.0, //強制高度為50.0
    itemBuilder: (BuildContext context, int index) {
      return ListTile(title: Text("$index"));
    }
);
  • ListView.separated
    可以在生成的列表項之間添加一個分割組件,它比ListView.builder多了一個separatorBuilder參數(shù),該參數(shù)是一個分割組件生成器。

GridView

用于構(gòu)建一個二維網(wǎng)格列表,其默認(rèn)構(gòu)造函數(shù)定義如下:

GridView({
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController controller,
  bool primary,
  ScrollPhysics physics,
  bool shrinkWrap = false,
  EdgeInsetsGeometry padding,
  @required SliverGridDelegate gridDelegate, //控制子widget layout的委托
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  double cacheExtent,
  List<Widget> children = const <Widget>[],
})

CustomScrollView



參考

Flutter實戰(zhàn)
Material風(fēng)格的組件展示

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容