(七)flutter入門之組件深入與常見基礎(chǔ)組件

上一篇博客中我們了解到窃祝,flutter中一切皆widget桩了,并且我們學(xué)習(xí)了widget相關(guān)比較詳細的生命周期等相關(guān)的介紹蔓挖,但是夕土,我們還需要了解一個概念,那就是Element

Element

flutter中我們開發(fā)的時候都是開發(fā)的一個個widget瘟判,在builder方法中進行每個widget的ui構(gòu)建怨绣,使用setState進行狀態(tài)的更新,從而刷新ui拷获,但是在flutter中真正的元素相關(guān)的類不是widget篮撑,而是Element,而widget屬于flutter中用來描述ui的一個基本數(shù)據(jù)匆瓜,我們可以理解為一個組件的完整構(gòu)建包裝類赢笨,而每一個widget都會綁定對應(yīng)的Element元素未蝌,但是我們需要注意的是一個Element元素只能對應(yīng)一個widget,但是一個widget可以對應(yīng)多個Element元素质欲,原因就在于widget可以包含多個其他的widget树埠,相當于一個ui樹可以掛載其他的節(jié)點一般

接下來我們大概看一下flutter中的所有widget的基類的大概源碼申明:

//widget繼承自DiagnosticableTree,DiagnosticableTree是一個診斷樹嘶伟,主要用來提供debug調(diào)試診斷功能的ui樹
@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  //每個widget都可以設(shè)置一個唯一的key怎憋,這個key在js的組件開發(fā)中并不陌生類似于組件的ref,在安卓原生開發(fā)中類似于每一個ui元素的唯一id九昧,可以用來在canUpdate方法中判斷是不是能夠復(fù)用原來的widget
  final Key key;

  //我們剛剛說一個element元素肯定指定了一個widget绊袋,就是這個方法,我們開發(fā)基本不使用铸鹰,系統(tǒng)也會默認在創(chuàng)建widget的時候創(chuàng)建一個element癌别,掛載在ui樹上
  @protected
  Element createElement();

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }
  
  //用來debug的模式下可以進行診斷的方法
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }
 
  //widget具體的更新ui是否復(fù)用舊的組件的策略方法,源碼會將舊的widget的key和runtimeType(類型)與調(diào)用修改ui的方法以后的widget的key和類型進行比較蹋笼,如果都是一樣的展姐,說明我們當前需要更新操作,就會將原來的widget的對應(yīng)改變的屬性修改剖毯,如果不一樣圾笨,就會創(chuàng)建調(diào)用修改ui方法后指定的widget掛載在ui樹對應(yīng)的位置上(同時我們也能看出來,flutter的組件中如果都指定了key的話逊谋,可以有效提升ui效率)
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

從上面大概可以看出來一個基本的widget包含的大概的方法結(jié)構(gòu)擂达,而我們開發(fā)使用的widget只有兩種,一個是StatelessWidgetStatefulWidget 胶滋,這兩個都直接或者間接繼承了Widget板鬓,而我們flutter的sdk中默認提供了常見的組件,全部都基于這兩類widget究恤,所以接下來我們來開始正式的了解flutter常見的ui組件相關(guān)的內(nèi)容

渲染組件分類

我們上面說過俭令,flutter中真正渲染的不是widget,而是Element部宿,而widget和Element之間是createElement方法綁定的唤蔗,而flutter中將Element總共分成了三類(博主基本上組件都看了一遍,應(yīng)該只有這三類)窟赏,大概如下:

Widget類型 Element類型 說明
LeafRenderObjectWidget LeafRenderObjectElement 葉子節(jié)點妓柜,一般來說是flutter中最基礎(chǔ)的最小的組件,不能包裹其他任何Widget的常見組件涯穷,比如Text棍掐、Icon等
SingleChildRenderObjectWidget SingleChildRenderObjectElement 包裹類的組件,這種組件普遍是用來提供約束或者提供特殊功能如提供點擊事件等的組件拷况,內(nèi)部可以包含一個基礎(chǔ)組件作煌,比如 InkWell等
MultiChildRenderObjectWidget MultiChildRenderObjectElement 容器類組件掘殴,這種組件一般可以用作布局操作粟誓,類似原生的布局容器,內(nèi)部包裹多個widget鹰服,比如Stack病瞳、Row等

我們可以看到渲染組件widget其實對應(yīng)的類型有三種悲酷,其實我們開發(fā)中使用的所有的組件幾乎都是直接或者間接繼承了這三類widget,而這三種wieget最終都是來源于RenderObjectWidget 设易,這個RenderObjectWidget 才是我們最終進行渲染ui的對象逗柴,所以整體的繼承關(guān)系如下:

Widget > RenderObjectWidget > (Leaf/SingleChild/MultiChild)RenderObjectWidget

基礎(chǔ)組件(子組件)

Text組件

flutter中,我們?nèi)绻枰M行文本或者文本相關(guān)屬性的樣式等戏溺,就需要使用Text組件屠尊,(類似于原生安卓中的TextView)知染,接下來我們來看下Text組件的構(gòu)造

/// Creates a text widget.
  ///
  /// If the [style] argument is null, the text will use the style from the
  /// closest enclosing [DefaultTextStyle].
  const Text(this.data, {//data必傳斑胜,說明必須要有一個文本的內(nèi)容
    Key key,//每一個widget都會有key字段,用作唯一索引
    this.style,//樣式掺炭,類型為TextStyle
    this.strutStyle,
    this.textAlign,//文本對齊方式凭戴,類型為TextAlign類型
    this.textDirection,//文本的方向,類型為TextDirection類型
    this.locale,//很少使用者冤,用于在相同的Unicode字符可以選擇字體時,會根據(jù)當前的國際化語言展示不一樣的內(nèi)容
    this.softWrap,//柔軟包裹档痪,默認false,作用是是否選擇在換行符的地方觸發(fā)換行操作腐螟,默認是不限制水平空間包裹范圍
    this.overflow,//處理溢出的方式困后,參數(shù)類型為TextOverflow 
    this.textScaleFactor,//文本縮放因子
    this.maxLines,//最大的行數(shù)衬廷,可以限制文本能展示的最大的行數(shù)
    this.semanticsLabel,
  }) : assert(data != null),
       textSpan = null,
       super(key: key);

上面的構(gòu)造我們大概知道了每個屬性的作用吗跋,這里我們通過一個案例來說明overflow、maxLines 和textScaleFactor 等看起來具有特殊效果的屬性救鲤,如下:

Text("Hello world",
  textAlign: TextAlign.center,//案例1
);

Text("Hello world! . " * 6
  maxLines: 1,
  overflow: TextOverflow.ellipsis,//案例2
);

Text("Hello world",
  textScaleFactor: 1.5,//案例3
);

Text("Hello world "*10,  //案例4:字符串后面使用*代表當前的字符串重復(fù)出現(xiàn)n次(這是因為dart語言中可以重寫運算符)
  textAlign: TextAlign.center,
);

上面的案例可以顯示出來一個很簡單的文本效果秩冈,但是我們要知道的是:

  • 案例1中我們指定了textAlign屬性,這個屬性指定了居中丹锹,但是如果我們運行起來的話楣黍,會發(fā)現(xiàn)文本不是居中顯示的棱烂,這是因為textAlign屬性針對的是當前的子組件生效,只要我們的Text寬度沒有指定為一行颊糜,這個屬性指定居中沒有任何效果
  • 如果說案例1是因為文本不夠的話衬鱼,那么案例2我們重復(fù)出現(xiàn)了6次一樣的內(nèi)容,而在flutter中Text展示文本的內(nèi)容如果滿了一行會自動換行的蒜胖,所以案例2應(yīng)該換行顯示出來結(jié)果,可是如果我們運行起來會發(fā)現(xiàn)結(jié)果根本不是這樣台谢,而是一行顯示岁经,超過的內(nèi)容自動變成了省略號代替蒿偎,看到這里我們可能理解了overflow的作用怀读,用來處理文本內(nèi)容超了以后的效果菜枷,而設(shè)置為TextOverflow.ellipsis以后就是設(shè)置超過的內(nèi)容為省略號
  • 案例3我們可以看到設(shè)置的屬性是textScaleFactor叁丧,而當我們運行起來的時候拥娄,會驚訝的發(fā)現(xiàn),文本變大到原來的150%牡昆,看到這里就明白了textScaleFactor所謂的縮放因子作用就是把整個文本的內(nèi)容按照比例縮放
  • 最后一個案例摊欠,我們可以理解為對第一個案例最有利的補充些椒,當然我們運行起來以后的效果的確證明了Text會自動換行,并且有文本進行居中赢乓,但是我們需要注意一點牌芋,TextAlign.center文本居中并不是按照總共的文本整體進行居中以后計算該有的行數(shù)和位置尼斧,而是滿一行的內(nèi)容占滿一行试吁,多出來并且不足一行的文本進行居中顯示
TextStyle

上面我們也看到文本可以設(shè)置樣式熄捍,并且類型是TextStyle類型,那么我們接下來看看TextStyle的構(gòu)造函數(shù)

const TextStyle({
    this.inherit = true,//是否使用外部組件(或者全局)的樣式來替換當前的style缚柏,默認是true
    this.color,//文本的顏色
    this.fontSize,//文本大小
    this.fontWeight,//文本字體粗細
    this.fontStyle,//文本的樣式
    this.letterSpacing,//每一個字母之間的距離币喧,可以使用負值縮小空白
    this.wordSpacing,//每個單詞(相當于一個連續(xù)的文本)之間的距離,可以使用負值縮小空白
    this.textBaseline,//文本對齊的基準線
    this.height,//文字高度
    this.locale,//根據(jù)你當前國際化本地環(huán)境的情況自動切換文本語言
    this.foreground,//繪制一個前景(與背景相反)干发,類型為Paint 
    this.background,//繪制一個背景枉长,類型為Paint 
    this.shadows,//將在文本下方繪制陰影列表
    this.decoration,//可以在文本下方進行裝飾琼讽,比如繪制一個下劃線钻蹬,類型為TextDecoration
    this.decorationColor,//文本下方裝飾器的顏色
    this.decorationStyle,//文本裝飾器的樣式
    this.debugLabel,//debug模式下默認顯示的提示
    String fontFamily,//用來重新創(chuàng)建文本樣式(一般使用在我們外部引入的字體庫的時候,但是外部引入字體庫以后這里必須指定package桂敛,package+fontFamily組合確定fontFamily屬性的具體樣式)
    List<String> fontFamilyFallback,
    String package,//當我們需要fontFamily的時候我們就需要指定當前的字體庫的包名
  }) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily',
       _fontFamilyFallback = fontFamilyFallback,
       _package = package,
       assert(inherit != null),
       assert(color == null || foreground == null, _kColorForegroundWarning);

好了术唬,TextStyle相關(guān)的屬性基本上博主都標注起來了滚澜,接下來我們舉一個案例來加深對TextStyle的部分容易混淆的屬性的理解:

Text("Hello world",
            style: TextStyle(
              color: Colors.green,
              fontSize: 20.0,
              height: 1.5,  
              fontFamily: "Courier",
              background: new Paint()..color=Colors.redAccent,
              decoration:TextDecoration.underline,
              decorationStyle: TextDecorationStyle.dashed
            ),
          );

但是這里我們需要針對幾點屬性進行一下說明:

  • height屬性我們這里指定了1.5设捐,可以看出來該屬性是一個比例因子萝招,并不是一個具體的值,默認是1曙蒸,而行高的計算方式是fontsize屬性 * height屬性則為每一行文本的高度
  • fontsize我們可以看出來和原生開發(fā)的文本大小似乎一樣纽窟,可以指定具體的值兼吓,而textScaleFactor 只是指定了整體的文本的縮放比例,我們可以理解為文本具體展示的大小為textScaleFactor * fontSize的值
TextSpan

上面我們介紹的文本及其樣式只能指定一種审孽,而我們開發(fā)的過程中往往遇到比較復(fù)雜的樣式瓷胧,這個時候我們就可以使用TextSpan指定了,比如需要實現(xiàn)一個帶鏈接效果的文本或者一個多種字體顏色的文本等,我們來看下構(gòu)造:

const TextSpan({
  TextStyle style, //文本樣式
  Sting text,//文本內(nèi)容
  List<TextSpan> children,//這里就是具體指定了多個樣式效果的組件集合
  GestureRecognizer recognizer,//手勢操作識別管理器杂数,可以對繪制樣式的流程進行操作
});

我們可以看到TextSpan的構(gòu)造屬性很少揍移,比較簡單

默認全局樣式

我們上面可以看到TextStyle中有inherit屬性反肋,該屬性的作用就是是否選擇繼承默認的樣式作為當前的樣式石蔗,也就是說在開發(fā)的過程中,我們可以指定一個全局的樣式诉探,這樣需要復(fù)用的時候就可以直接繼承使用肾胯,不需要再去指定新的樣式(可以理解為原生開發(fā)中xml布局文件中style樣式復(fù)用)耘纱,接下來我們看下使用的簡單案例:

DefaultTextStyle(
  //1.設(shè)置文本默認樣式  
  style: TextStyle(
    color:Colors.red,
    fontSize: 20.0,
  ),
  textAlign: TextAlign.start,
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      Text("hello world"),
      Text("not extends DefaultTextStyle",
        style: TextStyle(
          inherit: false, //2.不繼承默認樣式,所以運行的時候的樣式就不會是紅色
        ),
      ),
    ],
  ),
);

按鈕

在flutter中束析,按鈕的類型比較豐富,不過md風(fēng)格中的所有的按鈕都直接或者間接是RawMaterialButton 的子類弄慰,都默認實現(xiàn)了md風(fēng)格曹动,而在flutter中只要是md風(fēng)格的按鈕牲览,都是默認有按下水波紋的效果第献,并且都有onPressed 函數(shù)作為點擊事件觸發(fā)的回調(diào),如果當前方法并沒有重寫仔拟,那么就默認為禁用點擊事件(onPressed為必須填寫的參數(shù)飒赃,所以我們不需要點擊事件的時候只要指定一個空方法即可)

RaisedButton

RaisedButton 又叫做漂浮按鈕载佳,默認情況下蔫慧,我們按下的時候,會有陰影和灰色的變大的背景睡扬,從而造成一個漂浮的效果黍析,使用方法如下:

RaisedButton(
  child: Text("normal"),//按鈕中掛載一個內(nèi)容為normal的文本橄仍,即文本漂浮按鈕
    onPressed: (){
        //點擊事件
    },
);
FlatButton

FlatButton 指的是扁平類的按鈕侮繁,這種按鈕和漂浮按鈕比起來,區(qū)別在于背景透明并且不帶任何陰影娩贷,按壓以后只有背景色出現(xiàn)彬祖,使用如下:

FlatButton(
  child: Text("normal"),//按鈕中掛載一個內(nèi)容為normal的文本品抽,即扁平文本按鈕
  onPressed: (){
        //點擊事件
    },
)
OutlineButton

OutlineButton 是一種帶有邊框圆恤,背景透明并且無陰影的按鈕,該按鈕在按下以后會出現(xiàn)一個高亮的按鈕邊框羽历,同時按壓的時候會有一個比較淡的背景秕磷,使用如下:

OutlineButton(
  child: Text("normal"),//按鈕中掛載一個內(nèi)容為normal的文本,即邊框文本按鈕
  onPressed: (){
        //點擊事件
    },
)
IconButton

iconButton是一個可以點擊的icon按鈕疏尿,和其他按鈕不一樣的是润歉,iconButton不需要指定child颈抚,默認只需要指定icon為child即可贩汉,并且該按鈕同樣只有在被按下的時候才會出現(xiàn)背景:

IconButton(
  icon: Icon(Icons.thumb_up),//指定一個icon為系統(tǒng)自帶的某個icon
  onPressed: (){
        //點擊事件
    },
)

由于幾種按鈕源碼幾乎都一樣,這里我們就來看看IconButton的源碼:

const RaisedButton({
    Key key,
    @required VoidCallback onPressed, //點擊事件的回調(diào)函數(shù)
    ValueChanged<bool> onHighlightChanged,//基礎(chǔ)數(shù)據(jù)更改的時候觸發(fā)回調(diào)函數(shù)
    ButtonTextTheme textTheme,//用來指定按鈕的主題樣式褐鸥,我們看到的按下效果和有無邊框等效果就是當前屬性指定叫榕,類型為ButtonTextTheme晰绎,可以和ButtonTheme括丁、ButtonThemeData一起指定基礎(chǔ)按鈕顏色背景大小等
    Color textColor,//按鈕中的文本顏色
    Color disabledTextColor,//禁用的時候文本的顏色
    Color color,//按鈕的顏色
    Color disabledColor,//禁用的時候按鈕顏色
    Color highlightColor,//選中的時候的顏色
    Color splashColor,//點擊時史飞,水波動畫中水波的顏色
    Brightness colorBrightness,//按鈕的主題顏色,默認是淺色系
    double elevation,//未按下的時候抽诉,彈出樣式的高度(漂浮效果的視覺高度)
    double highlightElevation,//當按下的時候迹淌,彈出樣式的高度(漂浮效果的視覺高度)
    double disabledElevation,//禁用的時候按鈕的彈出樣式的高度
    EdgeInsetsGeometry padding,//按鈕內(nèi)部的內(nèi)邊距
    ShapeBorder shape,//形狀邊框
    Clip clipBehavior = Clip.none,
    MaterialTapTargetSize materialTapTargetSize,//指定當前按鈕所指定的material可以點擊的部分的尺寸大小巍沙,類型為MaterialTapTargetSize
    Duration animationDuration,
    Widget child,
  }) : assert(elevation == null || elevation >= 0.0),
       assert(highlightElevation == null || highlightElevation >= 0.0),
       assert(disabledElevation == null || disabledElevation >= 0.0),
       super(
         key: key,
         onPressed: onPressed,
         onHighlightChanged: onHighlightChanged,
         textTheme: textTheme,
         textColor: textColor,
         disabledTextColor: disabledTextColor,
         color: color,
         disabledColor: disabledColor,
         highlightColor: highlightColor,
         splashColor: splashColor,
         colorBrightness: colorBrightness,
         elevation: elevation,
         highlightElevation: highlightElevation,
         disabledElevation: disabledElevation,
         padding: padding,
         shape: shape,
         clipBehavior: clipBehavior,
         materialTapTargetSize: materialTapTargetSize,
         animationDuration: animationDuration,
         child: child,
       );

大概了解了這些屬性以后句携,我們來實現(xiàn)一個簡單的自定義按鈕:

new FlatButton(
  color: Colors.blue,
  highlightColor: Colors.blue[700],//按下的顏色為藍[700]
  colorBrightness: Brightness.dark,//按鈕的主題為暗色系
  splashColor: Colors.grey,//指定按下的時候水波紋的顏色為灰色
  child: Text("RaisedButton Submit"),
  shape:RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),//指定按鈕的邊框為圓形邊框
  onPressed: (){
        //點擊事件
    },
)

這樣我們就實現(xiàn)了一個藍色按鈕矮嫉,并且按下的時候會有灰色的水波紋覆蓋牍疏,文本內(nèi)容為‘RaisedButton Submit’的簡單自定義按鈕

多選框/單選框

在日常開發(fā)中鳞陨,無論是web前端開發(fā),還是原生安卓開發(fā)援岩,一定都會使用到單選按鈕和多選按鈕這兩種基礎(chǔ)組件享怀,flutter默認也提供了這兩個基礎(chǔ)組件趟咆,由于這兩個組件并不復(fù)雜值纱,我們大概看下這兩個組件的申明:

單選按鈕Switch

const Switch({
    Key key,
    @required this.value,//單選按鈕的值
    @required this.onChanged,//當值改變以后觸發(fā)的回調(diào)函數(shù)
    this.activeColor,//選中時的顏色
    this.activeTrackColor,//選中時的單選框軌道顏色虐唠,默認的情況下不透明為50%,如果使用Switch.adaptive構(gòu)造創(chuàng)建當前單選框妈橄,當前屬性無效
    this.inactiveThumbColor,//關(guān)閉的時候按壓的顏色眷蚓,如果使用Switch.adaptive構(gòu)造創(chuàng)建當前單選框反番,當前屬性無效
    this.inactiveTrackColor,//在關(guān)閉的時候單選框軌道使用的顏色,如果使用Switch.adaptive構(gòu)造創(chuàng)建當前單選框投队,當前屬性無效
    this.activeThumbImage,//選中的時候的圖案爵川,如果使用Switch.adaptive構(gòu)造創(chuàng)建當前單選框寝贡,當前屬性無效
    this.inactiveThumbImage,//關(guān)閉的時候按壓的圖案圃泡,如果使用Switch.adaptive構(gòu)造創(chuàng)建當前單選框,當前屬性無效
    this.materialTapTargetSize,
    this.dragStartBehavior = DragStartBehavior.down,//監(jiān)聽的手勢事件為按下事件
  }) : _switchType = _SwitchType.material,
       assert(dragStartBehavior != null),
       super(key: key);

多選按鈕checkbox:

const Checkbox({
    Key key,
    @required this.value,//必傳參數(shù)价说,當前多選框的內(nèi)容
    this.tristate = false,//是否開啟三態(tài)熔任,默認為false
    @required this.onChanged,//當前選中狀態(tài)改變的時候觸發(fā)的回調(diào)函數(shù)
    this.activeColor,//選中的時候的顏色
    this.checkColor,//進行驗證的時候的顏色
    this.materialTapTargetSize,
  }) : assert(tristate != null),
       assert(tristate || value != null),
       super(key: key);

從上面的屬性注釋大概可以看出來每一個屬性大概的作用疑苔,但是我們注意到checkbox上有一個屬性tristate甸鸟,從字面意義來看抢韭,是三態(tài)的意思,何為三態(tài)瞧省?這里我們需要注意一下鞍匾,flutter中的checkbox兼容了web和原生開發(fā)橡淑,可以支持最大三個值咆爽,分別為 false,null和true符糊,而在安卓開發(fā)中男娄,一般都是flase或者true沪伙,所以這里flutter默認禁止開啟三態(tài)县好,即不允許使用null屬性作為當前是否選中的標志缕贡,如果為true晾咪,那么會多一個null屬性作為選中標識,這里建議不開啟三態(tài)塞赂,僅僅作為擴展進行了解

表單

除了上述的三種基礎(chǔ)組件類型以外宴猾,我們平時開發(fā)的過程中叼旋,使用最多的還有表單組件及其其他組件夫植,比如TextField详民,F(xiàn)orm等

TextField

在前端編程中,我們需要指定一個輸入框一般都是Input及其變種組件衫冻,而在原生安卓中隅俘,這類組件一般為EditText,而在flutter中碌宴,這類組件為TextField贰镣,接下來我們來看看這類組件的構(gòu)造:

const TextField({
    Key key,
    this.controller,//由于是活動的動態(tài)的組件碑隆,一般會有一個對應(yīng)的控制器蹬音,可以通過綁定控制器著淆,在控制器中獲取當前的屬性值永部,焦點操作等苔埋,類型為TextEditingController
    this.focusNode,//定義了當前組件焦點操作的handler,屬于一個長期存在的對象孕惜,一般可以交給父組件來管理當前handler衫画,同時削罩,我們可以在當前handler中對焦點事件進行監(jiān)控费奸,只需要在當前handler中添加一個監(jiān)聽器即可愿阐,在FocusScope.of(context).requestFocus(myFocusNode)這種上下文切換的時候或者我們手動調(diào)用FocusScope對當前焦點進行改變的時候缨历,就會觸發(fā)當前的handler監(jiān)聽方法。參數(shù)類型為FocusNode 
    this.decoration = const InputDecoration(),//默認的裝飾為在文本下繪制一條線丛肮,可以對其設(shè)置圖標宝与,校驗成功的提示文本和錯誤的時候提示的文本信息
    TextInputType keyboardType,//該屬性可以指定我們在輸入鍵盤上最后一個按鈕是什么习劫,比如我們輸入的時候诽里,很多廠商默認的最后一個是完成按鈕或者是放大鏡圖標按鈕等,當前屬性可以動態(tài)修改為其他的按鈕,可取值為:(text-文本輸入鍵盤,multiline-多行輸入文本匿乃,number-只能輸入數(shù)字的鍵盤,phone-只能輸入電話號碼的鍵盤拒贱,datetime-輸入日期的鍵盤逻澳,emailAddress-可以輸入email的鍵盤暖呕,url-顯示url地址的鍵盤)
    this.textInputAction,//當前屬性與上一個keyboardType相關(guān)斜做,用來定義自定義按鈕的類型,只有在按鈕的keyboardType是TextInputType.multiline的時候才是TextInputAction.newline湾揽,否則其他情況下都是TextInputAction.done
    this.textCapitalization = TextCapitalization.none,//文本大寫瓤逼,默認為none
    this.style,//當前文本輸入框的樣式,類型為TextStyle
    this.textAlign = TextAlign.start,//指定編輯框內(nèi)文本的對齊方式库物,默認是從頭開始對齊
    this.textDirection,//文字的方向霸旗,類型為TextDirection
    this.autofocus = false,//當前組件是否默認就是獲取焦點的,一般情況下多個組件組合的時候戚揭,我們制定第一個為true,其他的都是false民晒,手動控制焦點切換
    this.obscureText = false,
    this.autocorrect = true,//是否開啟自動更正精居,默認會開啟當前輔助功能
    this.maxLines = 1,//默認的最大行數(shù)锄禽,如果上面的keyboardType屬性我們設(shè)置為multiline類型的話,當前屬性設(shè)置其他值才會有效箱蟆,否則默認都是一行
    this.maxLength,//設(shè)置我們可以輸入的最大的文本長度
    this.maxLengthEnforced = true,//是否允許超過我們指定的maxLength沟绪,如果為true,則會攔截超過文本的輸入空猜,不允許其輸入绽慈,如果為false,不會攔截辈毯,但是會在開啟驗證以后提供計數(shù)器和驗證后的警告
    this.onChanged,//輸入改變的回調(diào)函數(shù)
    this.onEditingComplete,//輸入完成以后的回調(diào)函數(shù)
    this.onSubmitted,//提交的回調(diào)函數(shù)
    this.inputFormatters,//我們手動指定的輸入文本的格式化條件坝疼,可以用來校驗,參數(shù)類型為List<TextInputFormatter>
    this.enabled,//是否為禁用
    this.cursorWidth = 2.0,//默認光標的寬度為2.0
    this.cursorRadius,//光標的半徑谆沃,特殊類型下有必要使用
    this.cursorColor,//光標的顏色(默認情況下使用當前主題一致的顏色)
    this.keyboardAppearance,//當前屬性僅對ios有效钝凶,可以調(diào)節(jié)外觀
    this.scrollPadding = const EdgeInsets.all(20.0),//默認的padding為全部20px
    this.dragStartBehavior = DragStartBehavior.down,//默認手勢監(jiān)聽為獲取焦點的事件為down事件
    this.enableInteractiveSelection,//是否啟動交互選擇,目前為止從來沒用過
    this.onTap,//flutter中的組件的自帶的點擊事件都為onTap
    this.buildCounter,//使用widget構(gòu)建者的回調(diào)方法唁影,我們可以在當前方法中使用InputCounterWidgetBuilder進行構(gòu)建當前的ui
  }) : assert(textAlign != null),
       assert(autofocus != null),
       assert(obscureText != null),
       assert(autocorrect != null),
       assert(maxLengthEnforced != null),
       assert(scrollPadding != null),
       assert(dragStartBehavior != null),
       assert(maxLines == null || maxLines > 0),
       assert(maxLength == null || maxLength == TextField.noMaxLength || maxLength > 0),
       keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
       super(key: key);

上面我們提到耕陷,TextField這類動態(tài)組件一般都會綁定controller,用來操作焦點以及獲取值等操作据沈,接下來哟沫,我們就簡單實現(xiàn)一個切換焦點的案例:

import 'package:flutter/material.dart';

class FocusDemo extends StatefulWidget {
  @override
  _FocusDemoState createState() => new _FocusDemoState();
}

class _FocusDemoState extends State<FocusDemo> {
  FocusNode focusNode1 = new FocusNode();//用來控制用戶名焦點的handler
  FocusNode focusNode2 = new FocusNode();//用來控制密碼焦點的handler
  TextEditingController userNameController = new TextEditingController();//用戶名綁定的控制器

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(16.0),
      child: Column(
        children: <Widget>[
          TextField(
            autofocus: true, 
            focusNode: focusNode1,//關(guān)聯(lián)handler1
            controller: userNameController,//關(guān)聯(lián)用戶名控制器
            decoration: InputDecoration(
                labelText: "用戶名",
                hintText: "請輸入用戶名",
                prefixIcon: new Icon(Icons.person)
            ),
          ),
          TextField(
            focusNode: focusNode2,//關(guān)聯(lián)handler2
            decoration: InputDecoration(
                labelText: "密碼",
                hintText: "請輸入登錄密碼",
                prefixIcon: new Icon(Icons.lock)
            ),
          ),
          new Builder(builder: (ctx) {//這里調(diào)用builder其實就是new 一個自定義的無狀態(tài)的widget,快捷創(chuàng)建的封裝類,原理就是內(nèi)部的build方法中傳遞上下文給我們實現(xiàn)的子組件
            return Column(
              children: <Widget>[
                RaisedButton(
                  child: Text("點擊當前按鈕切換到第二個焦點"),
                  onPressed: () {//按鈕的點擊事件
                    FocusScope.of(context).requestFocus(focusNode2);//將當前的焦點交給第二個锌介,context包含了當前整個完整的節(jié)點樹的完整信息記錄嗜诀,可以獲取當前的焦點所在的組件等
                    //切換完成,我們這里使用控制器獲取當前的輸入的用戶名的值孔祸,輸出出來
                    print('用戶名為:'+userNameController.text);
                  },
                )
              ],
            );
          },
          ),
        ],
      ),
    );
  }

}
Form表單

Form表單作為一個很重要的組件隆敢,flutter中也提供了常見的操作方法,構(gòu)造很簡單崔慧,如下:

const Form({
    Key key,
    @required this.child,//Form中的子組件為必傳拂蝎,并且這里可以傳遞多個組件,但是需要注意的是惶室,這里的子組件全部都是FormField類型温自,而FormField類型其實是TextField的包裝類,之間的區(qū)別就是內(nèi)部多了幾個針對表單的屬性拇涤,比如驗證等
    this.autovalidate = false,//是否對當前表單中所有可以驗證的子組件進行自動驗證捣作,默認為false
    this.onWillPop,//這里如果我們傳遞了一個最終結(jié)果為false的Future,那么就不會出現(xiàn)表單的路由
    this.onChanged,//表單的所有元素的焦點改變或者內(nèi)容改變的時候觸發(fā)的改變的回調(diào)函數(shù)
  }) : assert(child != null),
       super(key: key);

可以看出來Form表單是個包裹的容器鹅士,屬性很少券躁,但是我們一般使用Form進行驗證操作,用來完善業(yè)務(wù),而進行校驗操作也拜,往往需要涉及到FormState 的概念以舒,FormState 是Form的state類,我們使用的時候可以通過Form.of()或者使用key(GlobalKey )來獲取慢哈,該狀態(tài)中我們可以對子組件進行統(tǒng)一的操作蔓钟,具體如下:

方法 說明
FormState.validate() 當前方法會觸發(fā)FormField的 validate回調(diào),如果有一個子組件檢驗失敗卵贱,則結(jié)果就為false
FormState.save() 當前方法會觸發(fā)FormField的save回調(diào)滥沫,將每一個子組件的值保存進表單
FormState.reset() 當前方法會將當前Form的所有子組件的值以及校驗的結(jié)果全部清空,重置整個表單

那么键俱,我們大概知道了方法以及操作以后兰绣,將上面的登錄案例修改一下,加入兩個簡單的驗證编振,用戶名和密碼不能為空缀辩,并且密碼不能小于6位長度,如下:

import 'package:flutter/material.dart';

class FormDemo extends StatefulWidget {
  @override
  _FormDemoState createState() => new _FormDemoState();
}

class _FormDemoState extends State<FormDemo> {
  TextEditingController _unameController = new TextEditingController();//用來控制用戶名的控制器
  TextEditingController _pwdController = new TextEditingController();//用來控制密碼的控制器
  GlobalKey _formKey= new GlobalKey<FormState>();//傳說中的key踪央,我們用來和組件關(guān)聯(lián)的key臀玄,可以獲取組件的很多屬性和操作,以及當前的狀態(tài)等

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),//垂直方向上下分別16px畅蹂,水平方向左右分別24px
        child: Form(
          key: _formKey, //設(shè)置globalKey健无,用于后面獲取FormState
          autovalidate: true, //開啟自動校驗,只有開啟以后魁莉,子組件才能在輸入以后自動校驗睬涧,否則只能調(diào)用FormState.validate() 進行手動校驗
          child: Column(
            children: <Widget>[
              TextFormField(
                  autofocus: true,//第一個輸入框一般我們默認是獲取焦點狀態(tài)
                  controller: _unameController,//綁定用戶名控制器
                  decoration: InputDecoration(
                      labelText: "用戶名",
                      hintText: "請輸入用戶名",
                      icon: Icon(Icons.person)
                  ),
                  // 校驗用戶名
                  validator: (value) {//校驗的回調(diào),參數(shù)為當前組件的值
                    if(null == value || "" == value || value.trim().length == 0){//dart中字符串比較直接==
                      return "用戶名不能為空";
                    }
                  }
              ),
              TextFormField(
                  controller: _pwdController,
                  decoration: InputDecoration(
                      labelText: "密碼",
                      hintText: "請輸入密碼",
                      icon: Icon(Icons.lock)
                  ),
                  obscureText: true,
                  //校驗密碼
                  validator: (value) {
                     if(null == value || "" == value || value.trim().length == 0){//dart中字符串比較直接==
                      return "密碼不能為空";
                     }
                     if(value.trim().length < 6){
                       return "密碼不能小于六位數(shù)";
                     }
                  }
              ),
              // 登錄按鈕
              Padding(
                padding: const EdgeInsets.only(top: 28.0),
                child: Row(
                  children: <Widget>[
                    Expanded(
                      child: RaisedButton(
                        padding: EdgeInsets.all(15.0),//上下左右都是15px邊距
                        child: Text("點擊登錄"),
                        color: Theme.of(context).primaryColor,//這里獲取的顏色為當前app系統(tǒng)主題顏色
                        textColor: Colors.white,
                        onPressed: () {
                          //在點擊事件中募胃,我們需要再去手動校驗一次旗唁,然后就可以做其他操作了,也可以選擇不去手動表單校驗,獲取控制器對應(yīng)的值進行校驗
                          print(_unameController.text);
                          print(_pwdController.text);
                          //我們也可以用過key獲取當前的組件以及當前組件的state(datr語法中使用as就會將當前類型強制轉(zhuǎn)換為后面的類型痹束,這個時候的類型動態(tài)的變成后面的了检疫,所以可以直接用對應(yīng)類型的方法或者屬性,但是as方法有風(fēng)險祷嘶,不是父子類或者with等關(guān)系下屎媳,不要使用,否則會出現(xiàn)異常)
                          if((_formKey.currentState as FormState).validate()){
                            //校驗通過才會執(zhí)行到回調(diào)中论巍,這里我們可以進行提交等操作

                          }
                        },
                      ),
                    ),
                  ],
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

好了烛谊,簡單的登錄以及表單驗證操作完成了,現(xiàn)在嘉汰,我們對常見的基礎(chǔ)組件的了解已經(jīng)很深了丹禀,接下來,我們開始學(xué)習(xí)常見的包裹類組件(本篇博客開頭的SingleChildRenderObjectWidget類型的組件)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市双泪,隨后出現(xiàn)的幾起案子持搜,更是在濱河造成了極大的恐慌,老刑警劉巖焙矛,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件葫盼,死亡現(xiàn)場離奇詭異,居然都是意外死亡村斟,警方通過查閱死者的電腦和手機贫导,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蟆盹,“玉大人脱盲,你說我怎么就攤上這事∪沼В” “怎么了钱反?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長匣距。 經(jīng)常有香客問我面哥,道長,這世上最難降的妖魔是什么毅待? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任尚卫,我火速辦了婚禮,結(jié)果婚禮上尸红,老公的妹妹穿的比我還像新娘吱涉。我一直安慰自己,他們只是感情好外里,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布怎爵。 她就那樣靜靜地躺著,像睡著了一般盅蝗。 火紅的嫁衣襯著肌膚如雪鳖链。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天墩莫,我揣著相機與錄音芙委,去河邊找鬼。 笑死狂秦,一個胖子當著我的面吹牛灌侣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播裂问,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼侧啼,長吁一口氣:“原來是場噩夢啊……” “哼玖姑!你這毒婦竟也來了伍茄?” 一聲冷哼從身側(cè)響起疮鲫,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嗦哆,沒想到半個月后符喝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闪彼,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年协饲,在試婚紗的時候發(fā)現(xiàn)自己被綠了畏腕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡茉稠,死狀恐怖描馅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情而线,我是刑警寧澤铭污,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站膀篮,受9級特大地震影響嘹狞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜誓竿,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一磅网、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧筷屡,春花似錦涧偷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至规哲,卻和暖如春跟啤,著一層夾襖步出監(jiān)牢的瞬間诽表,已是汗流浹背唉锌。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留竿奏,地道東北人袄简。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像泛啸,于是被迫代替她去往敵國和親绿语。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361